diff --git a/ndiff/ndiff b/ndiff/ndiff index dc2087239..d76580a07 100755 --- a/ndiff/ndiff +++ b/ndiff/ndiff @@ -68,6 +68,7 @@ class Host(object): self.ports = {} self.extraports = {} self.os = [] + self.script_results = [] def get_id(self): """Return an id that is used to determine if hosts are "the same" across @@ -244,6 +245,7 @@ class Port(object): self.spec = spec self.state = state self.service = Service() + self.script_results = [] def state_string(self): if self.state is None: @@ -335,6 +337,39 @@ class Service(object): frag.appendChild(elem) return frag +class ScriptResult(object): + def __init__(self): + self.id = None + self.output = None + + def __eq__(self, other): + return self.id == other.id and self.output == other.output + + def __ne__(self, other): + return not self.__eq__(other) + + def __cmp__(self, other): + return cmp((self.id, self.output), (other.id, other.output)) + + def get_lines(self): + result = [] + lines = self.output.splitlines() + if len(lines) > 0: + lines[0] = self.id + u": " + lines[0] + for line in lines[:-1]: + result.append(u"| " + line) + if len(lines) > 0: + result.append(u"|_ " + lines[-1]) + return result + + def to_dom_fragment(self, document): + frag = document.createDocumentFragment() + elem = document.createElement(u"script") + elem.setAttribute(u"id", self.id) + elem.setAttribute(u"output", self.output) + frag.appendChild(elem) + return frag + def format_banner(scan): """Format a startup banner more or less like Nmap does.""" parts = [u"Nmap"] @@ -945,6 +980,26 @@ class NmapContentHandler(xml.sax.handler.ContentHandler): self.current_port.service.version = attrs.get(u"version") self.current_port.service.extrainfo = attrs.get(u"extrainfo") self.current_port.service.tunnel = attrs.get(u"tunnel") + elif name == u"script": + assert self.current_host is not None + result = ScriptResult() + try: + result.id = attrs[u"id"] + except KeyError: + warn(u"%s element of host %s missing the \"id\" attribute; skipping." % (name, self.current_host.format_name())) + return + try: + result.output = attrs[u"output"] + except KeyError: + warn(u"%s element of host %s missing the \"output\" attribute; skipping." % (name, self.current_host.format_name())) + return + if self.parent_element() == u"hostscript": + self.current_host.script_results.append(result) + elif self.parent_element() == u"port": + self.current_port.script_results.append(result) + else: + warn(u"%s element of host %s not inside hostscript or port element; ignoring." % (name, self.current_host.format_name())) + return elif name == u"osmatch": assert self.parent_element() == u"os" assert self.current_host is not None @@ -974,8 +1029,10 @@ class NmapContentHandler(xml.sax.handler.ContentHandler): assert spec not in self.current_host.ports self.current_host.add_port(Port(spec, state = extraports_state)) + self.current_host.script_results.sort() self.current_host = None elif name == u"port": + self.current_port.script_results.sort() self.current_port = None def usage(): diff --git a/ndiff/ndifftest.py b/ndiff/ndifftest.py index fb9b8cb60..d42f84f99 100755 --- a/ndiff/ndifftest.py +++ b/ndiff/ndifftest.py @@ -70,6 +70,14 @@ class scan_test(unittest.TestCase): host = scan.hosts[0] self.assertTrue(len(host.os) > 0) + def test_script(self): + """Test that script results are recorded.""" + scan = Scan() + scan.load_from_file("test-scans/complex.xml") + host = scan.hosts[0] + self.assertTrue(len(host.script_results) > 0) + self.assertTrue(len(host.ports[(22, u"tcp")].script_results) > 0) + # This test is commented out because Nmap XML doesn't store any information # about down hosts, not even the fact that they are down. Recovering the list of # scanned hosts to infer which ones are down would involve parsing the targets