Skip to content

Commit

Permalink
Merge pull request #18 from savon-noir/issue15
Browse files Browse the repository at this point in the history
Issue15
  • Loading branch information
savon-noir committed Apr 6, 2014
2 parents eec2258 + 48e1680 commit fba3026
Show file tree
Hide file tree
Showing 8 changed files with 191 additions and 10 deletions.
5 changes: 4 additions & 1 deletion .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,14 @@ install:
- "pip install pyflakes --use-mirrors"
- "pip install boto --use-mirrors"
- "pip install pymongo sqlalchemy MySQL-python --use-mirrors"
- "pip install coveralls"
- "python setup.py install"
# - "pip install -r requirements.txt --use-mirrors"
# # command to run tests
before_script:
- "pep8 . --exclude test,docs,examples"
- "pyflakes ."
- mysql -e 'create database poulet;'
script: nosetests
script: nosetests --with-coverage --cover-package=libnmap
after_success:
coveralls
8 changes: 8 additions & 0 deletions CHANGES.txt
Original file line number Diff line number Diff line change
@@ -1,3 +1,11 @@
v0.4.4, 04/04/2014 -- Bugs and features requests
- added support for tunnel attribute from <service> tag
- added support for servicefp (service fingerprint) in
attributes from <port><service> tag
- added support for reasons attributes from <port> tag
- added support for extraports/extrareasons tags
- corrected bug in serialization: missing extra data
(pull request from DougRoyal)
v0.4.3, 14/03/2014 -- Lots of bug corrections - Warning small changes in API:
- fix issue#14: better scripts parsing
- API change for NmapService.scripts_results:
Expand Down
21 changes: 21 additions & 0 deletions libnmap/objects/host.py
Original file line number Diff line number Diff line change
Expand Up @@ -495,6 +495,27 @@ def id(self):
"""
return self.address

@property
def extraports_state(self):
"""
dictionnary containing state and amount of extra ports scanned
for which a common state, usually, closed was discovered.
:return: dict with keys 'state' and 'count'
"""
_xtrports = self._extras['extraports']
return {'state': _xtrports['state'], 'count': _xtrports['count']}

@property
def extraports_reasons(self):
"""
dictionnary containing reasons why extra ports scanned
for which a common state, usually, closed was discovered.
:return: array of dict containing keys 'state' and 'count'
"""
return self._extras['extraports']['reasons']

def get_dict(self):
"""
Return a dict representation of the object.
Expand Down
72 changes: 70 additions & 2 deletions libnmap/objects/service.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,25 @@ def __init__(self, portid, protocol='tcp', state=None,
self._protocol = protocol
self._state = state if state is not None else {}
self._service = service if service is not None else {}

self._reason = ''
self._reason_ip = ''
self._reason_ttl = ''
self._servicefp = ''
self._tunnel = ''

if 'reason' in self._state:
self._reason = self._state['reason']
if 'reason_ttl' in self._state:
self._reason_ttl = self._state['reason_ttl']
if 'reason_ip' in self._state:
self._reason_ip = self._state['reason_ip']

if 'servicefp' in self._service:
self._servicefp = self._service['servicefp']
if 'tunnel' in self._service:
self._servicefp = self._service['tunnel']

self._service_extras = []
if service_extras is not None:
self._service_extras = service_extras
Expand Down Expand Up @@ -78,7 +97,7 @@ def __repr__(self):

def __hash__(self):
return (hash(self.port) ^ hash(self.protocol) ^ hash(self.state) ^
hash(self.service) ^ hash(self.banner))
hash(self.reason) ^ hash(self.service) ^ hash(self.banner))

def changed(self, other):
"""
Expand Down Expand Up @@ -117,6 +136,33 @@ def state(self):
"""
return self._state['state'] if 'state' in self._state else None

@property
def reason(self):
"""
Accessor for service's state reason (syn-ack, filtered,...)
:return: string or empty if not applicable
"""
return self._reason

@property
def reason_ip(self):
"""
Accessor for service's state reason ip
:return: string or empty if not applicable
"""
return self._reason_ip

