#!/usr/bin/python

import sys
from sys import stdin
from sys import stdout
import os
import re
import datetime
import time
import hashlib
import subprocess
#from ft_di_run import *
from ft_di_desc import *

#############################################################
#                                                           #
#  Generate Federated Testing Report -- Disk Imaging        #
#                                                           #
#  parms: test-configuration.txt  /.../path/... [path]      #
#                                                           #
#############################################################

dr_tab = ""
# extract version info 
vlist = set()
def vcheck(line):
	if line.find('Tool:') >= 0:
		line = line[6:]
	at = line.find('@(#)')
	if at >= 0:
		vlist.add(line[at+5:].strip())
	elif line.find('%'+'Z%') >= 0: vlist.add('Uncontrolled Version')
	return

# index of test case evaluation criteria (ft_di_run)
eval_map = {
 "FT-DI-01":eval_di_01,
 "FT-DI-02":eval_di_02,
 "FT-DI-03":eval_di_03,
 "FT-DI-04":eval_di_04,
 "FT-DI-05":eval_di_05,
 "FT-DI-06":eval_di_06,
 "FT-DI-07":eval_di_07,
 "FT-DI-08":eval_di_08,
 "FT-DI-09":eval_di_09,
 "FT-DI-10":eval_di_10,
 "FT-DI-11":eval_di_11,
 "FT-DI-12":eval_di_12,
 "FT-DI-13":eval_di_13,
 "FT-DI-14":eval_di_14,
 "FT-DI-15":eval_di_15}

full_report = False
#for arg in sys.argv:
class drive_desc:
	def __init__ (x):
		x.sectors = 0
		x.md5 = ''
		x.sha1 = ''
		x.sha256 = ''
		x.sha512 = ''
		x.md5b = ''
		x.sha1b = ''
		x.sha256b = ''
		x.sha512b = ''
		x.type = 'not known'
		x.h = {} # late addition; hashes indexed by type: MD5, SHA1, etc
		x.nt_size = 'Not set'
		x.nt = {}
# data struct to track case info
class case_desc:
	def __init__ (x):
		x.case = 'FT-??'
		x.host = 'N/A'
		x.user = 'N/A'
		x.src = 'N/A'
		x.image = 'N/A'
		x.dst = 'N/A'
		x.date = 'N/A'
		x.wb = ''

case_admin = {}
ref_drives = {}
fmt_map = {}
start_time = str(datetime.datetime.now())
start_at = start_time
start_time = re.sub(r'\.[0-9]*','',start_time)
start_time = re.sub('[ :]','+',start_time)
if len(sys.argv) == 4:
	full_report = True
	html_file_name = "test-report-di-"+start_time+".html"
	html_file = sys.argv[3]+"/"+html_file_name
try:
	if full_report: html = open(html_file,"wt")
	else: html = stdout
except IOError as e:
	html = open("/dev/null","wt")
try:
	sd = open("/tmp/test-report-dribble.txt","wt")
except IOError as e:
	sd = open("/dev/null","wt")

os_info = os.uname()
ver = "Tool: @(#) ft-di-prt_test_report.py Version 1.24 created 05/23/18 at 16:08:06\n"+"<br>OS: %s Version %s\n"%(
	os_info[0],os_info[2])
sd.write ('Generate Test Report\n')
sd.write(ver+'\n')
sd.write ('Start: '+start_at+'\n')

sd.write("Args:"+str(sys.argv)+'\n')
if full_report:
	sd.write('Full report\n'+html_file+'\n')
else: sd.write('Brief report\n')

Test_mode = False

head = """<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head>
  <meta content="text/html; charset=ISO-8859-1"
 http-equiv="Content-Type">
  <title>Test Results for Disk Imaging Tool</title>
  <meta content="J Lyle NIST/CFTT" name="author">
  <meta content="Federated Testing Disk Imaging Test Results"
 name="description">
	<style>
	em.x {color: green;font-size: 100%;font-style:italic;font-weight:bold;}
	em.p {bgcolor: green;font-size: 100%;font-weight:bold;}
	th {
	   border-collapse: collapse;
	   border: 2px solid black;
	   text-align: center;
	   } 
	table, th, td {
	   border-collapse: collapse;
	   border: 2px solid black;
	   } 
	</style>
</head>
<body>
"""
tail = """
</body>
</html>
"""

old_case_dict = {
"FT-DI-01":"Acquire a drive to an image file. Repeat variations for each interface that might be acquired. ",
"FT-DI-02":"Restore the image file of a drive to a destination clone. Repeat variations for each interface acquired in FT-DI-01.",
"FT-DI-03":"Acquire a removable media device to an image file. Repeat variations for each type of removable media that might be acquired. ",
"FT-DI-04":"Restore an image file of removable media to a destination clone. Repeat variations for each interface imaged in FT-DI-03.",
"FT-DI-05":"Acquire a partition to an image file. Repeat variations for each type of partition that might be acquired. ",
"FT-DI-06":"Restore an image file of a partition to a destination clone. Repeat vacations for each partition acquired in FT-DI-05.",
"FT-DI-07":"Acquire a drive to a destination clone. Repeat variations for each interface that might be acquired.",
"FT-DI-08":"Acquire a removable media device to a destination clone. Repeat variations for each removable media device that might be acquired.",
"FT-DI-09":"Acquire a partition to a destination clone. Repeat variations for each type of partition that might be acquired.",
"FT-DI-10":"Acquire a drive to an image file without enough space for the image file.",
"FT-DI-11":"Restore an image file to a destination clone without enough space.",
"FT-DI-12":"Acquire a drive to a destination clone without enough space.",
"FT-DI-13":"Compute the hash value of the acquired data within an image file.",
"FT-DI-14":"Compute the hash value of a drive.",
"FT-DI-15":"Acquire a drive with faulty sectors to a destination clone. Repeat variations for tool options."}

# <p>Tool generated log files and reports are not automatically
# examined by federated testing and should be inspected manually.
# Any discrepancies observed in tool log files could be noted and then inserted here.

report_header_text_part_1 = '''
<em class="x">
<br>Text formatted like this (Bold, Italics and Green) should be
removed and replaced. These are instructions
to the report writer.
</em>
<h1>Tool Description</h1>
<p>Tool Name: %s
<br>Tool Version: %s
<br>Vendor: 
<font color="green"><b><i>Insert vendor name and contact information.
<font color="black"></b></i>
<br><br>
<font color="green"><b><i>Select one of the following. For hardware imaging devices, include firmware version.
For software tools, include the operating system used.<font color="black"></b></i>
<br>Firmware Version:
<font color="green"><b><i>Insert name and version of device firmware for running imaging tool.<font color="black"></b></i>
<br>Operating System: 
<font color="green"><b><i>Insert name and version of operating system for running imaging tool.<font color="black"></b></i>
'''
report_header_text_part_2 = '''
<br><br>
<h1>Testing Organization</h1>
<font color="green"><b><i>The following items in this section may be included or omitted as per organization's
policy for tool test reports.<font color="black"></b></i>
<br>Organization conducting test:
<font color="green"><b><i>Insert vendor name.<font color="black"></b></i>
<br>Contact:
<font color="green"><b><i>Insert contact name.<font color="black"></b></i>
<br>Report date:
<font color="green"><b><i>Insert date.<font color="black"></b></i>
<br>Authored by:
<font color="green"><b><i>Insert author name.<font color="black"></b></i>
<br>Reviewed by:
<font color="green"><b><i>Insert name of reviewer.<font color="black"></b></i>
<br>Reviewed by date:
<font color="green"><b><i>Insert date.<font color="black"></b></i>
<br>Approved:
<font color="green"><b><i>Insert name of approving official.<font color="black"></b></i>
<br>Approved by date:
<font color="green"><b><i>Insert date.<font color="black"></b></i>
<br>
<br>This test report was generated using CFTT's Federated Testing Forensic
Tool Testing Environment, see <a href="http://www.cftt.nist.gov/federated-testing.html">
Federated Testing Home Page</a>.
<br>
<h1>How to Read This Report</h1>
<br><br>This report is organized into the following sections:
<br><em class=x>Of course, you can reorganize sections, but then you should update
this list.</em><br>
<ol>
<li>Tested Tool Description. The tool name, version, vendor information, support 
environment (e.g., operating system version, device firmware version, etc.) 
version are listed.
<li>Testing Organization. Contact information and approvals.
<li>Results Summary. This section identifies any significant anomalies
observed in the test runs.
This section provides a narrative of key findings identifying where the tool meets
expectations and provides a summary of any ways the tool did not
meet expectations.
The section also provides any observations of interest about the tool or about testing
the tool including any observed limitations or organization imposed
restrictions on tool use.
<li>Test Environment. Description of hardware and software used in tool testing in
sufficient detail to satisfy the testing organization's policy and requirements.
<li>Test Result Details by Case. Automatically generated test results that identify
anomalies.
<li>Appendix: Additional Details. Additional administrative details for each test case
such as, who ran the test, when the test was run, computer used, etc.
</ol>
<h1>Results Summary</h1>
<em class=x>
Provide a narrative of key findings -- did the tool meet
expectations. If not provide a summary of how the tool did not
meet expectations.
Provide any observations of interest about the tool or about testing
the tool including any observed limitations or organization imposed
restrictions on tool use.
</em>
<h1>Test Environment & Selected Cases</h1>
<br>Hardware:
<font color="green"><b><i>List and describe any hardware used during testing in sufficient detail
to repeat the tests.<font color="black"></b></i><br><br>
<font color="green"><b><i>Select one of the following. For hardware imaging devices, include firmware version.
For software tools, include the operating system used.<font color="black"></b></i>
<br>Firmware Version:
<font color="green"><b><i>Insert name and version of device firmware for running imaging tool.<font color="black"></b></i>
<br>Operating System: 
<font color="green"><b><i>Insert name and version of operating system for running imaging tool.<font color="black"></b></i>
<br>Software:
<font color="green"><b><i>List and describe any additional software used during testing in sufficient detail
to repeat the tests.<font color="black"></b></i>
'''
#<font color="magenta"><b><i>
#Need to finish changing stuff around before writting this!<font color="black"></b></i>
main_section = '''
<h1>Test Result Details by Case</h1>
This section presents test results grouped by function.
'''


