#!/usr/bin/env python

##    Copyright (c) 2006-2007, David Loveall
##
##    All rights reserved.
##
##    Redistribution and use in source and binary forms, with or without
##    modification, are permitted provided that the following conditions are met:
##
##    Redistributions of source code must retain the above copyright notice, this
##    list of conditions and the following disclaimer.
##    Redistributions in binary form must reproduce the above copyright notice,
##    this list of conditions and the following disclaimer in the documentation
##    and/or other materials provided with the distribution.
##    Neither the name of the creator nor the names of its contributors may be used
##    to endorse or promote products derived from this software without specific
##    prior written permission.
##    THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
##    "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
##    LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
##    A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
##    CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
##    EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
##    PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
##    PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
##    LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
##    NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
##    SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

from __future__ import with_statement
import sys, os, ctypes.util, errno, SocketServer, struct, threading, string, re, time, subprocess
from ctypes import *

debug = False

mypath = sys.path[0]
if os.path.isfile(mypath):
        mypath = os.path.dirname(mypath)

if os.path.isfile(os.path.join(mypath, "libewf.dll")):
        libewf_path = os.path.join(mypath, "libewf.dll")
else:
        libewf_path = ctypes.util.find_library('libewf')

if libewf_path:
	libewf=CDLL(libewf_path)
else:
	sys.stderr.write("Couldn't find libewf.\n")
	sys.exit(2)

def c_off_t(i):
	return ctypes.c_int64(i)

def c_char_p_array(l):
	count = len(l)
	ArrayType = ctypes.c_char_p * count
	results = ArrayType()
	for i in range(count):
		results[i] = ctypes.c_char_p(l[i])
	return results

def find_image_segments(filename):
	basename = os.path.basename(filename)
	rootname, extname = os.path.splitext(basename)
	dirname = os.path.dirname(filename)
	contents = os.listdir(dirname)
	filenames = []
	for item in contents:
		itemroot, itemext = os.path.splitext(item)
		if itemroot == rootname and itemext[:2] == extname[:2] and libewf.libewf_check_file_signature(os.path.join(dirname, item)) == 1:
			filenames.append(os.path.join(dirname, item))
	filenames.sort()
	return filenames

def find_partitions(disktype):
        partitions = {}
        pattern = re.compile('\n *Partition (?P<number>\d+):[^\n]*\((?P<size>\d+)[^\n]* (?P<start>\d+\+?\d*)(?:, bootable)?\)\n *Type (?P<type>[^\n]*) \([^\n]*\)(?: \(GUID [0-9a-fA-F-]+\))?', re.DOTALL)
        result = pattern.finditer(disktype)
        for match in result:
                if match.group('type') != "0x05" and match.group('type') != "0xEE":
                        partitions[match.group('number')] = {'offset': eval(match.group('start'))*512, 'size': eval(match.group('size'))}
        return partitions

IMDPROXY_REQ = {0:'IMDPROXY_REQ_NULL', 1:'IMDPROXY_REQ_INFO', 2:'IMDPROXY_REQ_READ', 3:'IMDPROXY_REQ_WRITE', 4:'IMDPROXY_REQ_CONNECT'} 

