#!/usr/bin/env python
import base64
import hashlib
import sys
import os.path, os
import subprocess
import shutil
import email, mimetypes, errno
from optparse import OptionParser
import zipfile

class pcapReport(object):
    def __init__(self, pcapfile, pathofreport="./report"):
        assert pcapfile
        if not os.path.exists(pcapfile):
            raise TypeError("Pcap file is not at path specified")
        self.reportRoot = pathofreport
        if not os.path.exists(self.reportRoot):
            os.makedirs(self.reportRoot)

        if not os.path.exists(os.path.join(self.reportRoot, "flows")):
            os.makedirs(os.path.join(self.reportRoot, "flows"))
        shutil.copyfile(pcapfile, os.path.join(self.reportRoot, "flows", "raw.pcap"))

        retcode = subprocess.call("(cd %s && tcpflow -r %s)"%(os.path.join(self.reportRoot, "flows"), "raw.pcap"), shell=True)
        self.toProcess = {}
        self.streamcounter = 0
        self.txt_report = ""
        for i in os.listdir(os.path.join(self.reportRoot, "flows")):
            if "pcap" not in i:
                x = os.path.join(self.reportRoot, "flows", i)
                if os.path.isfile(x):
                    self.toProcess[i] = {}
                    self.toProcess[i]['raw'] = file(x).read().split("\r\n")

    def get_names(self):
        names = []
        classes = [self.__class__]
        while classes:
            aclass = classes.pop(0)
            if aclass.__bases__:
                 classes = classes + list(aclass.__bases__)
                 names = names + dir(aclass)
            return names

    def log(self, x):
        self.txt_report += x + "\n"

    def run(self):
        for aFile in self.toProcess.keys():
            self.processFile(aFile)
            self.reportFile(aFile)
        
        print self.txt_report
        print "-"*40
        print "Writing complete report to: %s"%(os.path.join(self.reportRoot, "output-report.txt"))
        f = open(os.path.join(self.reportRoot, "output-report.txt"), "w")
        f.write(self.txt_report)
        f.close()
        print "MD5 Hash of report: %s"%(hashlib.md5(self.txt_report).hexdigest())
        print "Finished"

    def processFile(self, aFile): 
        decoders = {}
        for i in self.get_names():
            try:
                holder,name = i.split("_", 1)
                if holder == "decode":
                    if decoders.has_key(name):
                        decoders[name]['decoder'] = getattr(self,i)
                    else: 
                        decoders[name] = {'decoder':getattr(self,i), 'points':0}
                elif holder == "process":
                    if decoders.has_key(name):
                        decoders[name]['processer'] = getattr(self,i)
                    else: 
                        decoders[name] = {'processer':getattr(self,i), 'points':0}
            except:
                pass
        for i in self.toProcess[aFile]['raw']:
            for name in decoders.keys():
                decoders[name]['points'] = decoders[name]['decoder'](aFile)
        
        protocolSelected = ""
        for name in decoders.keys():
            if not protocolSelected:
                protocolSelected = name
            elif decoders[name]['points'] > decoders[protocolSelected]['points']:
                protocolSelected = name

        self.toProcess[aFile]['ptype'] = protocolSelected
        return decoders[protocolSelected]['processer'](aFile)



    def reportFile(self, aFile):
        if hasattr(self, "report_%s"%(self.toProcess[aFile]['ptype'])):
            f = getattr(self, "report_%s"%(self.toProcess[aFile]['ptype']))
            return f(aFile)
        else:
            self.log("No Reporter found for type := %s"%(self.toProcess[aFile]['ptype']))


    def decode_SMTP(self, i):
        if i.startswith("EHLO") or i.startswith("HELO"):
            return 1
        elif i.startswith("MAIL FROM") or i.startswith("RCPT TO"):
            return 1
        else: 
            return 0
            
    def process_SMTP(self, aFile):
        a = False
        b = False
        for i in self.toProcess[aFile]['raw']:

            if a and i.startswith("MAIL FROM"):
                a = False
            if b and i == ".":
                b = False

            if a:
                self.toProcess[aFile]['logindata'].append(i)
            if b: 
                self.toProcess[aFile]['msgdata'].append(i)

            if i == "AUTH LOGIN": 
                a = True
                self.toProcess[aFile]['logindata'] = []
            if i == "DATA": 
                b = True
                self.toProcess[aFile]['msgdata'] = []
            if i.startswith("MAIL FROM:"):
                self.toProcess[aFile]['msg_from'] = i[11:]
            if i.startswith("RCPT TO:"):
                self.toProcess[aFile]['rcpt_to'] = i[9:]


    def report_SMTP(self, aFile):
        self.log("-"* 40)
        self.log(" Report: %s"%(aFile))
        self.log("-"* 40 + "\n")
        self.log("Found SMTP Session data")
        #self.log(toProcess[aFile].keys()

        if self.toProcess[aFile].has_key("logindata"):
            self.log("SMTP AUTH Login: %s"%(base64.decodestring(self.toProcess[aFile]['logindata'][0])))
            self.log("SMTP AUTH Password: %s"%(base64.decodestring(self.toProcess[aFile]['logindata'][1])))
        if self.toProcess[aFile].has_key('msg_from'):
            self.log("SMTP MAIL FROM: %s"%(self.toProcess[aFile]['msg_from']))
        if self.toProcess[aFile].has_key("rcpt_to"):
            self.log("SMTP RCPT TO: %s"%(self.toProcess[aFile]['rcpt_to']))
        if self.toProcess[aFile].has_key('msgdata'):
            self.streamcounter += 1
            if not os.path.exists(os.path.join(self.reportRoot, "messages", str(self.streamcounter))):
                os.makedirs(os.path.join(self.reportRoot, "messages", str(self.streamcounter)))
            
            x = "\r\n".join(self.toProcess[aFile]['msgdata'])
            msg = email.message_from_string(x)
            f = open(os.path.join(self.reportRoot, "messages", str(self.streamcounter), "%s.msg"%(aFile)), "w")
            f.write(x)
            f.close()
            self.log("Found email Messages")
            self.log(" - Writing to file: %s"%(os.path.join(self.reportRoot, "messages", str(self.streamcounter), "%s.msg"%(aFile))))
            self.log(" - MD5 of msg: %s"%(hashlib.md5(x).hexdigest()))
            counter = 1
            # The great docs at http://docs.python.org/library/email-examples.html 
            # show this easy way of breaking up a mail msg
            for part in msg.walk():
                if part.get_content_maintype() == 'multipart':
                    continue
                filename = part.get_filename()
                if not filename:
                    ext = mimetypes.guess_extension(part.get_content_type())
                    if not ext:
                        ext = '.bin'
                    filename = 'part-%03d%s' % (counter, ext)
                part_data = part.get_payload(decode=True)
                part_hash = hashlib.md5()
                part_hash.update(part_data)
                self.log("   - Found Attachment" )
                self.log("     - Writing to filename: %s "%( os.path.join(self.reportRoot, "messages", str(self.streamcounter), filename)))
                f = open(os.path.join(self.reportRoot, "messages", str(self.streamcounter), filename), "wb")
                f.write(part_data)
                f.close()
                self.log("     - Type of Attachement: %s"%(part.get_content_type()))
                self.log("     - MDS of Attachement: %s"%(part_hash.hexdigest()))
                if filename.endswith(".zip") or filename.endswith(".docx"):
                    self.log("       - ZIP Archive attachment extracting")
                    if not os.path.exists(os.path.join(self.reportRoot, "messages", str(self.streamcounter), "%s.unzipped"%(filename))):
                        os.makedirs(os.path.join(self.reportRoot, "messages", str(self.streamcounter), "%s.unzipped"%(filename)))
                    zfp = os.path.join(self.reportRoot, "messages", str(self.streamcounter), "%s.unzipped"%(filename))
                    zf = zipfile.ZipFile(os.path.join(self.reportRoot, "messages", str(self.streamcounter), filename))
                    for name in zf.namelist():
                        try:
                            (path,fname) = os.path.split(os.path.join(zfp, name))
                            os.makedirs(path)
                        except:
                            pass
                        f = open(os.path.join(path, fname), 'wb')
                        data = zf.read(name)
                        f.write(data)
                        self.log("         - Found file")
                        self.log("           - Writing to filename: %s"%(os.path.join(path, fname)))
                        self.log("           - Type of file: %s"%(mimetypes.guess_type(os.path.join(path, fname))[0]))
                        self.log("           - MDS of File: %s"%(hashlib.md5(data).hexdigest()))
if __name__ == '__main__':
    usage = "usage: %prog [options]"
    parser = OptionParser(usage)
    parser.add_option("-p", "--pcap", dest="pcapfile", help="Filename of the of the pcap to process")
    parser.add_option("-r", '--report', dest="report", default="./report",
            help="Directory for reporting and processed output and Created if neededneeded [Default: ./report]")
    parser.add_option("-f", '--force', dest="force", default=False, action="store_true", help="Force overwriting of files and direcorties")
    (options, args) = parser.parse_args(sys.argv)
    if not options.pcapfile:
        parser.error("-p|--pcap option must be specified please see --help for more details")
    if not os.path.isfile(options.pcapfile):
        parser.error("-f|--file %s must already be present on the system and accessable"%(options.pcapfile))
    if options.report and os.path.isfile(options.report):
        parser.error("-r|--report %s is a file and cannot be used for report output"%(options.report))
    if options.report and os.path.isdir(options.report) and not options.force:
        if os.listdir(options.report):
            parser.error("-r|--report path of '%s' and other files are present, please use -f|--force to allow for overwriting (not advised)"%(options.report))
            
    
    x = pcapReport(options.pcapfile, options.report)
    x.run()