table_head = """
<table style="text-align: left;" border="2" cellpadding="2" cellspacing="2">
<tbody>
"""

table_tail = """
</tbody>
</table>
"""

wb_table_head = '''
<br><br>
<em class="x">
This is a table of write blockers used to protect source drives.
The user needs to fill in the firmware version of each write blocker.
</em>
<br>
<br>
''' + table_head+ ("<caption><b>Write Blockers Used in Testing</b></caption>"+'\n')

def scan_blockers(path):
#	print "Scan for write blockers"
	dlist = os.listdir(path)
	clist = []
	bset = set()
	mset = set()
	for file in dlist:
		if file.lower()[0:8] == 'ft-di-01': clist.append(file)
		if file.lower()[0:8] == 'ft-di-03': clist.append(file)
		if file.lower()[0:8] == 'ft-di-07': clist.append(file)
		if file.lower()[0:8] == 'ft-di-08': clist.append(file)
	for test_case in clist:
#		print test_case,
		case_path = path + '/' + test_case + '/case.txt'
		if os.access(case_path,os.F_OK) == 1: status = 'OK'
		else: status = 'missing'
#		print ' '+status
		if status == 'OK':
			cfile = open(case_path,"rt")
			blocker = ';'
			blocker_fw = ''
			for line in cfile:
				if line.startswith('write blocker firm'):
					blocker_fw = line.split(':')[1].rstrip().strip()
				if line.startswith('write blocker:'):
					blocker = line.split(':')[1].rstrip().strip()
					blocker = blocker.replace(')','')
					blocker = blocker.replace(' (',';')
					bset.add(test_case.upper()[9:]+':'+blocker)
			mset.add(blocker.split(';')[0]+'?'+blocker_fw)
#			print 'Blocker (%s)'%(blocker)
#	print 'Blocker set'
#	print mset
	slist = []
	for x in bset:
		slist.append(x)
	slist.sort()
	blocker_txt = ''
	for x in slist:
		blocker_txt += '<br>' + x.replace(':',' drive with ').replace(
			';',' connected to PC by ')+" interface"
	blocker_txt = blocker_txt.replace('SATA48','Large (> 138GB) SATA')
	blocker_txt = blocker_txt.replace('ATA48','Large (> 138GB) ATA')
	blocker_txt = blocker_txt.replace('SATA28','Small (< 138GB) SATA')
	blocker_txt = blocker_txt.replace('ATA28','Small (< 138GB) ATA')
	return (blocker_txt,mset)

def get_ver_old():
	vname = '/var/www/include/global.php'
	if os.access(vname,os.F_OK) == 1:
		vfile = open (vname,"rt")
		n = 2
		rel_date = "Unknown"
		rel_ver = "Unknown"
		for line in vfile:
			pass
			if line.startswith("$release_date"):
				rel_date = line.split('"')[1]
				n = n - 1
			if line.startswith("$version"):
				rel_ver = line.split('"')[1]
				n = n - 1
			if n == 0: break
		vfile.close()
		return "Federated Testing Version %s, released %s"%(rel_ver,rel_date)
	else: return "Unknown Version"

def get_ver():
	# find: where is our federated testing version and release information?	
	if os.access('/var/www/include/global.php',os.F_OK) == 1:
		vname = '/var/www/include/global.php'
	elif os.access('/Applications/MAMP/fedtesting/working/htdocs/include/global.php',os.F_OK) == 1:
		vname = '/Applications/MAMP/fedtesting/working/htdocs/include/global.php'
	else:
		vname = ''
		
	if vname != '':
		vfile = open (vname,"rt")
		n = 2
		rel_date = "Unknown"
		rel_ver = "Unknown"
		for line in vfile:
			pass
			if line.startswith("$release_date"):
				rel_date = line.split('"')[1]
				n = n - 1
			if line.startswith("$version"):
				rel_ver = line.split('"')[1]
				n = n - 1
			if n == 0: break
		vfile.close()
		return "Federated Testing Version %s, released %s"%(rel_ver,rel_date)
	else: return "Unknown Version"

#########################################################
def shut_down(success):
	finish_time = str(datetime.datetime.now())
	html.write("<br>\n")
	if not success:
		html.write("<H1>Report Generation Failed</H1>\n")
		sd.write('\nReport Failed\n')
	html.write (ver+'\n<br>')
	html.write("Done: "+finish_time+'\n')
	html.write("<br>\n")
	html.write('\n'+get_ver()+'\n')
	sd.write("\nDone: "+finish_time+'\n')
	html.write("<br>\n")
	if full_report: html.write(tail+'\n')
	if full_report: print html_file_name
	exit (0)

#######################################################
def scan_drives(path):
	global dr_tab
	qlist = os.listdir(path+"/setup")
	dlist = []
	for q in qlist:
		if os.path.isdir(path+"/setup/"+q): dlist.append(q)
	sd.write(str(dlist)+'\n')
	dlist.sort()
	for d in dlist:
		drive = {}
		drive ['id'] = d
		drive ['w'] = '-'
		drive ['s'] = '0'
		drive ['dev'] = '='
		drive ['type'] = '?'

		sd.write('\nprocess drive: %s\n'%(d))
		fflist = os.listdir(path+"/setup/"+d)
		flist = []
		for fn in fflist:
			if re.match('setup-',fn):flist.append(fn)

		if os.access(path+"/setup/"+d+"/setup-wipe.txt",os.F_OK):
			f = open(path+"/setup/"+d+"/setup-wipe.txt")
			sd.write('*** Scan %s setup-wipe.txt <<<<<<<<=====\n'%(d))
			for line in f:
				vcheck(line)
				v = line.strip().split(' ')
				if line.startswith("Unable to open"):
					sd.write('*** UNABLE TO OPEN %s\n'%(v[3]))
					drive['dev'] = v[3]
					drive['w'] = 'Fail'
					break
				elif line.startswith("Drive type:"):
					sd.write ('====> type: '+line+"(%s)/n"%(v[1]))
					drive['type'] = v[2]
				elif line.startswith("Wipe Drive"):
					drive['dev'] = v[2]
					sd.write('*** WIPE DRIVE %s\n'%(v[2]))
				elif line.find("sectors wiped with") > 0:
					drive['s'] = v[0]
					drive['w'] = 'known'
#					drive['w'] = v[4]
					sd.write('*** SECTORS WIPED WITH %s/%s\n'%(v[0],v[4]))
				elif line.startswith("Wipe "):