@property
def reason_ttl(self):
"""
Accessor for service's state reason ttl
:return: string or empty if not applicable
"""
return self._reason_ttl

@property
def service(self):
"""
Expand Down Expand Up @@ -167,6 +213,27 @@ def scripts_results(self):
pass
return scripts_dict

@property
def servicefp(self):
"""
Accessor for the service's fingerprint
if the nmap option -sV or -A is used
:return: string if available
"""
return self._servicefp

@property
def tunnel(self):
"""
Accessor for the service's tunnel type
if applicable and available from scan
results
:return: string if available
"""
return self._tunnel

@property
def id(self):
"""
Expand All @@ -188,7 +255,8 @@ def get_dict(self):
"""
return ({'id': str(self.id), 'port': str(self.port),
'protocol': self.protocol, 'banner': self.banner,
'service': self.service, 'state': self.state})
'service': self.service, 'state': self.state,
'reason': self.reason})

def diff(self, other):
"""
Expand Down
39 changes: 35 additions & 4 deletions libnmap/parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -253,8 +253,10 @@ def _parse_xml_host(cls, scanhost_data):
for hostname in cls.__parse_hostnames(xh):
_hostnames.append(hostname)
elif xh.tag == 'ports':
for port in cls._parse_xml_ports(xh):
ports_dict = cls._parse_xml_ports(xh)
for port in ports_dict['ports']:
_services.append(port)
_host_extras['extraports'] = ports_dict['extraports']
elif xh.tag == 'status':
_status = cls.__format_attributes(xh)
elif xh.tag == 'address':
Expand Down Expand Up @@ -320,15 +322,18 @@ def _parse_xml_ports(cls, scanports_data):

xelement = cls.__format_element(scanports_data)

ports = []
rdict = {'ports': [], 'extraports': None}
for xservice in xelement:
if xservice.tag == 'port':
nport = cls._parse_xml_port(xservice)
ports.append(nport)
rdict['ports'].append(nport)
elif xservice.tag == 'extraports':
extraports = cls.__parse_extraports(xservice)
rdict['extraports'] = extraports
# else:
# print "struct port unknown attr: %s value: %s" %
# (h.tag, h.get(h.tag))
return ports
return rdict

@classmethod
def _parse_xml_port(cls, scanport_data):
Expand Down Expand Up @@ -378,6 +383,32 @@ def _parse_xml_port(cls, scanport_data):
_service_extras)
return nport

@classmethod
def __parse_extraports(cls, extraports_data):
"""
Private method parsing the data from extra scanned ports.
X extraports were in state "closed" server returned "conn-refused"
tag: <extraports>
:param extraports_data: XML data for extraports
:type extraports_data: xml.ElementTree.Element or a string
:return: python dict with following keys: state, count, reason
"""
rdict = {'state': '', 'count': '', 'reasons': []}
xelement = cls.__format_element(extraports_data)
extraports_dict = cls.__format_attributes(xelement)

if 'state' in extraports_dict:
rdict['state'] = extraports_dict
if 'count' in extraports_dict:
rdict['count'] = extraports_dict
for xelt in xelement:
if xelt.tag == 'extrareasons':
extrareasons_dict = cls.__format_attributes(xelt)
rdict['reasons'].append(extrareasons_dict)
return rdict

