#!/usr/bin/env python2
# -*- coding: utf-8 -*-

'''
Faraday Penetration Test IDE
Copyright (C) 2013  Infobyte LLC (http://www.infobytesec.com/)
See the file 'doc/LICENSE' for the license information

'''
from __future__ import with_statement
from plugins import core
from model import api
import re
import os
import pprint
import sys

try:
    import xml.etree.cElementTree as ET
    import xml.etree.ElementTree as ET_ORIG
    ETREE_VERSION = ET_ORIG.VERSION
except ImportError:
    import xml.etree.ElementTree as ET
    ETREE_VERSION = ET.VERSION

ETREE_VERSION = [int(i) for i in ETREE_VERSION.split(".")]

current_path = os.path.abspath(os.getcwd())

__author__ = "Francisco Amato"
__copyright__ = "Copyright (c) 2013, Infobyte LLC"
__credits__ = ["Francisco Amato"]
__license__ = ""
__version__ = "1.0.0"
__maintainer__ = "Francisco Amato"
__email__ = "famato@infobytesec.com"
__status__ = "Development"


class NexposeXmlParser(object):
    """
    The objective of this class is to parse an xml file generated by the nexpose tool.

    TODO: Handle errors.
    TODO: Test nexpose output version. Handle what happens if the parser doesn't support it.
    TODO: Test cases.

    @param nexpose_xml_filepath A proper xml generated by nexpose
    """

    def __init__(self, xml_output):
        tree = self.parse_xml(xml_output)
        if tree:
            self.items = [data for data in self.get_items(tree)]
        else:
            self.items = []

    def parse_xml(self, xml_output):
        """
        Open and parse an xml file.

        TODO: Write custom parser to just read the nodes that we need instead of
        reading the whole file.

        @return xml_tree An xml tree instance. None if error.
        """
        try:
            tree = ET.fromstring(xml_output)
        except SyntaxError, err:
            print "SyntaxError: %s. %s" % (err, xml_output)
            return None

        return tree

    def get_items(self, tree):
        """
        @return items A list of Host instances
        """
        for node in tree.findall("devices/device"):
            yield Item(node)


class Item(object):
    """
    An abstract representation of a Item


    @param item_node A item_node taken from an nexpose xml tree
    """

    def __init__(self, item_node):
        self.node = item_node
        self.ip = item_node.get('address')
        self.os = self.get_text_from_subnode("fingerprint/description")

        self.vulns = self.getResults(item_node)

        self.srv = []

        for srv in item_node.findall("services/service"):
            item = {}
            self.node = srv
            item['name'] = srv.get('name')
            item['port'] = srv.get('port')
            item['protocol'] = srv.get('protocol')
            item['version'] = self.get_text_from_subnode(
                "fingerprint/description")
            item['vulns'] = self.getResults(srv)
            self.srv.append(item)

    def getResults(self, tree):
        """
        :param tree:
        """
        for self.issues in tree.findall("vulnerabilities/vulnerability"):
            yield Results(self.issues)

    def get_text_from_subnode(self, subnode_xpath_expr):
        """
        Finds a subnode in the host node and the retrieves a value from it.

        @return An attribute value
        """
        sub_node = self.node.find(subnode_xpath_expr)
        if sub_node is not None:
            return sub_node.text

        return None


class Results():

    def __init__(self, issue_node):
        self.node = issue_node
        self.name = issue_node.get('id')
        self.ref = []
        data = self.get_text_from_subnode("id/[@type='cve']")
        if data:
            self.ref.append(data)

        data = self.get_text_from_subnode("id/[@type='bid']")
        if data:
            self.ref.append("bid-" + data)

        data = self.get_text_from_subnode("id/[@type='osvdb']")
        if data:
            self.ref.append("osvdb-" + data)

        for v in issue_node.findall("id/[@type='secunia']"):
            self.ref.append("secunia-" + v.text)

        for v in issue_node.findall("id/[@type='url']"):
            self.ref.append("url-" + v.text)

        self.url = self.get_text_from_subnode("key")

    def get_text_from_subnode(self, subnode_xpath_expr):
        """
        Finds a subnode in the host node and the retrieves a value from it.

        @return An attribute value
        """
        sub_node = self.node.find(subnode_xpath_expr)
        if sub_node is not None:
            return sub_node.text

        return None


class NexposePlugin(core.PluginBase):
    """
    Example plugin to parse nexpose output.
    """

    def __init__(self):
        core.PluginBase.__init__(self)
        self.id = "Nexpose"
        self.name = "Nexpose XML Output Plugin"
        self.plugin_version = "0.0.1"
        self.version = "Nexpose Enterprise 5.7.19"
        self.framework_version = "1.0.0"
        self.options = None
        self._current_output = None
        self._command_regex = re.compile(r'^(sudo nexpose|\.\/nexpose).*?')

        global current_path
        self._output_file_path = os.path.join(self.data_path,
                                              "nexpose_output-%s.xml" % self._rid)

    def parseOutputString(self, output, debug=False):

        parser = NexposeXmlParser(output)
        for item in parser.items:
            h_id = self.createAndAddHost(item.ip, item.os)
            i_id = self.createAndAddInterface(
                h_id, item.ip, ipv4_address=item.ip, hostname_resolution=item.ip)

            for v in item.vulns:
                v_id = self.createAndAddVulnToHost(h_id, v.name, ref=v.ref)

            for s in item.srv:
                web = False
                s_id = self.createAndAddServiceToInterface(h_id, i_id, s['name'],
                                                           s['protocol'],
                                                           ports=[
                                                               str(s['port'])],
                                                           status="open",
                                                           version=s['version'])
                for v in s['vulns']:
                    if v.url:
                        v_id = self.createAndAddVulnWebToService(
                            h_id, s_id, v.name, ref=v.ref, website=item.ip, path=v.url)
                        if not web:
                            n_id = self.createAndAddNoteToService(
                                h_id, s_id, "website", "")
                            n2_id = self.createAndAddNoteToNote(
                                h_id, s_id, n_id, item.ip, "")
                            web = True
                    else:
                        v_id = self.createAndAddVulnToService(
                            h_id, s_id, v.name, ref=v.ref)
        del parser

    def processCommandString(self, username, current_path, command_string):
        return None

    def setHost(self):
        pass


def createPlugin():
    return NexposePlugin()

if __name__ == '__main__':
    parser = NexposeXmlParser(sys.argv[1])
    for item in parser.items:
        if item.status == 'up':
            print item