#					drive['s'] = v[1]
#					drive['w'] = v[8]
					sd.write('======= %s =====\n'%(line+str(v)))
					drive['w'] = 'known'
#					drive['dev'] = v[6]
#					sd.write('*** WIPE %s/%s/%s\n'%(v[1],v[6],v[8]))
			f.close()
		else:
			drive['s'] = '0'
			drive['w'] = 'N/A'
			drive['dev'] = 'N/A'
			drive['type'] = 'N/A'

#		print('Found %s %s %s %s #########\n'%(drive['s'],drive['w'],drive['dev'],
#			drive['type']))
		sd.write('Found %s %s %s %s #########\n'%(drive['s'],drive['w'],drive['dev'],
			drive['type']))
		hash_seen = False
		for file_name in flist:
			sd.write('=== Consider %s \n'%(file_name))
			src_drive = drive_desc()
			src_drive.type = drive['type']
			hash_list = []
			if file_name.find('hash') != -1:
				f = open(path+"/setup/"+d+"/"+file_name)
				sd.write('=== Scan %s \n'%(file_name))
				ht = f.readlines()
				f.close()
				hash_vector = ['/dev/sdz','N/A','N/A','N/A','N/A']
				full_hashes = {'MD5':'N/A','SHA1':'N/A','SHA256':'N/A','SHA512':'N/A'}
				fmt = ""
				hash_seen = True
				for line in ht:
					vcheck(line)
					if line.startswith('NTFS '):
#						print 'NTFS ==>',line,
						if line.find('File System size') > 0:
							fields = line.strip().split(':')
							src_drive.nt_size = fields[1]
						if line.find('MD5') > 0 or line.find('SHA') > 0:
#							print 'hash: ',line.strip()
							fields = line.strip().split(':')
							src_drive.nt[fields[0]] = fields[1]
						continue
					if line.find('Format:') != -1:
						fmt = line.strip().split(' ')[1]
					if line.find('Source device:') != -1:
						fields = line.strip().split(' ')
						hash_vector[0] = fields[2][5:]
						sz = fields[3]
						drive['s'] = sz
					if line.find('Brief') != -1:
						sd.write (line)
						fields = line.strip().split(':')
						if fields[0].find('MD5') != -1: ix = 1
						if fields[0].find('SHA1') != -1: ix = 2
						if fields[0].find('256') != -1: ix = 3
						if fields[0].find('512') != -1: ix = 4
						hash_vector[ix] = fields[1].strip()
					else:
						if line.startswith('MD5') or line.startswith('SHA'):
							hix = line.split(':')[0]
							hval = line.split(' ')[1]
							full_hashes[hix] = hval

#				print len(src_drive.nt)," extra hashes"
#				for ixx in src_drive.nt:
#					print '[%s] '%(ixx),src_drive.nt[ixx]
				if fmt != '':fmt_map[d+"+"+fmt] = hash_vector[0][-1]
#				print fmt," format for ",drive['id']
				sd.write(sz+'\n')
#				sd.write(sz+' '+str(hash_vector))
				hash_list.append(hash_vector)
				if len(hash_list) == 0:hash_list.append(('/dev/sdz','N/A','N/A','N/A','N/A'))
				h = hash_list[0]
				if len(h[0]) == 4:
					dx = drive['id']+'+'+h[0][-1]
					obj_type = fmt
				else:
					dx = drive['id']
					obj_type = drive['type']
				dr_tab += ("<tr><td>"+dx)
				dr_tab += ("</td><td>"+obj_type)
				dr_tab += ("</td><td>"+drive['w'])
				if (int(drive['s'])) >= (2**28): big = "*"
				else: big = ''
				if (int(drive['s'])*512)/(1024**3) == 0:
					dr_tab += ("</td><td align=right>"+drive['s']+" ("+str((int(drive['s'])*512)/(1024**2))+'MiB)')
				else:
					dr_tab += ("</td><td align=right>"+drive['s']+" ("+str((int(drive['s'])*512)/(1024**3))+'GiB)'+big)
				dr_tab += ("</td>\n")
				dr_tab += ("<td><tt>%s</tt></td><td><tt>%s</tt></td><td><tt>%s</tt></td><td><tt>%s</tt></td>"%(
					h[1][0:9],h[2][0:9],h[3][0:9],h[4][0:9]))
				dr_tab += ("</tr>\n")
				if len(src_drive.nt) > 0:
					dr_tab += ("<tr>\n")
					dr_tab += ("<td>%s</td>\n"%(dx))
					dr_tab += ("<td>NTFS-FS</td>\n")
					dr_tab += ("<td>%s</td>\n"%(drive['w']))
					if (int(src_drive.nt_size)*512)/(1024**3) == 0:
						dr_tab += ("<td align=right>%s</td>\n"%(src_drive.nt_size+
						' ('+str((int(src_drive.nt_size)*512)/(1024**2))+'MiB)'))
					else:
						dr_tab += ("<td align=right>%s</td>\n"%(src_drive.nt_size+
						' ('+str((int(src_drive.nt_size)*512)/(1024**3))+'GiB)'))
#				#
#					dr_tab += ("<td></td>\n")
#					dr_tab += ("<td></td>\n")
#					dr_tab += ("<td></td>\n")
					dr_tab += ("<td><tt>%s</tt></td>\n"%(
						src_drive.nt['NTFS Brief MD5'][0:9]))
					dr_tab += ("<td><tt>%s</tt></td>\n"%(
						src_drive.nt['NTFS Brief SHA1'][0:9]))
					dr_tab += ("<td><tt>%s</tt></td>\n"%(
						src_drive.nt['NTFS Brief SHA256'][0:9]))
					dr_tab += ("<td><tt>%s</tt></td>\n"%(
						src_drive.nt['NTFS Brief SHA512'][0:9]))
					dr_tab += ("</tr>\n")
				src_drive.md5b = h[1]
				src_drive.sha1b = h[2]
				src_drive.sha256b = h[3]
				src_drive.sha512b = h[4]
				for hix in full_hashes:
					src_drive.h[hix] = full_hashes[hix]
				ref_drives[dx] = src_drive
#				print src_drive.type, " is drive type"
		if not hash_seen:
			obj_type = ''
			sd.write('No Hashes\n')
			dx = drive['id']
			dr_tab += ("<tr><td>"+dx)
			dr_tab += ("</td><td>"+obj_type)
			dr_tab += ("</td><td>"+drive['w'])
			if (int(drive['s'])) >= (2**28): big = "*"
			else: big = ''
			if (int(drive['s'])*512)/(1024**3) == 0:
				dr_tab += ("</td><td align=right>"+drive['s']+" ("+str((int(drive['s'])*512)/(1024**2))+'MiB)')
			else:
				dr_tab += ("</td><td align=right>"+drive['s']+" ("+str((int(drive['s'])*512)/(1024**3))+'GiB)'+big)
				dr_tab += ("</td>\n")
			dr_tab += ("<td>N/A</td><td>N/A</td><td>N/A</td><td>N/A</td>")
			dr_tab += ("</tr>\n")
	sd.write('\n==========================\n')
	xk = ref_drives.keys()
	xk.sort()
	for x in xk:
		sd.write (x+' '+ref_drives[x].type+' @@@@@@@@@\n')
#		sd.write (str(x)+str(str(ref_drives[x]))+'\n')
	sd.write('\n==========================\n')