class ewfHandler(SocketServer.StreamRequestHandler):
        def handle(self):
                self.close_connection = False

                self.handle_one_request()
                while not self.close_connection:
                        self.handle_one_request()

        def handle_one_request(self):
                raw_request = self.rfile.read(sizeof(c_ulonglong))
                if len(raw_request) == 0:
                        self.close_connection = True
                        request = 0
                else:
                        request = struct.unpack('Q', raw_request)[0]
                
                if IMDPROXY_REQ.has_key(request):
                        exec("self." + IMDPROXY_REQ[request] + "()")
                else:
                        print "Undefined Request"

        def IMDPROXY_REQ_NULL(self):
                if debug:
                        print "IMDPROXY_REQ_NULL"

        def IMDPROXY_REQ_INFO(self):
                if debug:
                        print "IMDPROXY_REQ_INFO"
                req_alignment = 512
                flags = 0x1 # Read-only
                if debug:
                        print "file_size", self.server.media_size
                        print "req_alignment", req_alignment
                        print "flags", flags
                response = struct.pack('QQQ', self.server.media_size, req_alignment, flags)
                self.wfile.write(response)

        def IMDPROXY_REQ_READ(self):
                if debug:
                        print "IMDPROXY_REQ_READ"
                offset, length = struct.unpack('QQ', self.rfile.read(2*sizeof(c_ulonglong)))
                if debug:
                        print "offset", offset
                        print "length", length
                buf = create_string_buffer(length)
                with self.server.read_lock:
                        result = libewf.libewf_read_random(self.server.ewfHandle, buf, c_size_t(length), c_off_t(offset))
                if result == -1:
                        errorno = errno.EBADF
                        length = 0
                else:
                        errorno = 0
                        length = result
                response = struct.pack('QQ', errorno, length)
                self.wfile.write(response)
                self.wfile.write(buf.raw)

        def IMDPROXY_REQ_WRITE(self):
                if debug:
                        print "IMDPROXY_REQ_WRITE"
                offset, length = struct.unpack('QQ', self.rfile.read(2*sizeof(c_ulonglong)))
                if debug:
                        print "offset", offset
                        print "length", length
                response = struct.pack('QQ', errno.EBADF, 0)
                self.wfile.write(response)                

        def IMDPROXY_REQ_CONNECT(self):
                if debug:
                        print "IMDPROXY_REQ_CONNECT"
                flags, length = struct.unpack('QQ', self.rfile.read(2*sizeof(c_ulonglong)))
                if debug:
                        print "flags", flags
                        print "length", length
                error_code = 0
                object_ptr = 0
                response = struct.pack('QQ', error_code, object_ptr)
                self.wfile.write(response)
	
class ewfServer(SocketServer.ThreadingTCPServer):
        def __init__(self, server_address, RequestHandlerClass, ewfHandle, media_size):
                SocketServer.ThreadingTCPServer.__init__(self, server_address, RequestHandlerClass)
                self.ewfHandle = ewfHandle
                self.media_size = media_size
                self.read_lock = threading.Lock()

def call_imdisk(partitions, port):
        for partitionNumber in partitions:
                time.sleep(1)
                os.system("imdisk -a -t proxy -m #: -o ro,ip -f 127.0.0.1:" + str(port) + " -b " + str(partitions[partitionNumber]['offset']) + " -s " + str(partitions[partitionNumber]['size']))
        sys.exit()

def main():
	libewf.libewf_get_version.restype = c_char_p
	if not libewf.libewf_get_version() == '20070717':
		sys.stderr.write("Using libewf-" + libewf.libewf_get_version() + ". Tested with libewf-20070717.\n")

	filenames = []
	for arg in sys.argv[1:]:
		if os.path.isfile(arg):
			if libewf.libewf_check_file_signature(arg) == 1:
                                filenames.append(arg)
	if len(filenames) == 0:
		sys.stderr.write("ewf segment filename(s) required.\n")
		sys.exit(1)
	if len(filenames) == 1:
		filenames = find_image_segments(os.path.abspath(filenames[0]))

        if not os.path.isfile(os.path.join(mypath, 'disktype-ewf.exe')):
                sys.stderr.write("Couldn't find disktype-ewf.exe.\n")
        cmd = [os.path.join(mypath, 'disktype-ewf.exe')]
        p = subprocess.Popen(cmd + filenames, stdout=subprocess.PIPE).stdout
        disktype = p.read()
        p.close()
        partitions = find_partitions(disktype)

	ewfHandle = libewf.libewf_open(c_char_p_array(filenames), c_ulong(len(filenames)), c_char('r'))
	if ewfHandle == 0:
		sys.stderr.write("Couldn't open EWF file.\n")
		sys.exit(1)
	libewf.libewf_parse_header_values(ewfHandle, 3)

	libewf.libewf_get_media_size.restype = c_int64
	media_size = libewf.libewf_get_media_size(ewfHandle)

	if len(partitions) == 0:
		partitions = {1: {'offset': 0, 'size': media_size}}

        server_address = ('127.0.0.1', 0)
	myServer = ewfServer(server_address, ewfHandler, ewfHandle, media_size)
	sa = myServer.socket.getsockname()
	print "Serving on", sa[0], "port", sa[1], "..."

        mounter = threading.Thread(target=call_imdisk, args=[partitions, sa[1]])
        mounter.start()
	try:
                for i in range(len(partitions)):
                        myServer.handle_request()
        	#myServer.serve_forever()
        except KeyboardInterrupt:
                print "Exiting..."
                sys.exit(0)

if __name__ == '__main__':
	main()

