#!/usr/bin/python
# This software was developed at the National Institute of Standards
# and Technology in whole or in part by employees of the Federal
# Government in the course of their official duties. Pursuant to
# title 17 Section 105 of the United States Code portions of this
# software authored by NIST employees are not subject to copyright
# protection and are in the public domain. For portions not authored
# by NIST employees, NIST has been granted unlimited rights. NIST
# assumes no responsibility whatsoever for its use by other parties,
# and makes no guarantees, expressed or implied, about its quality,
# reliability, or any other characteristic.
#
# We would appreciate acknowledgement if the software is used.
# generate MD5s for a directory in Digital Forensics XML Output
# Uses dublin core.
# Find out more at http://www.dublincore.org/documents/dc-xml-guidelines/
# http://dublincore.org/documents/dc-citation-guidelines/
# http://jedmodes.sourceforge.net/doc/DC-Metadata/dcmi-terms-for-jedmodes.html
# http://www.ukoln.ac.uk/metadata/dcmi/mixing-matching-faq/
__version__ = '1.0.0'
import sys
import os.path
import hashlib
from xml.sax.saxutils import escape
xmloutputversion = '0.3'
dfxml_ns = {'xmlns':'http://afflib.org/fiwalk/',
'xmlns:xsi':'http://www.w3.org/2001/XMLSchema-instance',
'xmlns:dc':'http://purl.org/dc/elements/1.1/'}
class xml:
def __init__(self):
self.stack = []
def set_outfilename(self,fn):
self.outfilename = fn
def open(self,f):
if hasattr(f, 'seek'):
self.f = f
if isinstance(f, str):
self.f = open(f,'w')
self.write("\n")
def dublin_core(self,dc_record):
self.push('metadata',dfxml_ns,attrib_delim='\n ')
for (n,v) in dc_record.items():
if v!=None:
self.xmlout(n,v)
self.pop('metadata')
self.write('\n')
def provenance(self):
global args
if args.allprovenance or \
args.commandline or \
args.pythonversion:
self.push('creator')
self.xmlout('program', os.path.basename(sys.argv[0]))
self.xmlout('version', __version__)
self.push('execution_environment')
if args.allprovenance or args.commandline:
self.xmlout('command_line', ' '.join(sys.argv))
if args.allprovenance or args.pythonversion:
self.xmlout('python_version', sys.version)
self.pop('execution_environment')
self.pop('creator')
self.f.write('\n')
def push(self,tag,attribs={},attrib_delim=' '):
"""Enter an XML block, with optional attributes on the tag"""
self.tagout(tag,attribs=attribs,attrib_delim=attrib_delim,newline=True)
self.stack.append(tag)
def pop(self,v=None):
"""Leave an XML block"""
if v: assert v==self.stack[-1]
self.tagout("/"+self.stack.pop(),newline=True)
def tagout(self,tag,attribs={},attrib_delim=' ',newline=None):
"""Outputs a plain XML tag and optional attributes"""
self.f.write("<%s" % tag)
if attribs:
self.f.write(" ")
count = len(attribs)
for (n,v) in attribs.items():
self.f.write("%s='%s'" % (n,escape(v)))
count -= 1
if count>0: self.f.write(attrib_delim)
self.f.write(">")
if newline: self.f.write("\n")
def xmlout(self,tag,value,attribs={}):
"""Output an XML tag and its value"""
self.tagout(tag,attribs,newline=False)
self.write(escape(str(value)))
self.write("%s>\n" % tag)
def write(self,s):
self.f.write(s)
def xmlout_times(fn,x,fistat):
global args
for (time_tag, time_field) in [
("mtime", "st_mtime"),
("atime", "st_atime"),
("ctime", "st_ctime"),
("crtime", "st_birthtime")
]:
if time_field in dir(fistat):
attrs_dict = dict()
time_data = getattr(fistat,time_field)
#Format timestamp data
if args.iso_8601:
import dfxml
text_out = str(dfxml.dftime(time_data))
else:
attrs_dict["format"] = "time_t"
text_out = str(time_data)
x.xmlout(time_tag, text_out, attrs_dict)
def emit_directory(fn,x,partno=None):
x.push("fileobject")
if not args.nofilenames:
if args.stripprefix and fn.startswith(args.stripprefix):
x.xmlout("filename",fn[ len(args.stripprefix) : ])
elif args.stripleaddirs and args.stripleaddirs > 0:
x.xmlout("filename","/".join(fn.split("/")[args.stripleaddirs:]))
else:
x.xmlout("filename",fn)
if not args.nometadata:
fistat = os.stat(fn)
if partno:
x.xmlout("partition",partno)
x.xmlout("inode",fistat.st_ino)
x.xmlout("filesize",fistat.st_size)
xmlout_times(fn,x,fistat)
x.xmlout("name_type", "d")
if args.addfixml:
x.write(args.addxml)
x.pop("fileobject")
x.write("\n")
def hash_file(fn,x,partno=None):
import hashlib
try:
f = open(fn)
except IOError as e:
sys.stderr.write("%s: %s\n" % (fn,str(e)))
return
x.push("fileobject")
if not args.nofilenames:
if args.stripprefix and fn.startswith(args.stripprefix):
x.xmlout("filename",fn[ len(args.stripprefix) : ])
elif args.stripleaddirs and args.stripleaddirs > 0:
x.xmlout("filename","/".join(fn.split("/")[args.stripleaddirs:]))
else:
x.xmlout("filename",fn)
if not args.nometadata:
fistat = os.stat(fn)
if partno:
x.xmlout("partition",partno)
x.xmlout("inode",fistat.st_ino)
x.xmlout("filesize",fistat.st_size)
xmlout_times(fn,x,fistat)
#Distinguish regular files from directories, if directories are requested
if args.includedirs:
x.xmlout("name_type", "r")
if args.addfixml:
x.write(args.addxml)
if args.md5: md5_all = hashlib.md5()
if args.sha1: sha1_all = hashlib.sha1()
if args.sha256: sha256_all = hashlib.sha256()
chunk_size = 65536 # default chunk size
if args.piecewise:
chunk_size = args.piecewise
if args.piecewise:
x.push("byte_runs")
offset = 0
read_error = False
while True:
buf = ""
try:
buf = f.read(chunk_size)
except:
warning = "Warning: read() failed. Cannot produce hash."
read_error = True
x.write("\n")
sys.stderr.write("%s File: %r\n" % (warning, fn))
buf = ""
if buf=="": break
buf = buf.encode('utf-8')
if args.md5: md5_all.update(buf)
if args.sha1: sha1_all.update(buf)
if args.sha256: sha256_all.update(buf)
if args.piecewise:
x.write("