Skip to content

Commit 02430ff

Browse files
committed
Basic diff() for NmapReport implemented
FIXME: - Create generic NmapObject with following methods as interface: - get_dict() - __eq__() - __lt__() - __ne__() - __dict__ ??? - Implement Following objects inheriting from NmapObject: - NmapHost - NmapService - NmapHostList - NmapServiceList - ... - Adapt NmapParser.parse() to be generic and return NmapObject if only a portion of XML is provided
1 parent 713272f commit 02430ff

5 files changed

Lines changed: 101 additions & 13 deletions

File tree

libnmap/parser.py

Lines changed: 23 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,29 @@
11
#!/usr/bin/env python
2+
import os
23
import xml.etree.ElementTree as ET
34
from StringIO import StringIO
45
from libnmap import NmapHost, NmapService
56

67
class NmapParser(object):
78
@classmethod
8-
def parse(cls, nmap_xml_report=None, type='XML'):
9+
def parse(cls, nmap_data=None, type='XML'):
910
nmap_scan = { 'nmaprun': {}, 'scaninfo': {}, 'hosts': [], 'runstats': {} }
10-
if not nmap_xml_report:
11-
raise Exception("No report to parse")
11+
if not nmap_data:
12+
raise NmapParserException("No report data to parse: please provide a file")
1213

1314
try:
14-
tree = ET.parse(StringIO(nmap_xml_report))
15+
if isinstance(nmap_data, str):
16+
tree = ET.parse(StringIO(nmap_data))
17+
elif isinstance(nmap_data, file):
18+
tree = ET.parse(nmap_data)
1519
except:
1620
raise
1721

1822
root = tree.getroot()
1923
if root.tag == 'nmaprun':
2024
nmap_scan['nmaprun'] = cls.__format_attributes(root)
2125
else:
22-
raise Exception('Unpexpected data structure for XML root node')
26+
raise NmapParserException('Unpexpected data structure for XML root node')
2327
for el in root:
2428
if el.tag == 'scaninfo':
2529
nmap_scan['scaninfo'] = cls.parse_scaninfo(el)
@@ -30,6 +34,20 @@ def parse(cls, nmap_xml_report=None, type='XML'):
3034
#else: print "struct pparse unknown attr: %s value: %s" % (el.tag, el.get(el.tag))
3135
return nmap_scan
3236

37+
@classmethod
38+
def parse_fromstring(cls, nmap_data, type="XML"):
39+
return cls.parse(nmap_data, type)
40+
41+
@classmethod
42+
def parse_fromfile(cls, nmap_report_path, type="XML"):
43+
if os.path.exists(nmap_report_path):
44+
fd = open(nmap_report_path, 'r')
45+
r = cls.parse(fd, type)
46+
fd.close()
47+
else:
48+
raise NmapParserException("Nmap data file could not be found or permissions were not set correctly")
49+
return r
50+
3351
@classmethod
3452
def parse_scaninfo(cls, scaninfo_data):
3553
xelement = cls.__format_element(scaninfo_data)

libnmap/report.py

Lines changed: 33 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,8 @@
11
#!/usr/bin/env python
2-
2+
from libnmap import NmapParser, NmapParserException, DictDiffer
33

44
## TODO: del/add_host()
55
# add/del_service()
6-
76
class NmapReport(object):
87
def __init__(self, name='', raw_data=None):
98
self._name = name
@@ -15,11 +14,19 @@ def __init__(self, name='', raw_data=None):
1514
self.set_raw_data(raw_data)
1615

1716
def report_import(self, file_path):
18-
return 0
17+
try:
18+
set_raw_data(NmapParser.parse_fromfile(file_path))
19+
except:
20+
raise NmapParserException("Error while trying to import file: {0}".format(file_path))
21+
1922
def report_export(self, file_path, output='csv'):
2023
return 0
24+
2125
def diff(self, other):
22-
return 0
26+
diff_dict = {}
27+
report_diffs = NmapDiff(self, other)
28+
29+
return report_diffs
2330

2431
def set_raw_data(self, raw_data):
2532
self._nmaprun = raw_data['nmaprun']
@@ -56,5 +63,27 @@ def get_raw_data(self):
5663
}
5764
return raw_data
5865

