# -*- coding: utf-8 -*-
from libnmap.diff import NmapDiff
from libnmap.objects.os import NmapOSFingerprint
[docs]class NmapHost(object):
"""
NmapHost is a class representing a host object of NmapReport
"""
def __init__(self, starttime='', endtime='', address=None, status=None,
hostnames=None, services=None, extras=None):
"""
NmapHost constructor
:param starttime: unix timestamp of when the scan against
that host started
:type starttime: string
:param endtime: unix timestamp of when the scan against
that host ended
:type endtime: string
:param address: dict ie :{'addr': '127.0.0.1', 'addrtype': 'ipv4'}
:param status: dict ie:{'reason': 'localhost-response',
'state': 'up'}
:return: NmapHost:
"""
self._starttime = starttime
self._endtime = endtime
self._hostnames = hostnames if hostnames is not None else []
self._status = status if status is not None else {}
self._services = services if services is not None else []
self._extras = extras if extras is not None else {}
self._osfingerprinted = False
self.os = None
if 'os' in self._extras:
self.os = NmapOSFingerprint(self._extras['os'])
self._osfingerprinted = True
else:
self.os = NmapOSFingerprint({})
self._ipv4_addr = None
self._ipv6_addr = None
self._mac_addr = None
self._vendor = None
for addr in address:
if addr['addrtype'] == "ipv4":
self._ipv4_addr = addr['addr']
elif addr['addrtype'] == 'ipv6':
self._ipv6_addr = addr['addr']
elif addr['addrtype'] == 'mac':
self._mac_addr = addr['addr']
if 'vendor' in addr:
self._vendor = addr['vendor']
self._main_address = self._ipv4_addr or self._ipv6_addr or ''
self._address = address
def __eq__(self, other):
"""
Compare eq NmapHost based on :
- hostnames
- address
- if an associated services has changed
:return: boolean
"""
rval = False
if(self.__class__ == other.__class__ and self.id == other.id):
rval = (self.changed(other) == 0)
return rval
def __ne__(self, other):
"""
Compare ne NmapHost based on:
- hostnames
- address
- if an associated services has changed
:return: boolean
"""
rval = True
if(self.__class__ == other.__class__ and self.id == other.id):
rval = (self.changed(other) > 0)
return rval
def __repr__(self):
"""
String representing the object
:return: string
"""
return "{0}: [{1} ({2}) - {3}]".format(self.__class__.__name__,
self.address,
" ".join(self._hostnames),
self.status)
def __hash__(self):
"""
Hash is needed to be able to use our object in sets
:return: hash
"""
return (hash(self.status) ^ hash(self.address) ^
hash(frozenset(self._services)) ^
hash(frozenset(" ".join(self._hostnames))))
[docs] def changed(self, other):
"""
return the number of attribute who have changed
:param other: NmapHost object to compare
:return int
"""
return len(self.diff(other).changed())
@property
def starttime(self):
"""
Accessor for the unix timestamp of when the scan was started
:return: string
"""
return self._starttime
@property
def endtime(self):
"""
Accessor for the unix timestamp of when the scan ended
:return: string
"""
return self._endtime
@property
def address(self):
"""
Accessor for the IP address of the scanned host
:return: IP address as a string
"""
return self._main_address
@address.setter
def address(self, addrdict):
"""
Setter for the address dictionnary.
:param addrdict: valid dict is {'addr': '1.1.1.1',
'addrtype': 'ipv4'}
"""
if addrdict['addrtype'] == 'ipv4':
self._ipv4_addr = addrdict['addr']
elif addrdict['addrtype'] == 'ipv6':
self._ipv6_addr = addrdict['addr']
elif addrdict['addrtype'] == 'mac':
self._mac_addr = addrdict['addr']
if 'vendor' in addrdict:
self._vendor = addrdict['vendor']
self._main_address = self._ipv4_addr or self._ipv6_addr or ''
self._address = addrdict
@property
def ipv4(self):
"""
Accessor for the IPv4 address of the scanned host
:return: IPv4 address as a string
"""
return self._ipv4_addr or ''
@property
def mac(self):
"""
Accessor for the MAC address of the scanned host
:return: MAC address as a string
"""
return self._mac_addr or ''
@property
def vendor(self):
"""
Accessor for the vendor attribute of the scanned host
:return: string (vendor) of empty string if no vendor defined
"""
return self._vendor or ''
@property
def ipv6(self):
"""
Accessor for the IPv6 address of the scanned host
:return: IPv6 address as a string
"""
return self._ipv6_addr or ''
@property
def status(self):
"""
Accessor for the host's status (up, down, unknown...)
:return: string
"""
return self._status['state']
@status.setter
def status(self, statusdict):
"""
Setter for the status dictionnary.
:param statusdict: valid dict is {"state": "open",
"reason": "syn-ack",
"reason_ttl": "0"}
'state' is the only mandatory key.
"""
self._status = statusdict
[docs] def is_up(self):
"""
method to determine if host is up or not
:return: bool
"""
rval = False
if self.status == 'up':
rval = True
return rval
@property
def hostnames(self):
"""
Accessor returning the list of hostnames (array of strings).
:return: array of string
"""
return self._hostnames
@property
def services(self):
"""
Accessor for the array of scanned services for that host.
An array of NmapService objects is returned.
:return: array of NmapService
"""
return self._services
[docs] def get_ports(self):
"""
Retrieve a list of the port used by each service of the NmapHost
:return: list: of tuples (port,'proto') ie:[(22,'tcp'),(25, 'tcp')]
"""
return [(p.port, p.protocol) for p in self._services]
[docs] def get_open_ports(self):
"""
Same as get_ports() but only for open ports
:return: list: of tuples (port,'proto') ie:[(22,'tcp'),(25, 'tcp')]
"""
return ([(p.port, p.protocol)
for p in self._services if p.state == 'open'])
[docs] def get_service(self, portno, protocol='tcp'):
"""
:param portno: int the portnumber
:param protocol='tcp': string ('tcp','udp')
:return: NmapService or None
"""
plist = [p for p in self._services if
p.port == portno and p.protocol == protocol]
if len(plist) > 1:
raise Exception("Duplicate services found in NmapHost object")
return plist.pop() if len(plist) else None
[docs] def get_service_byid(self, service_id):
"""
Returns a NmapService by providing its id.
The id of a nmap service is a python tupl made of (protocol, port)
"""
rval = None
for _tmpservice in self._services:
if _tmpservice.id == service_id:
rval = _tmpservice
return rval
[docs] def os_class_probabilities(self):
"""
Returns an array of possible OS class detected during
the OS fingerprinting.
:return: Array of NmapOSClass objects
"""
rval = []
if self.os is not None:
rval = self.os.osclasses
return rval
[docs] def os_match_probabilities(self):
"""
Returns an array of possible OS match detected during
the OS fingerprinting
:return: array of NmapOSMatches objects
"""
rval = []
if self.os is not None:
rval = self.os.osmatches
return rval
@property
def os_fingerprinted(self):
"""
Specify if the host has OS fingerprint data available
:return: Boolean
"""
return self._osfingerprinted
@property
def os_fingerprint(self):
"""
Returns the fingerprint of the scanned system.
:return: string
"""
rval = ''
if self.os is not None:
rval = "\n".join(self.os.fingerprints)
return rval
[docs] def os_ports_used(self):
"""
Returns an array of the ports used for OS fingerprinting
:return: array of ports used: [{'portid': '22',
'proto': 'tcp',
'state': 'open'},]
"""
rval = []
try:
rval = self._extras['os']['ports_used']
except (KeyError, TypeError):
pass
return rval
@property
def tcpsequence(self):
"""
Returns the difficulty to determine remotely predict
the tcp sequencing.
return: string
"""
rval = ''
try:
rval = self._extras['tcpsequence']['difficulty']
except (KeyError, TypeError):
pass
return rval
@property
def ipsequence(self):
"""
Return the class of ip sequence of the remote hosts.
:return: string
"""
rval = ''
try:
rval = self._extras['ipidsequence']['class']
except (KeyError, TypeError):
pass
return rval
@property
def uptime(self):
"""
uptime of the remote host (if nmap was able to determine it)
:return: string (in seconds)
"""
rval = 0
try:
rval = int(self._extras['uptime']['seconds'])
except (KeyError, TypeError):
pass
return rval
@property
def lastboot(self):
"""
Since when the host was booted.
:return: string
"""
rval = ''
try:
rval = self._extras['uptime']['lastboot']
except (KeyError, TypeError):
pass
return rval
@property
def distance(self):
"""
Number of hops to host
:return: int
"""
rval = 0
try:
rval = int(self._extras['distance']['value'])
except (KeyError, TypeError):
pass
return rval
@property
def scripts_results(self):
"""
Scripts results specific to the scanned host
:return: array of <script> dictionary
"""
rval = {}
try:
rval = self._extras['hostscript']
except (KeyError, TypeError):
pass
return rval
@property
def id(self):
"""
id of the host. Used for diff()ing NmapObjects
:return: string
"""
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' or None
"""
_xtrports = self._extras.get('extraports', None)
if _xtrports is None:
return None
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' or None
"""
r = self._extras.get('extraports', {})
return r.get('reasons', None)
[docs] def get_dict(self):
"""
Return a dict representation of the object.
This is needed by NmapDiff to allow comparaison
:return dict
"""
d = dict([("{0}::{1}".format(s.__class__.__name__, str(s.id)),
hash(s))
for s in self.services])
d.update({'address': self.address, 'status': self.status,
'hostnames': " ".join(self._hostnames)})
return d
[docs] def diff(self, other):
"""
Calls NmapDiff to check the difference between self and
another NmapHost object.
Will return a NmapDiff object.
This objects return python set() of keys describing the elements
which have changed, were added, removed or kept unchanged.
:param other: NmapHost to diff with
:return: NmapDiff object
"""
return NmapDiff(self, other)