# -*- coding: utf-8 -*-
from libnmap.diff import NmapDiff
[docs]class NmapReport(object):
"""
NmapReport is the usual interface for the end user to
read scans output.
A NmapReport as the following structure:
- Scan headers data
- A list of scanned hosts (NmapReport.hosts)
- Scan footer data
It is to note that each NmapHost comprised in NmapReport.hosts array
contains also a list of scanned services (NmapService object).
This means that if NmapParser.parse*() is the input interface for the
end user of the lib. NmapReport is certainly the output interface for
the end user of the lib.
"""
def __init__(self, raw_data=None):
"""
Constructor for NmapReport object.
This is usually called by the NmapParser module.
"""
self._nmaprun = {}
self._scaninfo = {}
self._hosts = []
self._runstats = {}
if raw_data is not None:
self.__set_raw_data(raw_data)
[docs] def save(self, backend):
"""
This method gets a NmapBackendPlugin representing the backend.
:param backend: libnmap.plugins.PluginBackend object.
Object created by BackendPluginFactory and enabling nmap reports
to be saved/stored in any type of backend implemented in plugins.
The primary key of the stored object is returned.
:return: str
"""
if backend is not None:
_id = backend.insert(self)
else:
raise RuntimeError
return _id
[docs] def diff(self, other):
"""
Calls NmapDiff to check the difference between self and
another NmapReport object.
Will return a NmapDiff object.
:return: NmapDiff object
:todo: remove is_consistent approach, diff() should be generic.
"""
if self.is_consistent() and other.is_consistent():
_rdiff = NmapDiff(self, other)
else:
_rdiff = set()
return _rdiff
@property
def started(self):
"""
Accessor returning a unix timestamp of when the scan was started.
:return: integer
"""
rval = -1
try:
s_start = self._nmaprun['start']
rval = int(s_start)
except(KeyError, TypeError, ValueError):
pass
return rval
@property
def startedstr(self):
"""
Accessor returning a human readable string of when the
scan was started
:return: string
"""
rval = ''
try:
rval = self._nmaprun['startstr']
except(KeyError, TypeError, ValueError):
pass
return rval
@property
def commandline(self):
"""
Accessor returning the full nmap command line fired.
:return: string
"""
return self._nmaprun['args']
@property
def version(self):
"""
Accessor returning the version of the
nmap binary used to perform the scan.
:return: string
"""
return self._nmaprun['version']
@property
def xmlversion(self):
"""
Accessor returning the XML output
version of the nmap report.
:return: string
"""
return self._nmaprun['xmloutputversion']
@property
def scan_type(self):
"""
Accessor returning a string which identifies what type of scan
was launched (syn, ack, tcp,...).
:return: string
"""
return self._scaninfo.get('type')
@property
def numservices(self):
"""
Accessor returning the number of services the
scan attempted to enumerate.
:return: integer
"""
rval = -1
try:
s_numsvcs = self._scaninfo.get('numservices')
rval = int(s_numsvcs)
except(KeyError, TypeError, ValueError):
pass
return rval
@property
def hosts(self):
"""
Accessor returning an array of scanned hosts.
Scanned hosts are NmapHost objects.
:return: array of NmapHost
"""
return self._hosts
[docs] def get_host_byid(self, host_id):
"""
Gets a NmapHost object directly from the host array
by looking it up by id.
:param ip_addr: ip address of the host to lookup
:type ip_addr: string
:return: NmapHost
"""
rval = None
for _rhost in self._hosts:
if _rhost.address == host_id:
rval = _rhost
return rval
@property
def endtime(self):
"""
Accessor returning a unix timestamp of when the scan ended.
:return: integer
"""
rval = -1
try:
rval = int(self._runstats['finished']['time'])
except(KeyError, TypeError, ValueError):
pass
return rval
@property
def endtimestr(self):
"""
Accessor returning a human readable time string
of when the scan ended.
:return: string
"""
rval = ''
try:
rval = self._runstats['finished']['timestr']
except(KeyError, TypeError, ValueError):
pass
return rval
@property
def summary(self):
"""
Accessor returning a string describing and
summarizing the scan.
:return: string
"""
rval = ''
try:
rval = self._runstats['finished']['summary']
except(KeyError, TypeError):
pass
if len(rval) == 0:
rval = ("Nmap ended at {0} ; {1} IP addresses ({2} hosts up)"
" scanned in {3} seconds".format(self.endtimestr,
self.hosts_total,
self.hosts_up,
self.elapsed))
return rval
@property
def elapsed(self):
"""
Accessor returning the number of seconds the scan took
:return: float (0 >= or -1)
"""
rval = -1
try:
s_elapsed = self._runstats['finished']['elapsed']
rval = float(s_elapsed)
except (KeyError, TypeError, ValueError):
rval = -1
return rval
@property
def hosts_up(self):
"""
Accessor returning the numer of host detected
as 'up' during the scan.
:return: integer (0 >= or -1)
"""
rval = -1
try:
s_up = self._runstats['hosts']['up']
rval = int(s_up)
except (KeyError, TypeError, ValueError):
rval = -1
return rval
@property
def hosts_down(self):
"""
Accessor returning the numer of host detected
as 'down' during the scan.
:return: integer (0 >= or -1)
"""
rval = -1
try:
s_down = self._runstats['hosts']['down']
rval = int(s_down)
except (KeyError, TypeError, ValueError):
rval = -1
return rval
@property
def hosts_total(self):
"""
Accessor returning the number of hosts scanned in total.
:return: integer (0 >= or -1)
"""
rval = -1
try:
s_total = self._runstats['hosts']['total']
rval = int(s_total)
except (KeyError, TypeError, ValueError):
rval = -1
return rval
[docs] def get_raw_data(self):
"""
Returns a dict representing the NmapReport object.
:return: dict
:todo: deprecate. get rid of this uglyness.
"""
raw_data = {'_nmaprun': self._nmaprun,
'_scaninfo': self._scaninfo,
'_hosts': self._hosts,
'_runstats': self._runstats}
return raw_data
def __set_raw_data(self, raw_data):
self._nmaprun = raw_data['_nmaprun']
self._scaninfo = raw_data['_scaninfo']
self._hosts = raw_data['_hosts']
self._runstats = raw_data['_runstats']
[docs] def is_consistent(self):
"""
Checks if the report is consistent and can be diffed().
This needs to be rewritten and removed: diff() should be generic.
:return: boolean
"""
rval = False
rdata = self.get_raw_data()
_consistent_keys = ['_nmaprun', '_scaninfo', '_hosts', '_runstats']
if(set(_consistent_keys) == set(rdata.keys()) and
len([dky for dky in rdata.keys() if rdata[dky] is not None]) == 4):
rval = True
return rval
[docs] def get_dict(self):
"""
Return a python dict representation of the NmapReport object.
This is used to diff() NmapReport objects via NmapDiff.
:return: dict
"""
rdict = dict([("{0}::{1}".format(_host.__class__.__name__,
str(_host.id)),
hash(_host)) for _host in self.hosts])
rdict.update({'commandline': self.commandline,
'version': self.version,
'scan_type': self.scan_type,
'elapsed': self.elapsed,
'hosts_up': self.hosts_up,
'hosts_down': self.hosts_down,
'hosts_total': self.hosts_total})
return rdict
@property
def id(self):
"""
Dummy id() defined for reports.
"""
return hash(1)
def __eq__(self, other):
"""
Compare eq NmapReport based on :
- create a diff obj and check the result
report are equal if added&changed&removed are empty
:return: boolean
"""
rval = False
if(self.__class__ == other.__class__ and self.id == other.id):
diffobj = self.diff(other)
rval = (len(diffobj.changed()) == 0 and
len(diffobj.added()) == 0 and
len(diffobj.removed()) == 0
)
return rval
def __ne__(self, other):
"""
Compare ne NmapReport based on:
- create a diff obj and check the result
report are ne if added|changed|removed are not empty
:return: boolean
"""
rval = True
if(self.__class__ == other.__class__ and self.id == other.id):
diffobj = self.diff(other)
rval = (len(diffobj.changed()) != 0 or
len(diffobj.added()) != 0 or
len(diffobj.removed()) != 0
)
return rval
def __repr__(self):
return "{0}: started at {1} hosts up {2}/{3}".format(
self.__class__.__name__,
self.started,
self.hosts_up,
self.hosts_total)