66+
def is_consistent(self):
67+
r = False
68+
rd = self.get_raw_data()
69+
if set(['nmaprun', 'scaninfo', 'hosts', 'runstats']) == set(rd.keys()) and \
70+
len([ k for k in rd.keys() if rd[k] is not None ]) == 4:
71+
r = True
72+
return r
73+
5974
def __repr__(self):
6075
return "{0} {1} hosts: {2} {3}".format(self._nmaprun, self._scaninfo, len(self._hosts), self._runstats)
76+
77+
def get_dict(self):
78+
return dict([ (h.address, h) for h in self.scanned_hosts ])
79+
80+
class NmapDiff(DictDiffer):
81+
class NmapDiffException(Exception):
82+
def __init__(self, msg):
83+
self.msg = msg
84+
85+
def __init__(self, nmap_obj1, nmap_obj2):
86+
self.object1 = nmap_obj1.get_dict()
87+
self.object2 = nmap_obj2.get_dict()
88+
89+
DictDiffer.__init__(self, self.object1, self.object2)

libnmap/test/diff_test.py

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
#!/usr/bin/env python
2+
3+
import unittest
4+
import os, sys
5+
6+
from libnmap import NmapParser, NmapParserException, NmapReport
7+
8+
class TestNmapParser(unittest.TestCase):
9+
def setUp(self):
10+
fdir = os.path.dirname(os.path.realpath(__file__))
11+
self.flist_full = [{'file': "%s/%s" % (fdir, 'files/2_hosts.xml'), 'hosts': 2}, {'file': "%s/%s" % (fdir,'files/1_hosts.xml'), 'hosts': 1},]
12+
self.flist = self.flist_full
13+
14+
def test_diff_host_list(self):
15+
fdir = os.path.dirname(os.path.realpath(__file__))
16+
d1 = NmapParser.parse_fromfile("%s/%s" % (fdir, 'files/1_hosts.xml'))
17+
d2 = NmapParser.parse_fromfile("%s/%s" % (fdir, 'files/2_hosts.xml'))
18+
19+
r1 = NmapReport("r1", d1)
20+
r2 = NmapReport("r2", d2)
21+
r3 = NmapReport("r3", d1)
22+
23+
d = r1.diff(r2)
24+
print "changed: %s" % (d.changed())
25+
print "unchanged: %s" % (d.unchanged())
26+
print "added: %s" % (d.added())
27+
print "removed: %s" % (d.removed())
28+
print "___________________________________"
29+
d = r1.diff(r3)
30+
print "changed: %s" % (d.changed())
31+
print "unchanged: %s" % (d.unchanged())
32+
print "added: %s" % (d.added())
33+
print "removed: %s" % (d.removed())
34+
35+
if __name__ == '__main__':
36+
test_suite = [ 'test_diff_host_list' ]
37+
suite = unittest.TestSuite(map(TestNmapParser, test_suite))
38+
test_result = unittest.TextTestRunner(verbosity=2).run(suite) ## for more verbosity uncomment this line and comment next line

libnmap/test/parser_test.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -79,9 +79,12 @@ def test_port_except(self):
7979
self.assertRaises(NmapParserException, NmapParser.parse_port, self.port_string7)
8080
self.assertRaises(NmapParserException, NmapParser.parse_port, self.port_string8)
8181
self.assertRaises(NmapParserException, NmapParser.parse_port, self.port_string9)
82+
83+
def test_parser_generic(self):
84+
plist = NmapParser.parse_fromstring(self.ports_string)
8285

8386
if __name__ == '__main__':
84-
test_suite = [ 'test_class_parser', 'test_class_ports_parser' , 'test_class_port_parser', 'test_port_except']
87+
test_suite = [ 'test_class_parser', 'test_class_ports_parser' , 'test_class_port_parser', 'test_port_except', 'test_parser_generic']
8588
# io_file = StringIO()
8689
suite = unittest.TestSuite(map(TestNmapParser, test_suite))
8790
test_result = unittest.TextTestRunner(verbosity=2).run(suite) ## for more verbosity uncomment this line and comment next line

setup.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,12 @@
22

33
setup(
44
name='libnmap',
5-
version='0.1',
5+
version='0.1.4',
66
author='Ronald Bister',
77
author_email='mini.pelle@gmail.com',
88
packages=['libnmap', 'libnmap.test'],
9-
url='http://pypi.python.org/pypi/pynmap/',
9+
url='http://pypi.python.org/pypi/libnmap/',
1010
license='LICENSE.txt',
11-
description='A Python NMAP librairy enabling you to launch nmap scans and parse XML scan results',
11+
description='A Python NMAP librairy enabling you to launch nmap scans, parse and compare (diff) scan results',
1212
long_description=open('README.txt').read(),
1313
)

0 commit comments

Comments
 (0)