def check_case (p):
#	needs = {"01":["tool","hash","case"],
#	"02":["tool","hash","case"],
#	"03":["tool","hash","case"],
#	"04":["tool","cmp","hash","case","FASTcmp","wipe"],
#	"05":["tool","case"],
##	"05":["tool","cmp","hash","case","FASTcmp","wipe"],
#	"06":["tool","cmp","hash","case","FASTcmp","wipe"],
#	"07":["tool","cmp","hash","case","FASTcmp","wipe"],
#	"08":["tool","cmp","hash","case","FASTcmp","wipe"],
#	"09":["tool","cmp","hash","case","FASTcmp","wipe"],
#	"10":["tool","case","wipe","mess"],
#	"11":["tool","case","wipe","mess"],
#	"12":["tool","cmp","hash","case","FASTcmp","wipe","mess"],
#	"13":["tool","case"],
#	"14":["tool","case"],
#	"15":["tool","cmp","case","FASTcmp","wipe","mess"]}
	needs = {"01":["tool","case"],
	"02":["tool","wipe","case","cmp","FASTcmp"],
	"03":["tool","case"],
	"04":["tool","case","wipe","cmp","FASTcmp"],
	"05":["tool","case"],
	"06":["tool","case","cmp","FASTcmp","wipe"],
	"07":["case","cmp","FASTcmp","wipe"],
	"08":["case","cmp","FASTcmp","wipe"],
	"09":["case","cmp","FASTcmp","wipe"],
	"10":["tool","case"],
	"11":["tool","case"],
	"12":["tool","case"],
	"13":["tool","case"],
	"14":["tool","case"],
	"15":["tool","cmp","case","FASTcmp","wipe"]}

	old_logs = {
	"case":"case.txt",
	"FASTcmp":"compare.txt",
	"cmp":"compare.txt",
	"tool":"tool-specific-log.txt",
	"mess":"message.txt",
	"hash":"analysis-rehash.txt",
	"wipe":"dst-wipe-log.txt"
	}

	logs = {
	"case":"document_case",
	"FASTcmp":"compare_src_to_dst",
	"cmp":"compare_src_to_dst",
	"tool":"tool_specific_log",
	"mess":"tool_message",
	"hash":"rehash_src",
	"wipe":"initialize_dst"
	}
	
	sd.write ("\nCase dir: %s\nLog files: "%(p))
	flist = os.listdir(p)
	acts = []
	tool_seen = False
	for item in flist:
		sd.write("%s "%(item))
		if item == "case.txt": acts.append("case")
		elif item == "compare.txt":
			acts.append("FASTcmp")
			acts.append("cmp")
		elif item == "analysis-compare.txt":
			acts.append("cmp")
			acts.append("FASTcmp")
		elif item == "dst-wipe-log.txt": acts.append("wipe")
		elif item == "analysis-dst-wipe.txt": acts.append("wipe")
		elif item == "message.txt": acts.append("mess")
		elif item.find("rehash") != -1 : acts.append("hash")
		else:
			if not tool_seen:
				tool_seen = True
				acts.append ("tool")
	sd.write("\nActions: ")
	for action in acts: sd.write("%s "%(action))
	ix = p.lower().find("ft-di-")
	case_no = p[ix+6:ix+8]
	need = set(needs[case_no]).difference(set(acts))
	sd.write("\nRequired actions: ")
	for todo in needs[case_no]: sd.write("%s "%(todo))
	sd.write("\nNot done: ")
	for notdone in needs: sd.write("%s "%(notdone))
	sd.write("\ncase: "+p)
	sd.write("\n\tSTEPS %s: "%(p[ix+6:ix+8]))
	for act in acts:
		sd.write(act)
		sd.write(" ")
	sd.write ('needs: ')
	if len(need) == 0: sd.write("Complete")
	missing_steps = ""
	missing_logs = set()
	for missing in need:
		missing_logs.add(logs[missing])
		sd.write(missing)
	for log_file in missing_logs:
		if missing_steps != "": missing_steps = missing_steps + ', '
		missing_steps = missing_steps + log_file
	sd.write("\n") 
	if len(need) == 0: return "completed"
	return "*Missing: <i>"+missing_steps+"</i>"

def miner (file_name):
	md5 = set()
	sha1 = set()
	sha256 = set()
	f = open(file_name,"r")
	for line in f:
#		print line,
		hex = re.sub('[^0-9a-f]+',' ',line,flags=re.I)
#		print hex
		ck = hex.upper().split(' ')
		for chunk in ck:
			k = len(chunk)
			if k == 32:
				md5.add(chunk)
			elif k == 40:
				sha1.add(chunk)
			elif k == 64:
				sha256.add(chunk)
#		print
	return {'MD5':md5,'SHA1':sha1,'SHA256':sha256}

def extract_tool_hashes(path,case_name):
	dlist = os.listdir(path+'/'+case_name)
	hash_vector = {}
	logs = []
#	print "\nSCAN for HASHES in %s"%(case_name)
	for file in dlist:
		if file.find ('compare') != -1: continue
		if file.find ('rehash') != -1: continue
		if file.find ('analysis-') != -1: continue
		if file == 'case.txt': continue
		if file == 'dst-wipe-log.txt': continue
		if not os.path.isfile(path+'/'+case_name+'/'+file): continue
		logs.append(file)
	log_text = []
	for log_name in logs:
#		print "Look at: ",log_name
		lf = open(path+'/'+case_name+'/'+log_name,"rt")
		log_text.extend(lf.readlines())
	md5_hashes_found = set()
	sha1_hashes_found = set()
	sha256_hashes_found = set()
	sha512_hashes_found = set()
	

	for line in log_text:
		hex = re.sub('[^0-9a-f]+',' ',line,flags=re.I)
		ck = hex.upper().split(' ')
		for chunk in ck:
			k = len(chunk)
			if k == 32:
				md5_hashes_found.add(chunk)
			elif k == 40:
				sha1_hashes_found.add(chunk)
			elif k == 64:
				sha256_hashes_found.add(chunk)
			elif k == 128:
				sha512_hashes_found.add(chunk)
	hash_sets = (
		('MD5',md5_hashes_found),
		('SHA1',sha1_hashes_found),
		('SHA256',sha256_hashes_found),
		('SHA512',sha512_hashes_found)
		)
	sd.write('\nCase: '+case_name+'\n')
	for hs in hash_sets:
		if len(hs[1]) > 0:
			sd.write (hs[0]+': \n')
			for xh in hs[1]:
				sd.write ('\t'+xh+'\n')
#			sd.write (hs[0]+': '+hs[1].pop()+'\n')
#			for xh in hs[1]:
#				sd.write ('\t'+hs[1].pop()+'\n')
#	if len(md5_hashes_found) == 0: hash_vector['MD5'] = 'N/A'
#	elif len(md5_hashes_found) == 1:
#		hash_vector ['MD5'] = md5_hashes_found.pop()
#	else: hash_vector ['MD5'] = 'Multi'
#
#	if len(sha1_hashes_found) == 0: hash_vector['SHA1'] = 'N/A'
#	elif len(sha1_hashes_found) == 1:
#		hash_vector['SHA1'] = sha1_hashes_found.pop()
#	else: hash_vector['SHA1'] = 'Multi'
#
#	if len(sha256_hashes_found) == 0: hash_vector['SHA256'] = 'N/A'
#	elif len(sha256_hashes_found) == 1:
#		hash_vector['SHA256'] = sha256_hashes_found.pop()
#	else: hash_vector['SHA256'] = 'Multi'
#
#	if len(sha512_hashes_found) == 0: hash_vector['SHA512'] = 'N/A'
#	elif len(sha512_hashes_found) == 1:
#		hash_vector['SHA512'] = sha512_hashes_found.pop()
#	else: hash_vector['SHA512'] = 'Multi'
	hash_vector['MD5'] = md5_hashes_found
	hash_vector['SHA1'] = sha1_hashes_found
	hash_vector['SHA256'] = sha256_hashes_found
	hash_vector['SHA512'] = sha512_hashes_found
	return hash_vector
############################################################

def old_extract_tool_hashes(path,case_name):
	dlist = os.listdir(path+'/'+case_name)
	hash_vector = {}
	logs = []
#	print "\nSCAN for HASHES in %s"%(case_name)
	for file in dlist:
		if file.find ('compare') != -1: continue
		if file.find ('rehash') != -1: continue
		if file.find ('analysis-') != -1: continue
		if file == 'case.txt': continue
		if file == 'dst-wipe-log.txt': continue
		if not os.path.isfile(path+'/'+case_name+'/'+file): continue
		logs.append(file)
	log_text = []
	for log_name in logs:
#		print "Look at: ",log_name
		lf = open(path+'/'+case_name+'/'+log_name,"rt")
		log_text.extend(lf.readlines())
	md5_hashes_found = set()
	sha1_hashes_found = set()
	sha256_hashes_found = set()
	sha512_hashes_found = set()
	ln = 0
	i = 0
	tt = ""
	while i < 256:
	        if chr(i) in 'ABCDEFabcdef0123456789SHMshm':
	                tt += chr(i)
	        else: tt += ' '
	        i += 1

	for line in log_text:
		ln += 1
#		line = line.translate(tt)
		if line.upper().find('SHA') != -1:
