#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
    @copyright: 2009 Juhani Eronen, based on opencollab-nmap-uploader
    by Lari Huttunen

    Usable after eg. the following toolchain

    ettercap -l 20090427 -q -T -r capture-20090427.pcap //
    etterlog -mx 20090427.eci | sed -e '1,/<?xml/ d' > etter-20090427.xml

    @license: MIT <http://www.opensource.org/licenses/mit-license.php>
"""
import os
import sys
import socket
import optparse
import xml.dom.minidom
from opencollab.meta import Metas
from opencollab.util.wiki import importMetas
from opencollab.util.config import parseOptions
from opencollab.wiki import CLIWiki, WikiFailure
from opencollab.util.file import hashFile, uploadFile
from xml.dom.minidom import Node, ProcessingInstruction


def getText(nodelist):
    rc = ""
    if not nodelist:
        return rc
    for node in nodelist[0].childNodes:
        if node.nodeType == node.TEXT_NODE:
            rc = rc + node.data
    return rc


def fix_stylesheet_and_upload(collab, xml_page, file, fname):
    try:
        doc = xml.dom.minidom.parse(file)
    except xml.parsers.expat.ExpatError, e:
        error = file + ": " + str(e)
        sys.exit(error)

    stylesheet_nodes = []

    for node in doc.childNodes:
        if not isinstance(node, ProcessingInstruction):
            continue

        if node.nodeName != 'xml-stylesheet':
            continue

        stylesheet_nodes.append(node)

    if len(stylesheet_nodes) != 1:
        return False

    node = stylesheet_nodes[0]

    attributes = dict([item.split("=") for item in node.data.split()])

    stylesheet = attributes["href"].strip('"')

    if not os.path.exists(stylesheet):
        return False

    stylesheet_data = open(stylesheet).read()

    attributes["href"] = \
        '"?action=AttachFile&amp;do=get&amp;target=ettercap.xsl"'
    node.data = " ".join(["=".join(item) for item in attributes.iteritems()])

    xml_data = doc.toxml()

    collab.putAttachment(xml_page, fname, xml_data, True)
    collab.putAttachment(xml_page, "ettercap.xsl", stylesheet_data, True)
    return True


def parse_xml(file, ettercaprun_page):
    metas = Metas()
    attach = dict()

    try:
        doc = xml.dom.minidom.parse(file)
    except xml.parsers.expat.ExpatError, e:
        error = file + ": " + str(e)
        sys.exit(error)
    hosts = doc.getElementsByTagName('host')
    for host in hosts:
        if not host.nodeType == Node.ELEMENT_NODE:
            continue

        hostIp = host.getAttribute('ip')

        # Handle link elements
        res = host.getElementsByTagName('mac')
        if res:
            mac = getText(res)
            metas[hostIp]['Packet vertical'].add("[[%s]]" % (mac))
            metas[hostIp]['Identity type'].add("L3")
            metas[mac]['Packet vertical'].add("[[%s]]" % (hostIp))
            metas[mac]['Identity type'].add("L2")

        res = host.getElementsByTagName('hostname')
        if res:
            hostname = getText(res)
            metas[hostIp]['PTR'].add("[[%s]]" % (hostname))

        res = host.getElementsByTagName('os')
        if res:
            accuracy = res[0].hasAttribute('type')
            if accuracy:
                metas[hostIp]['OS Accuracy'].add(res[0].getAttribute('type'))
            os_detect = getText(res)
            metas[hostIp]['OS'].add(os_detect)

        res = host.getElementsByTagName('manuf')
        if res:
            os_manuf = getText(res)
            metas[hostIp]['OS Vendor'].add(os_manuf)

        for port in host.getElementsByTagName('port'):
            # Construct banner and service, if any
            banner = getText(port.getElementsByTagName('banner')).strip()
            if banner:
                banner = 'with %s ' % (banner)

            service = port.getAttribute('service')
            if service:
                service = '%s ' % (service)

            proto = port.getAttribute('proto').upper()
            port_nro = port.getAttribute('addr').upper()

            metas[hostIp]['serves'].add("%s%son port %s:%s" %
                                        (service, banner, proto, port_nro))
            metas[hostIp]['listens to'].add("[[%s:%s]]" % (proto, port_nro))

            for acct in port.getElementsByTagName('account'):
                user = getText(acct.getElementsByTagName('user'))
                if user:
                    user = 'user %s ' % (user)

                passwd = getText(acct.getElementsByTagName('pass'))
                if passwd:
                    passwd = 'with password %s ' % (passwd)

                client = getText(acct.getElementsByTagName('client'))
                if client:
                    client = 'from [[%s]] ' % (client)

                info = getText(acct.getElementsByTagName('info'))
                if info:
                    info = '(%s) ' % (info)

                success = port.getAttribute('failed')
                if success:
                    success = '(%s) ' % (success)

                metas[hostIp]['Plaintext credentials'].add("%s%s%s%s%s" %
                                                           (user, passwd,
                                                            client, info,
                                                            success))

    # Get general statistics
    etterlog = doc.getElementsByTagName('etterlog')[0]
    metas[ettercaprun_page]['version'].add(etterlog.getAttribute('version'))
    metas[ettercaprun_page]['date'].add(etterlog.getAttribute('date'))

    return metas, attach


def main():
    parser = optparse.OptionParser()
    parser.set_usage("%prog [options]")
    parser.add_option("-l", "--last-edit-wins",
        action="store_true", dest="lasteditwins", default=False,
        metavar="LAST-EDIT-WINS",
        help="Replace meta keys and values with LAST-EDIT-WINS semantics.")
    parser.add_option("-P", "--progress", action="store_true",
        dest="progress", default=False,
        help="Display file upload progress information.")
    ettercap_files = []
    ops = {}
    sect = "ettercap-uploader"
    ops = parseOptions(parser, sect, template=True, category=True)
    progress = ops[sect]["progress"]
    url = ops["creds"]["url"]
    x509 = ops[sect]["x509"]
    x509_ca_file = ops[sect]["x509_ca_file"]
    verbose = ops[sect]["verbose"]
    template = ops[sect]["template"]
    if template is None:
        template = "IdentityTemplate"
    category = ops[sect]["category"]
    if category is None:
        category = "CategoryIdentity"
    replace = ops[sect]["lasteditwins"]
    args = ops[sect]["args"]
    if len(args) < 1:
        parser.error("At least one XML input file path needs to be specified.")
    if verbose:
        print "Authenticating to: " + repr(url)
    while True:
        try:
            collab = CLIWiki(ssl_verify_cert=x509, ssl_ca_certs=x509_ca_file, **ops['creds'])
        except WikiFailure:
            print "ERROR: Authentication failed."
        except (UnicodeError, socket.gaierror):
            sys.exit("ERROR: Not a valid URL.")
        else:
            break
    if verbose:
        print "NOTE: Processing input files."
    for arg in args:
        ettercap_files.append(arg)
    for file in ettercap_files:
        xml_page = hashFile(file)
        fname = os.path.basename(file)
        if verbose:
            print "NOTE: Parsing", file
        metas, attach = parse_xml(file, xml_page)

        metas[xml_page]['TYPE'].add('Ettercap Run')
        metas[xml_page]['gwikicategory'] = ['CategoryEttercap']
        metas[xml_page]['gwikitemplate'] = ['EttercapTemplate']

        if category is not None:
            for page in metas:
                if not 'gwikicategory' in metas[page]:
                    metas[page]["gwikicategory"].add(category)
        if template is not None:
            for page in metas:
                if not 'gwikitemplate' in metas[page]:
                    metas[page]["gwikitemplate"].add(template)

        if verbose:
            print "NOTE: Importing metas to", url

        failed = importMetas(collab, metas, '', verbose, replace)
        if failed:
            for page in failed:
                if verbose:
                    print "ERROR uploading:", repr(metas[page])
                else:
                    print "ERROR uploading:", page
        if verbose:
            print "NOTE: Fixing stylesheet and uploading", file, \
                "and stylesheet to", xml_page
        if not fix_stylesheet_and_upload(collab, xml_page, file, fname):
            print "ERROR: Failed to patch xml file, uploading orginal instead"
            if verbose:
                print "NOTE: Uploading", file, "to", xml_page
            uploadFile(collab, xml_page, file, fname, progress)
        for page in attach:
            for fname in attach[page]:
                uploadFile(collab, page, attach[page][fname], fname, progress)

if __name__ == "__main__":
    try:
        main()
    except KeyboardInterrupt:
        print "Script interrupted via CTRL-C."