@classmethod
def __parse_script(cls, script_data):
"""
Expand Down
10 changes: 10 additions & 0 deletions libnmap/test/test_host.py
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +173,16 @@ def test_host_api(self):
self.assertEqual(len(h2.get_open_ports()), 3)
self.assertEqual(h2.get_service(22, "tcp").state, "open")

def test_extra_ports(self):
h1 = NmapParser.parse(host1)
h2 = NmapParser.parse(host2)

self.assertEqual(h1.extraports_state['state'], {'count': '995', 'state': 'WILLY_WONCKA'})
self.assertEqual(h1.extraports_reasons, [{'reason': 'conn-refused', 'count': '995'}])

self.assertEqual(h2.extraports_state['state'], {'count': '995', 'state': 'closed'})
self.assertEqual(h2.extraports_reasons, [{'reason': 'conn-refused', 'count': '995'}])

def test_diff_host(self):
h1 = NmapParser.parse(host1)
h2 = NmapParser.parse(host2)
Expand Down
7 changes: 6 additions & 1 deletion libnmap/test/test_parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,8 @@ def test_class_parser(self):
NmapParser.parse(s)

def test_class_ports_parser(self):
plist = NmapParser.parse(self.ports_string)
pdict = NmapParser.parse(self.ports_string)
plist = pdict['ports']
self.assertEqual(len(plist), 4)
self.assertEqual(sorted([p.port for p in plist]),
sorted([22, 25, 9929, 80]))
Expand All @@ -139,6 +140,10 @@ def test_class_port_parser(self):
self.assertNotEqual(p.state, "open")
self.assertEqual(p.state, "filtered")
self.assertEqual(p.service, "smtp")
self.assertEqual(p.reason, "admin-prohibited")
self.assertEqual(p.reason_ttl, "253")
self.assertEqual(p.reason_ip, "109.133.192.1")


def test_port_except(self):
self.assertRaises(ValueError,
Expand Down
39 changes: 37 additions & 2 deletions libnmap/test/test_service.py
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,34 @@
method="probed" conf="10"/>
</port>"""

port_string_other10 = """
<port protocol="tcp" portid="25">
<state state="open" reason="syn-ack" reason_ttl="64"/>
<service name="smtp" product="Postfix smtpd"
hostname=" jambon.localdomain" method="probed" conf="10"/>
</port>"""

port_string_other11 = """
<port protocol="tcp" portid="25">
<state state="open" reason="syn-ack" reason_ttl="69"/>
<service name="smtp" product="Postfix smtpd"
hostname=" jambon.localdomain" method="probed" conf="10"/>
</port>"""

port_string_other12 = """
<port protocol="tcp" portid="25">
<state state="filtered" reason="admin-prohibited"
reason_ttl="253" reason_ip="109.133.192.1"/>
<service name="smtp" method="table" conf="3"/>
</port>"""

port_string_other13 = """
<port protocol="tcp" portid="25">
<state state="filtered" reason="patin"
reason_ttl="253" reason_ip="109.133.192.1"/>
<service name="smtp" method="table" conf="3"/>
</port>"""


class TestNmapService(unittest.TestCase):
def setUp(self):
Expand Down Expand Up @@ -150,7 +178,7 @@ def test_port_state_unchanged(self):
#nservice4 = NmapParser.parse(port_string_other4)

self.assertEqual(nservice1.diff(nservice2).unchanged(),
set(['banner', 'protocol', 'port', 'service', 'id']))
set(['banner', 'protocol', 'port', 'service', 'id', 'reason']))

def test_port_service_changed(self):
nservice1 = NmapParser.parse(port_string)
Expand Down Expand Up @@ -181,11 +209,18 @@ def test_diff_service(self):
self.assertRaises(NmapDiffException, self.s1.diff, self.s3)
self.assertEqual(self.s1.diff(self.s4).changed(), set(['state']))
self.assertEqual(self.s1.diff(self.s4).unchanged(),
set(['banner', 'protocol', 'port', 'service', 'id']))
set(['banner', 'protocol', 'port', 'service',
'id', 'reason']))

self.assertEqual(self.s5.diff(self.s6).changed(), set(['banner']))
self.assertEqual(self.s6.diff(self.s6).changed(), set([]))

def test_diff_reason(self):
nservice12 = NmapParser.parse(port_string_other12)
nservice13 = NmapParser.parse(port_string_other13)
ddict = nservice12.diff(nservice13)
self.assertEqual(ddict.changed(), set(['reason']))

if __name__ == '__main__':
test_suite = ['test_port_state_changed', 'test_port_state_unchanged',
'test_port_service_changed', 'test_eq_service',
Expand Down

0 comments on commit fba3026

Please sign in to comment.