#			print "SHA@(%s): "%(ln),line
			line = line.translate(tt)
			fields = line.strip().split(' ')
			for item in fields:
				if len(item) == 40:
					sha1_hashes_found.add(item.upper())
				elif len(item) == 64:
					sha256_hashes_found.add(item.upper())
				elif len(item) == 128:
					sha512_hashes_found.add(item.upper())
		if line.upper().find('MD5') != -1:
#			print "MD5@(%s): "%(ln),line
			line = line.translate(tt)
#			print "MD5@(%s) translated: "%(ln),line
			fields = line.strip().split(' ')
#			print "\t\t",fields
			for item in fields:
				if len(item) == 32:
					md5_hashes_found.add(item.upper())
#	print "LAST Line:",ln
	hash_sets = (
		('MD5',md5_hashes_found),
		('SHA1',sha1_hashes_found),
		('SHA256',sha256_hashes_found),
		('SHA512',sha512_hashes_found)
		)
	sd.write('\nCase: '+case_name+'\n')
	for hs in hash_sets:
		if len(hs[1]) > 0:
			sd.write (hs[0]+': \n')
			for xh in hs[1]:
				sd.write ('\t'+xh+'\n')
#			sd.write (hs[0]+': '+hs[1].pop()+'\n')
#			for xh in hs[1]:
#				sd.write ('\t'+hs[1].pop()+'\n')
	if len(md5_hashes_found) == 0: hash_vector['MD5'] = 'N/A'
	elif len(md5_hashes_found) == 1:
		hash_vector ['MD5'] = md5_hashes_found.pop()
	else: hash_vector ['MD5'] = 'Multi'

	if len(sha1_hashes_found) == 0: hash_vector['SHA1'] = 'N/A'
	elif len(sha1_hashes_found) == 1:
		hash_vector['SHA1'] = sha1_hashes_found.pop()
	else: hash_vector['SHA1'] = 'Multi'

	if len(sha256_hashes_found) == 0: hash_vector['SHA256'] = 'N/A'
	elif len(sha256_hashes_found) == 1:
		hash_vector['SHA256'] = sha256_hashes_found.pop()
	else: hash_vector['SHA256'] = 'Multi'

	if len(sha512_hashes_found) == 0: hash_vector['SHA512'] = 'N/A'
	elif len(sha512_hashes_found) == 1:
		hash_vector['SHA512'] = sha512_hashes_found.pop()
	else: hash_vector['SHA512'] = 'Multi'
	return hash_vector
############################################################
def extract_compare(path):
#	print '============== ',path
#
# the compare file will be in one of two possible formats: default or alternate
#
	cmp_file_name = path+'/'+'compare.txt'
#	sd.write("Try: "+cmp_file_name+'\n')
	try:
		cmp_file = open(cmp_file_name,"rt")
	except IOError as e:
		cmp_file_name = path+'/'+'analysis-compare.txt'
		try:
			cmp_file = open(cmp_file_name,"rt")
		except IOError as e:
			return (0,-2)

#	sd.write("Opened: "+cmp_file_name+'\n')
	cmp_text = cmp_file.readlines()
	cmp_file.close()
	n_cmp = 0
	n_differ = -1
	run_text = ''
	fill_text = ''
	n_runs = 0
	src_fill = ''
	dst_fill = ''
	join_up = ''
	grab_more = False
	buffer = ''
	for line in cmp_text:
		vcheck(line)
#		if buffer != '': print 'LOOK: ',line,len(line.rstrip()),line.rstrip().rfind(',')
#	combine lines that end in a comma, i.e., lists of non-matching sectors
# 	(default format)
		if len(line) > 3:
			if line.rstrip()[-1] == ',':
				buffer += line
#				print "LONG LINE",buffer
				continue
			elif buffer != '':
				buffer += line
				line = buffer
				buffer = ''
#		print "#####: ",line.strip()
#		if len(line.strip()) > 2:
#			if line.strip()[-1] == ',':
#				print "Continue: ",line
#				join_up += line
#				continue
#		else:
#			line = join_up + line
#			join_up = ''
		if line.find('Excess Source') != -1: break
		if line.find('Sectors Compared:') != -1: n_cmp = int(line.split(':')[1])
		if line.find('Sectors Differ:') != -1: n_differ = int(line.split(':')[1])
		if line.find('Sectors compared:') != -1: n_cmp = int(line.split(':')[1])
		if line.find('Sectors differ:') != -1: n_differ = int(line.split(':')[1])
		if line.find('Dst Byte fill') == 0:
			dst_fill = line.split('(')[1][0:2]
#			print '*************** dst: ',dst_fill
		if line.find('Shifted Src Byte fill') == 0:
			src_fill = line.split('(')[1][0:2]
#     xxxx	          Shifted Src Byte fill
#			print '*************** src: ',src_fill
#		print "?????",line.find('Dst Byte fill'),line.find('Shifted Src Byte fill')

# process alt format lists of non-matching sectors
#
		if line.find('Run: ') == 0:
			if n_runs == 0:
				run_text += "<br>Runs of differences:\n"
			if n_runs <= 21:	
				run_text += '<br>'+line[5:]
			elif n_runs == 22:
				run_text += '<br>Plus more . . .'
			n_runs += 1
		if line.find('fill range:') > 0 or line.find('filled range:') > 0 or grab_more:
#		if line.find('fill range:') > 0 or line.find('fill range:') > 0:
#			run_text += "<br>"+line
#			print "LINE: ",line.strip()
#			print "FILL RANGE",line.strip().split(':')
			ex_runs = line.strip().split(':')[1]
			ss = line.split(' ')[0:2]
			fill_type = ' '+ss[0]+' '+ss[1]
#			print "Fill type: %s [%d]"%(fill_type,n_runs)
#			print "EX_RUNS: ",ex_runs
			if ex_runs == '': continue


# process default format non-matching sector lists


			run_list = ex_runs.split(',')
#			print "========== Found %d runs"%(len(run_list))
			if n_runs == 0:
				run_text += "<br>Runs of differences:\n"
			for run_range in run_list:
				if n_runs <= 21 and len(run_range) > 2:	
					zrange = run_range.split('-')
					if len(zrange) == 2:
						delta = 1 + int(zrange[1]) - int(zrange[0])
					else: delta = 1
					fill_type = src_fill
					if line.startswith('Zero'): fill_type = 'Zero fill'
					elif line.startswith('Shifted'): fill_type = 'Shifted src fill '+src_fill
					elif line.startswith('Dst'): fill_type = 'Destination fill '+dst_fill
#					print 'ADD <br>%s (%d) %s'%(run_range,delta,fill_type)
					run_text += '<br>%s (%d) %s'%(run_range,delta,fill_type)
				elif n_runs == 22:
					run_text += '<br>Plus more . . .'
				n_runs += 1

	return (n_cmp,n_differ,run_text)
def extract_src(path,case_name):
	case_file = open (path+'/'+case_name+'/case.txt')
	case_text = case_file.readlines()
	case_file.close()
	src_drive = 'Not found'
	this_case = case_desc()
	this_case.blocker = ''
	for line in case_text:
		vcheck(line)
		if line.find('Start time:') != -1:
			this_case.date = line.strip()[11:]
		if line.find('Host:') != -1:
			this_case.host = line.strip().split(':')[1]
		if line.find('Case:') != -1:
			this_case.case = line.strip().split(':')[1]
		if line.find('Operator:') != -1:
			this_case.user = line.strip().split(':')[1]
		if line.find('write blocker:') != -1:
			this_case.blocker = line.strip().split(':')[1].strip()
		if line.find('image:') != -1:
			this_case.image = line.strip().split(':')[1]
		if line.find('dst:') != -1:
			this_case.dst = line.strip().split(':')[1]
		if line.find('src:') != -1:
			src_drive = line.strip().split(':')[1]
			this_case.src = src_drive
	case_admin [case_name] = this_case
	return src_drive.strip()
def process_message (path,case_list):
	html.write('<tr><th>Case</th><th>Message</th></tr>\n')
	alist = {}
	for case_name in case_list:
		src = extract_src(path,case_name)
		try:
			case_file = open (path+'/'+case_name+'/message.txt')
			OK = True
			message = case_file.readline().strip()
			case_file.close()
			html.write('<tr><td>%s</td><td>%s</td></tr>\n'%
			(case_name,message))
		except IOError as e:
			alist [case_name] = '''The tested tool did not issue
				a message indicating insufficent space'''
			OK = False
			html.write('<tr><td>%s</td><td bgcolor=pink>%s</td></tr>\n'%
			(case_name,'No message.txt file present'))

	return alist
