""" This implemented the Mounted Filesystem driver to provide access to filesystem which we dont have a driver for. We use the kernel to mount the filesystem on a directory in the upload directory. We dont have access to slack, deleted files etc, but its something. """ import pyflag.FileSystem as FileSystem import pyflag.pyflaglog as pyflaglog from pyflag.FileSystem import FileSystem,DBFS,File import pyflag.DB as DB import pyflag.IO as IO import pyflag.FlagFramework as FlagFramework import time,os,posixpath import math import bisect import pyflag.conf config=pyflag.conf.ConfObject() import os.path import stat import StringIO ## This is just a psuedo filesystem to load the image into the VFS: class Raw(DBFS): """ A psuedo file system to load raw images """ name="Raw" order = 50 def load(self, mount_point, iosource_name, scanners=None, directory = None): ## Ensure that mount point is normalised: mount_point = posixpath.normpath(mount_point) DBFS.load(self, mount_point, iosource_name) ## Just add a single inode: inode = "I%s|o0" % iosource_name self.VFSCreate(None,inode, "%s/raw_filesystem" % mount_point) ## Call the scanners on the new inode if scanners: scanner_string = ",".join(scanners) pdbh = DB.DBO() pdbh.insert('jobs', command = 'Scan', arg1 = self.case, arg2 = inode, arg3= scanner_string, cookie = int(time.time()), _fast = True, ) ## This is the IO Source: class Mounted(IO.Image): """ Treat a mounted directory as an image """ size = 0 def seek(self, offset, whence=0): pass def close(self): pass def read(self, length=None): return '' def form(self,query, result): result.fileselector("Select a directory or a file in the image", name="filename") def open(self, name, case, query=None): dbh = DB.DBO(case) if query: if query.has_key('filename'): ## Check to see that filename is in the upload dir: filename = os.path.normpath(query['filename']) ## Check if iosrc is a directory or a file: try: os.listdir(filename) query['directory'] = filename except OSError,e: print e query['directory'] = os.path.dirname(filename) if not query.has_key('directory'): raise IOError("Mandatory parameter 'directory' not provided") else: self.directory = query['directory'] ## Check that the name does not already exist: if name: dbh.execute("select * from iosources where name = %r" , name) if dbh.fetch(): raise IOError("An iosource of name %s already exists in this case" % name) ## If we get here we made it successfully so store in db: dbh.insert('iosources', name = query['iosource'], type = self.__class__.__name__, parameters = "%s" % query, _fast = True) else: ## No query provided, we need to fetch it from the db: dbh.check_index('iosources','name') dbh.execute("select parameters from iosources where name = %r" , name) row = dbh.fetch() query = FlagFramework.query_type(string=row['parameters']) try: self.directory = query['directory'] except: self.directory = query['filename'] ## Find the mount point: dbh.execute("select value from filesystems where property='mount point' and iosource=%r", name) row = dbh.fetch() if row: self.mount_point = row['value'] return self class MountedFS(DBFS): """ A class implementing the mounted filesystem option """ name = 'Mounted' order = 100 def guess(self, fd, result, metadata): """ We can only really handle mounted IO sources, we cant actually handle anything else """ DBFS.guess(self, fd, result, metadata) if fd.__class__.__name__ == 'Mounted': return 120 else: return -1 def load(self, mount_point, iosource_name, scanners=None, directory = None): DBFS.load(self, mount_point, iosource_name) iosrc = self.iosource path = iosrc.directory if not path.startswith(posixpath.normpath(config.UPLOADDIR)): path = FlagFramework.sane_join(config.UPLOADDIR, path) path = path.encode("ascii","ignore") pyflaglog.log(pyflaglog.DEBUG,"Loading files from directory %s" % path) dbh_file=DB.DBO(self.case) dbh_file.mass_insert_start('file') dbh_inode=DB.DBO(self.case) dbh_inode.mass_insert_start('inode') if scanners: scanner_string = ",".join(scanners) pdbh = DB.DBO() pdbh.mass_insert_start('jobs') cookie = int(time.time()) ## This deals with a mounted filesystem - we dont get the full ## forensic joy, but we can handle more filesystems than ## sleuthkit can. The downside is that the user has to mount ## the filesystem first, we also need to be running as root or ## we may not be able to stat all the files :-( def insert_into_table(mode ,root ,name): rel_root = FlagFramework.normpath(DB.expand("%s/%s/" , (mount_point, root[len(path):]))) try: s=os.lstat(os.path.join(root,name)) except OSError: pyflaglog.log(pyflaglog.WARNING, DB.expand("Unable to stat %s - mount the directory with the uid option", root)) return inode = DB.expand("I%s|M%s", (iosource_name, s.st_ino)) dbh_inode.insert('inode', inode = inode, uid = s.st_uid, gid = s.st_gid, _mtime = "from_unixtime(%s)" % s.st_mtime, _atime = "from_unixtime(%s)" % s.st_atime, _ctime = "from_unixtime(%s)" % s.st_ctime, status = 'alloc', mode = str(oct(s.st_mode)), size = s.st_size, _fast=True) inode_id = dbh_inode.autoincrement() dbh_file.mass_insert(inode_id = inode_id, inode = inode, mode = mode, status = 'alloc', path = rel_root, name = name) ## If needed schedule inode for scanning: if scanners and mode=='r/r': pdbh.mass_insert( command = 'Scan', arg1 = self.case, arg2 = inode, arg3= scanner_string, cookie=cookie, ) ## Fixme - handle symlinks try: link=os.readlink(DB.expand("%s/%s", (root,name)).encode("utf8")) except OSError: link='' # dbh.execute("insert into inode_%s set inode='M%s',uid=%r,gid=%r, mtime=%r,atime=%r,ctime=%r,mode=%r,links=%r,link=%r,size=%r",(self.table,s.st_ino,s.st_uid,s.st_gid,s.st_mtime,s.st_atime,s.st_ctime,str(oct(s.st_mode))[1:],s.st_nlink,link,s.st_size)) ## Just walk over all the files and stat them all building the tables. for root, dirs, files in os.walk(path): for name in dirs: insert_into_table('d/d',root,name) for name in files: insert_into_table('r/r',root,name) dbh_file.mass_insert_commit() dbh_inode.mass_insert_commit() class MountedFS_file(File): """ access to real file in filesystem """ specifier = 'M' def __init__(self, case, fd, inode): File.__init__(self, case, fd, inode) #strategy: must determine basepath from parent, get our path #from db and then return the file: ## Note this _must_ work because we can only ever be called on ## a mounted iosource - it is an error otherwise: basepath = fd.io.directory self.case = case dbh = DB.DBO(case) dbh.check_index("file" ,"inode") dbh.execute("select path,name from file where inode=%r limit 1",(inode)) row=dbh.fetch() path = row['path'] mount_point = fd.io.mount_point ## Prune the path down to the mount point: if path[:len(mount_point)] != mount_point: raise RuntimeError(DB.expand("Something went wrong - %s should be mounted on %s", (path, mount_point))) path = path[len(mount_point):] path=basepath+'/'+path+"/"+row['name'] if not path.startswith(posixpath.normpath(config.UPLOADDIR)): path = FlagFramework.sane_join(config.UPLOADDIR, path) if os.path.isdir(path): self.fd = StringIO.StringIO('') else: self.fd = open(path,'r') s = os.stat(path) self.size = s.st_size def close(self): self.fd.close() def seek(self, offset, rel=None): if rel!=None: self.fd.seek(offset,rel) else: self.fd.seek(offset) def read(self, length=None): if length!=None: return self.fd.read(length) else: return self.fd.read() def tell(self): return self.fd.tell() def explain(self, query, result): name = self.fd.name ## Trim the upload directory if present if name.startswith(config.UPLOADDIR): name = name[len(config.UPLOADDIR):] result.row("Filename",DB.expand("%s", name), **{'class':'explainrow'}) ## Unit tests: import pyflag.tests as tests class MountedTest(pyflag.tests.ScannerTest): """ Test the Mounted Psuedo Filesystem driver """ test_case = "PyFlagTestCase" test_file = "pyflag_stdimage_0.5.dd" subsystem = "Mounted" fstype = "Mounted" def test01Mounted(self): """ Test that mounted images work """ ## We should be able to see test_file in the file table: dbh=DB.DBO(self.test_case) dbh.execute("select * from file where name = %r",self.test_file) row = dbh.fetch() self.assert_(row,"Unable to find the file in the VFS???") class RawTest(pyflag.tests.ScannerTest): """ Test a Raw filesystem """ test_case = "PyFlagTestCase" test_file = "pyflag_stdimage_0.4.dd" subsystem = "Standard" fstype = "Raw"