def process_clone (path,case_list):
	html.write('''<tr><th>Case</th><th>Src</th>''')
	if test_class in ['01','03','07','08']:
		html.write('<th>Blocker (interface)</th>\n')
	html.write('''<th>Compared</th> <th>Differ</th></tr>\n''')
	alist = {}
	for case_name in case_list:
		src = extract_src(path,case_name)
		case_file = open (path+'/'+case_name+'/case.txt')
		case_text = case_file.readlines()
		case_file.close()
		src_drive = 'Not found'
		html.write('<tr><td>'+case_name+'</td>')
		for line in case_text:
			vcheck(line)
			if line.find('src:') != -1:
				src_drive = line.strip().split(':')[1]
		html.write('<td>'+src_drive+'</td>')
		cmp_vec = extract_compare(path+'/'+case_name)
		n_cmp = str(cmp_vec[0])
		n_diff = str(cmp_vec[1])
		sd.write ("Case: %s %s sectors compared, %s sectors differ\n"%(
			case_name,n_cmp,n_diff))
		if cmp_vec[0] == 0:
			if cmp_vec[1] == -2: alist[case_name] = "Compare file missing"
			if cmp_vec[1] == -1: alist[case_name] = "Compare file incomplete"
			n_diff = "error"
		else:
			if cmp_vec[1] != 0:
				alist[case_name] = "%d sectors of the clone are different from the original"%(
				cmp_vec[1])+cmp_vec[2]
				if test_class in ['01','03','07','08']:
					html.write('<td>'+case_admin[case_name].blocker+'</td>')
				html.write ('<td align=right>'+n_cmp+'''</td><td align=right bgcolor=pink
					>'''+n_diff+'</td>')
			else:
				if test_class in ['01','03','07','08']:
					html.write('<td>'+case_admin[case_name].blocker+'</td>')
				html.write ('<td align=right>'+n_cmp+'</td><td align=right>'+n_diff+'</td>')
		html.write('</tr>')
	return alist

#####################################################################
#                 Process cases with an image file                  #
#####################################################################

def process_image_match (path,case_list,ah,test_class):
	hash_keys = ah.keys()
	hash_keys.sort()
	html.write('<tr><th rowspan="2">Case</th><th rowspan="2">Src</th>')
	if test_class in ['01','03','07','08']:
		html.write('<th rowspan=2>Blocker (interface)</th>\n')
	head_hash = 'colspan="%d"> Reference Hash vs Tool Hash'%(len(hash_keys))
	html.write('<th '+head_hash+'</th></tr>')
#	html.write('<tr><th>Case</th><th>Src</th>')
	html.write('<tr>')
	if len(hash_keys) == 0:
		html.write('<th>Ref MD5</th><th>Tool MD5</th>')
		html.write('<th>Ref SHA1</th><th>Tool SHA1</th></tr>\n')
	else:
		for hkey in hash_keys:
			html.write('<th>%s</th>'%(hkey))
		html.write('</tr>\n')


# loop over list of cases
	for case_name in case_list:
		src = extract_src(path,case_name)
#		print "%-16s %s "%(case_name,src),
#		print case_admin[case_name].blocker
		if case_name[6:8] in ('05','06','09'):
#			src = src + '+' + fmt_map[src+"+"+case_name[9:]]
			src = src + '+' + fmt_map[src+"+"+case_name[9:].lower()]
		hash_list = extract_tool_hashes(path,case_name)
		ok = True
		if src == 'Not found':
			alist[case_name] = 'No drive found in case.txt'
			ok = False
		else:
			if src not in ref_drives:
				ok = False
				alist[case_name] = 'No setup found for drive %s'%(src)
		if ok:
			html.write('<tr><td>%s</td><td>%s</td>'%(case_name,src))
#			print "Checking %s %s"%(case_name,src)
			if test_class in ['01','03','07','08']:
				html.write('<td>'+case_admin[case_name].blocker+'</td>')
			for hkey in hash_keys:
				if ref_drives[src].h[hkey].strip() in hash_list[hkey]:
					html.write('<td>%s</td>'%("match"))
				elif len(ref_drives[src].nt) > 0:
					if ref_drives[src].nt['NTFS '+hkey].strip() in hash_list[hkey]:
						html.write('<td>%s</td>'%("match"))
					else:
						html.write('<td bgcolor="pink"><b>%s</b></td>'%("differ"))
						alist[case_name] = '''<b>Tool %s does
							not match reference hash</b>'''%(hkey)
						alist[case_name+' '+hkey+' ref'] = ref_drives[src].h[hkey]
						alist[case_name+' '+hkey+' ref-FS'] = ref_drives[src].nt['NTFS '+hkey]
						if len(hash_list[hkey]) == 1:
							alist[case_name+' '+hkey+' tool'] = hash_list[hkey].pop()
						else:
							alist[case_name+' '+hkey+' tool'] = "%d hashes found, none match"%(len(hash_list[hkey]))
				else:
					html.write('<td bgcolor=pink><b>%s</b></td>'%("differ"))
#					alist[case_name+ ' '+hkey] = '''<em class=p>Tool %s does not match
					alist[case_name+ ' '+hkey] = '''<b>Tool %s does not match
						reference hash</b>'''%(hkey)
					alist[case_name+' '+hkey+' ref'] = ref_drives[src].h[hkey]
#					alist[case_name+' '+hkey+' tool'] = hash_list[hkey]
					if len(hash_list[hkey]) == 1:
						alist[case_name+' '+hkey+' tool'] = hash_list[hkey].pop()
					else:
						alist[case_name+' '+hkey+' tool'] = "%d hashes found, none match"%(len(hash_list[hkey]))
			html.write('</tr>\n')
		
	return alist
#######################################################

# process_image appears to be unused
def process_image (path,case_list,ah):
	hash_keys = ah.keys()
	hash_keys.sort()
	html.write('<tr><th>Case</th><th>Src</th>')
	if len(hash_keys) == 0:
		html.write('<th>Ref MD5</th><th>Tool MD5</th>')
		html.write('<th>Ref SHA1</th><th>Tool SHA1</th></tr>\n')
	else:
		for hkey in hash_keys:
			html.write('<th>Ref %s</th><th>Tool %s</th>'%(hkey,hkey))
		html.write('</tr>\n')
	for case_name in case_list:
		src = extract_src(path,case_name)
		if case_name[6:8] in ('05','06','09'):
#			src = src + '+' + fmt_map[src+"+"+case_name[9:]]
			src = src + '+' + fmt_map[src+"+"+case_name[9:].lower()]
		hash_list = extract_tool_hashes(path,case_name)
		ok = True
		if src == 'Not found':
			alist[case_name] = 'No drive found in case.txt'
			ok = False
		else:
			if src not in ref_drives:
				ok = False
				alist[case_name] = 'No setup found for drive %s'%(src)
		if ok:
			html.write('<tr><td>%s</td><td>%s</td>'%(case_name,src))
			for hkey in hash_keys:
				if ref_drives[src].h[hkey].strip() != hash_list[hkey].strip():
					alist[case_name] = '<b>Tool %s does not match reference hash</b>'%(hkey)
					alist[case_name+' '+hkey+' ref'] = ref_drives[src].h[hkey]
					alist[case_name+' '+hkey+' tool'] = hash_list[hkey]
				html.write('<td><tt>%s</tt></td><td><tt>%s</tt></td>'%(
					ref_drives[src].h[hkey][0:5],hash_list[hkey][0:5]))
			html.write('</tr>\n')
		
	return alist
#######################################################
#main
#########################################################
#########################################################
########### S t a r t    H e r e ########################
#########################################################
#########################################################

case_list = ('01', '02', '03', '04', '05', '06',
	'07', '08', '09', '10', '11', '12',
	'13', '14', '15')

conf = "test-configuration.txt"
sd.write("START\n")


if full_report: html.write(head+'\n')
#html.write("<br>"+'\n')
#html.write("call %s with %d args:"%(sys.argv[0],len(sys.argv)-1))
#for arg in sys.argv[1:]:
#	html.write('  '+arg)
#html.write('\n')

# check if configuration exists
if len(sys.argv) == 1:
	html.write("<h1>NO LOG Path given</h1>\n")
	sd.write("<h1>NO LOG Path given</h1>\n")
	shut_down(False)
else:
#	html.write("<h1>LOG Path: %s</h1>\n"%(sys.argv[2]))
	conf = sys.argv[1]
	path = sys.argv[2]
	sd.write("Path: %s\n"%(path))
	if not os.access(path,os.F_OK):
		html.write("<h1>LOG Path: %s does not exist</h1>\n"%(path))
		sd.write("<h1>LOG Path: %s does not exist</h1>\n"%(path))
		shut_down(False)
if not os.access(path+"/"+conf,os.F_OK):
	sd.write("There is no %s in %s\n"%(conf,path))
	html.write("<h1>There is no %s in %s</h1>\n"%(conf,path))
	shut_down(False)
cf = open (path+"/"+conf,"rt")
tn = cf.readline().strip()
tx = tn.find(' ')
tv = cf.readline().strip()
vx = tv.find(' ')
blocker_list = scan_blockers(path)
blocker_txt = blocker_list[0]
blocker_set = blocker_list[1]
tool_name = tn[tx:]+ " Version: " + tv[vx:]
if full_report:
	html.write('''<h1>Test Results for Disk Imaging Tool:<br>
		%s</h1>\n'''%(tool_name))
	html.write('''<b><br>Tests were Configured for the Following
		Write Block Scenarios:<br>'''+ blocker_txt+"<br></b>")
else:
	html.write('''<h1>Test Status Report for Disk Imaging 
		with %s</h1>\n'''%(tool_name))

def print_blocker_tab(html,wb_table_head,table_tail):
	html.write(wb_table_head)
#	html.write ('<tr><th colspan="2">Write Blockers Used </th></tr>\n')
	html.write ('<tr><th>Blocker Model</th><th>Firmware Version</th></tr>\n')
#	for b in blocker_set:
#		print "======== blocker ==========",b
	for b in blocker_set:
		if len(b.split('?')) != 2:
			html.write('''<tr><td>%s</td><td>
				<em class=x>insert firmware version</em>
				</td></tr>\n'''%(b))
		else: html.write('''<tr><td>%s</td><td>%s</em>
				</td></tr>\n'''%(b.split('?')[0],b.split('?')[1]))
	html.write(table_tail+'\n')

if full_report:
	html.write(report_header_text_part_1%(tn[tx:],tv[vx:])+'\n')
#	print_blocker_tab(html,wb_table_head,table_tail)
	html.write(report_header_text_part_2+'\n')
	print_blocker_tab(html,wb_table_head,table_tail)
#	html.write(wb_table_head)
#	html.write ('<tr><th>Blocker Model</th><th>Firmware Version</th></tr>\n')
#	for b in blocker_set:
#		html.write('''<tr><td>%s</td><td><em class=x>insert firmware version</em>
#			</td></tr>\n'''%(b))
#	html.write(table_tail+'\n')
#	html.write(main_section+'\n')
	html.write("<br>"+'\n')
# ##############   Check for drive setup #################
dr_tab_desc = '''
<p>The following table presents the state of each source object, drive or partition, including
reference hashes and known content.<br><p>
Both drives and partitions are described in the table. Partitions are indicated
in the <i>Drive</i> column by the notation <b>[drive]+[partition number]</b>.
Where <b>[drive]</b> is the drive label and <b>[partition number]</b> is the
partition number. For example, the first partition on drive A3 would be A3+1.
The type column records either the drive type, e.g. sata, usb, etc., or the
partition type, e.g., ntfs, fat32, etc., depending on whether a drive or a partition 
is being described.
'''
if os.access(path+"/"+"setup",os.F_OK):
	dr_tab += ("<h2>Test drives and Partitions</h2>\n")
	dr_tab += dr_tab_desc+'\n'
#	dr_tab += ('<br>NEED to explain partition notation<br>\n')
	dr_tab += (table_head+'\n')
	dr_tab += ("<caption><b>Test Drives</b></caption>"+'\n')
	dr_tab += ("<tr><th>Drive</th><th>Type</th><th>Content</th><th>Sectors</th>")
	dr_tab += ("<th>MD5</th><th>SHA1</th><th>SHA256</th><th>SHA512</th></tr>\n")
	scan_drives (path)
#	html.write (dr_tab)
	dr_tab += (table_tail+'\n')
	dr_tab += ('<br>* Large 48-bit address drive<br>\n')
#	dr_tab += ('<br>NEED to ADD drive type/partition type column<br>\n')
else:
	dr_tab += ('<h1>No test drives have been setup in %s</h1>\n'%(path))
# ##############   Check test case status ################
selected_explain = '''
<p>This table presents a brief description of each test case that was performed.
<br><br><em class=x>The <i>Status</i> column indicates any required steps
that need to be executed.<br></em>
<em class=x>You may either remove the status column from the final report or
keep it in the final report.</em><br><br>
'''
html.write("<h2>Selected Test Cases</h2>\n")
html.write(selected_explain+'\n')
html.write(table_head+'\n')
html.write("<caption><b>Test Case Status</b></caption>\n")
html.write("<tr><th>Case</th><th>Description</th><th>Status</th></tr>\n")
output_text = "tests-to-run"
tests_completed = "completed"
tests_not_started = "tests-to-run"
tests_in_progress = "***XXX***partially-completed"
sep = ":"
case_list = []
case_groups = {}
active_hash = {}
for line in cf:
	if line[0] == '#': continue
	if line[0:5] == "hash ": 
		hlist = line.strip().split(' ')
		for code in hlist[1:]:
			if code == 'h_sha128': continue
			active_hash[code[2:].upper()] = True
	if line[0:2] == "FT":
		target = path+"/"+line.split(" ")[0].rstrip()
		html.write("<tr><td>\n")
		test_case_id = line.rstrip("\n\r")
		test_class = test_case_id.split('-')[2]
#		print '===========SET TEST CLASS =',test_class,target
		html.write(test_case_id)
#		html.write("<td>"+target+"</td>\n")
#case_dict = {
		dix = test_case_id[:8]
		html.write("<td>"+case_dict[dix]+"</td>\n")
		output_text = output_text + sep + test_case_id
		if os.path.exists(target):
			check_out = check_case (target)
			output_text = output_text + sep + check_out
			html.write("</td><td>"+check_out+"</td>\n")
			if check_out[0] == 'c':
				tests_completed = tests_completed + sep + test_case_id
				case_list.append(test_case_id)
				if test_class not in case_groups: case_groups[test_class] = []
				case_groups[test_class].append(test_case_id)
			else:
				tests_in_progress = tests_in_progress + sep + test_case_id
				tests_in_progress = tests_in_progress + check_out[check_out.find(':'):]
		else:
			html.write("</td><td>Pending</td>\n")
			tests_not_started = tests_not_started + sep + test_case_id
		html.write("</tr>\n")
html.write(table_tail+'\n')

os_info = os.uname()
foot_note = '''
<em class=x><p>*Partially completed test cases have something missing.</em>
'''
path_foot_note = '''
See <a href="./runtests.php?path=/%(path)s#if-something-is-missing">
runtests.php?#if-something-is-missing</a>.</p>\n
<br>
'''
linux_foot_note = '''
<p>*Partially completed test cases have something missing.
See <a href="./runtests.php?path=/media/cftt/FT-LOGS#if-something-is-missing">
runtests.php?#if-something-is-missing</a>.</p>\n
************ take out link for full_report *************
<br>
'''
#html.write(foot_note+'\n')
if not full_report:
	html.write(foot_note+'\n')
	if os_info[0] == 'Darwin':
		html.write(path_foot_note%{'path':'tmp'}+'\n')
	else:
		html.write(path_foot_note%{'path':'media/cftt/FT-LOGS'}+'\n')

case_01_note_old = '''
<p>
This test can be repeated to test acquisition of multiple drive types. The drive
type tested is included in the test case name. 
<p>
Note that in addition to testing the ability of
the tool to access data from a given drive type, the ability to access data
over the interface between
the PC and the write blocker is also tested.
<p>
For example, if a FireWire drive is attached to a write blocker
that is attached to the test PC with a USB interface, two things are tested:
<ol>
<li>the test case name would be FT-DI-01-FW,
<li>test ability of the tool to access the USB interface, and
<li>test ability of the tool to acquire a FireWire drive through
a write blocker.
</ol>
<br><br>
<hr>
'''
case_01_note = '''
<p>
This test can be repeated to test acquisition of multiple drive types.
This test tests the ability of the tool to acquire a specific type of drive 
(the drive type tested is included in the test case name) to an image file
using a specific write blocker
(applies only to tools that are used with
hardware write blockers) and a certain interface connection
between the test computer and the write blocker. The write blocker
used and the interface connection between the test computer and the write blocker
are listed for each test case in the table below.
Two tests are required to test ATA or SATA drives, one to test drives smaller than 138GB
(ATA28 & SATA28: 28-bit addressing) and one to test larger drives
(ATA48 & SATA48: 48-bit addressing).
<br><br>
'''
case_03_note = '''
<p>
This test can be repeated to test acquisition of multiple removable media types.
This test tests the ability of the tool to acquire a specific type of removable media
(the removable media type tested is included in the test case name) to an image file
using a specific media reader which may also be a write blocker
and a certain interface connection
between the test computer and the media reader. The media reader
used and the interface connection between the test computer and the media reader
are listed for each test case in the table below.
<br><br>
'''
case_07_note = '''
<p>
This test can be repeated to test acquisition of multiple drive types.
This test tests the ability of the tool to clone a specific type of drive 
(the drive type tested is included in the test case name)
using a specific write blocker
(applies only to tools that are used with
hardware write blockers) and a certain interface connection
between the test computer and the write blocker. The write blocker
used and the interface connection between the test computer and the write blocker
are listed for each test case in the table below.
Two tests are required to test ATA or SATA drives, one to test drives smaller than 138GB
(ATA28 & SATA28: 28-bit addressing) and one to test larger drives
(ATA48 & SATA48: 48-bit addressing).
<br><br>
'''
case_08_note = '''
<p>
This test can be repeated to test acquisition of different removable media types.
This test tests the ability of the tool to clone a specific type of removable media
(the removable media type tested is included in the test case name)
using a specific media reader which may also be a write blocker
and a certain interface connection
between the test computer and the media reader. The media reader
used and the interface connection between the test computer and the media reader
are listed for each test case in the table below.
<br><br>
'''
#using a specific media reader or write blocker
#between the test computer and the write blocker. The write blocker
#used and the interface connection between the test computer and the write blocker
#are listed for each test case in the table below.

anomaly_insert = '''
<h3>Case Summary</h3>
<em class=x>
<br>Pick one of the following:<br>
</em>
Results are as expected.<br>
Results are not as expected.
<br>
<em class=x>INSERT ANY DISCUSSION, COMMENTS, OBSERVATIONS HERE.</em>
<br>
'''

html.write(main_section+'\n')
xxx = case_groups.keys()
xxx.sort()
for test_class in xxx:
	alist = {}
	html.write('<h2>FT-DI-'+test_class+'</h2><p>')
	html.write('<h3>Test Case Description</h3><p>')
#	html.write('<br>******************<br>')
	html.write(case_dict['FT-DI-'+test_class]+'<p>')
#	html.write('<br>******************<br>')
	if test_class == '01':
		html.write(case_01_note)
	if test_class == '03':
		html.write(case_03_note)
	if test_class == '07':
		html.write(case_07_note)
	if test_class == '08':
		html.write(case_08_note)
	html.write('<h3>Test Evaluation Criteria</h3><p>')
#	html.write('<br>******************<br>')
	html.write(eval_map['FT-DI-'+test_class]+'<p>')
	if test_class == '15':
#		print '======= List bad sectors here ========='
		html.write(table_head)
		html.write("<caption><b>Bad Sector Drives for FT-DI-%s cases</b></caption>\n"%(test_class))
		html.write('<tr><th>Case</th><th>Drive</th><th>Bad Sectors</th></tr>\n')
		for case_name in case_groups[test_class]:
			bad_src = extract_src(path,case_name)
			html.write('<tr><td>%s</td><td>%s</td><td>'%(case_name,bad_src))
#			print case_name,extract_src(path,case_name)
			if os.access(path+"/setup/"+bad_src+"/bad-log.txt",os.F_OK):
				bad_f = open(path+"/setup/"+bad_src+"/bad-log.txt")
				for bad_line in bad_f:
					bad_stuff = bad_line.strip('\n').split(' ')[0]
					if bad_stuff.isdigit():
#						print bad_stuff,
						html.write(' %s'%(bad_stuff))
#				print
				html.write('</td></tr>\n')
		html.write(table_tail)
#	html.write('<br>******************<br>')
	html.write('<h3>Test Case Results</h3><p>')
	html.write('''The following table presents results for individual test cases''')
	html.write(table_head)
	html.write("<caption><b>Test Results for FT-DI-%s cases</b></caption>\n"%(test_class))
	if test_class in ('07','08','09'):alist = process_clone(path,case_groups[test_class])
	elif test_class in ('02','04','06','15'):alist = process_clone(path,case_groups[test_class])
	elif test_class in ('01','03','05','13','14'):
		alist = process_image_match(path,case_groups[test_class],active_hash,test_class)
	elif test_class in ('10','11','12'):alist = process_message(path,case_groups[test_class])
	else:
		for case_name in case_groups[test_class]:
			html.write('<tr><td>'+case_name+'</td>')
			html.write('</tr>')
	html.write(table_tail)
	if len(alist) > 0:
#		html.write('<h4>FT-DI-'+test_class+' Anomalies</h2><p>')
		html.write('''<h4>Anomalies</h2><p>The following table lists any observed
			anomalies and provides additional details.''')
		html.write(table_head)
		html.write("<caption><b>Test Anomalies for FT-DI-%s cases</b></caption>\n"%(test_class))
		html.write('<tr><th>Case</th><th>Anomaly</th></tr>\n')
		alist_keys = alist.keys()
		alist_keys.sort()
		for anomaly in alist_keys:
			html.write('<tr><td>'+anomaly+'</td><td>'+alist[anomaly]+'</td></tr>\n')

		html.write(table_tail)
	if full_report:
		html.write(anomaly_insert)

html.write('<h1>Appendix: Additional Details</h1><p>')
html.write (dr_tab)
html.write('<h2>Test Case Admin Details</h2><p>')
html.write('For each test run, the test computer,\n')
html.write(' the tester,')
html.write(' the source drive,')
html.write(' the image file drive,')
html.write(' the destination drive,')
html.write(' and the date the test was run are listed.<br><br><p>\n')
html.write(table_head)
html.write("<caption><b>Test Case Admin Details</b></caption>"+'\n')
html.write('<tr><th>Case</th>\n')
html.write('<th>User</th><th>Host</th>\n')
html.write('<th>Blocker (PC interface)</th>\n')
html.write('<th>Src</th><th>Image</th>\n')
html.write('<th>Dst</th><th>Date</th>\n')
html.write('</tr>\n')
y = case_admin.keys()
y.sort()
for x in y:
	line = ""
	line +=case_admin[x].case
	line +='</TD><TD>'
	line +=case_admin[x].user
	line +=' '
	line +='</td><td>'
	line +=case_admin[x].host
	line +=' '
	line +='</td><td>'
	line +=case_admin[x].blocker
	line +=' '
	line +='</td><td><tt>'
	line +=case_admin[x].src
	line +=' '
	line +='</td><td><tt>'
	if len(case_admin[x].image) == 0: line += '--'
	else: line +=case_admin[x].image
	line +=' '
	line +='</td><td><tt>'
	if len(case_admin[x].dst) == 0: line += '--'
	else: line +=case_admin[x].dst
	line +=' '
	line +='</td><td>'
	line += case_admin[x].date
	line +='</td></tr>'
	html.write("<tr><td>%s\n"%(line))
html.write(table_tail)
html.write('<h2>Test Setup & Analysis Tool Versions</h2><p>')
html.write('Version numbers of tools used are listed.<br><br>\n')
html.write(table_head)
html.write("<caption><b>Setup & Analysis Tool Versions</b></caption>"+'\n')
#html.write('<tr><th>Version</th></tr>\n')
slist = []
for v in vlist:
	slist.append(v)
slist.sort()
for v in slist:
	html.write('<tr><td>%s</td></tr>\n'%(v))
html.write(table_tail)
shut_down(True)
