python-libnmap-0.7.0/ 0000775 0001750 0001750 00000000000 12664653634 015260 5 ustar ronald ronald 0000000 0000000 python-libnmap-0.7.0/setup.py 0000664 0001750 0001750 00000002133 12664652760 016770 0 ustar ronald ronald 0000000 0000000 # -*- coding: utf-8 -*-
from distutils.core import setup
with open("README.rst") as rfile:
long_description = rfile.read()
setup(
name='python-libnmap',
version='0.7.0',
author='Ronald Bister',
author_email='mini.pelle@gmail.com',
packages=['libnmap', 'libnmap.plugins', 'libnmap.objects'],
url='http://pypi.python.org/pypi/python-libnmap/',
license='Creative Common "Attribution" license (CC-BY) v3',
description=('Python NMAP library enabling you to start async nmap tasks, '
'parse and compare/diff scan results'),
long_description=long_description,
classifiers=["Development Status :: 5 - Production/Stable",
"Environment :: Console",
"Programming Language :: Python :: 2",
"Programming Language :: Python :: 2.6",
"Programming Language :: Python :: 2.7",
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3.3",
"Programming Language :: Python :: 3.4",
"Topic :: System :: Networking"]
)
python-libnmap-0.7.0/LICENSE.txt 0000664 0001750 0001750 00000002264 12664652334 017103 0 ustar ronald ronald 0000000 0000000 This code is licensed under Creative Common "Attribution" license (CC-BY: http://creativecommons.org/licenses/by/3.0/).
You are free to:
- Share: to copy, distribute and transmit the work
- Remix: to adapt the work
- and make commercial use of the work
Under the following conditions:
- Attribution: You must attribute the work in the manner specified by the author or licensor (but not in any way that suggests that they endorse you or your use of the work).
With the understanding that:
Waiver: Any of the above conditions can be waived if you get permission from the copyright holder.
Public Domain: Where the work or any of its elements is in the public domain under applicable law, that status is in no way affected by the license.
Other Rights: In no way are any of the following rights affected by the license:
Your fair dealing or fair use rights, or other applicable copyright exceptions and limitations;
The author's moral rights;
Rights other persons may have either in the work itself or in how the work is used, such as publicity or privacy rights.
The full text is available here: http://creativecommons.org/licenses/by/3.0/legalcode
python-libnmap-0.7.0/libnmap/ 0000775 0001750 0001750 00000000000 12664653634 016702 5 ustar ronald ronald 0000000 0000000 python-libnmap-0.7.0/libnmap/diff.py 0000664 0001750 0001750 00000005520 12664652334 020162 0 ustar ronald ronald 0000000 0000000 # -*- coding: utf-8 -*-
class DictDiffer(object):
"""
Calculate the difference between two dictionaries as:
(1) items added
(2) items removed
(3) keys same in both but changed values
(4) keys same in both and unchanged values
"""
def __init__(self, current_dict, past_dict):
self.current_dict = current_dict
self.past_dict = past_dict
self.set_current = set(current_dict.keys())
self.set_past = set(past_dict.keys())
self.intersect = self.set_current.intersection(self.set_past)
def added(self):
return self.set_current - self.intersect
def removed(self):
return self.set_past - self.intersect
def changed(self):
return (set(o for o in self.intersect
if self.past_dict[o] != self.current_dict[o]))
def unchanged(self):
return (set(o for o in self.intersect
if self.past_dict[o] == self.current_dict[o]))
class NmapDiff(DictDiffer):
"""
NmapDiff compares two objects of same type to enable the user to check:
- what has changed
- what has been added
- what has been removed
- what was kept unchanged
NmapDiff inherit from DictDiffer which makes the actual comparaison.
The different methods from DictDiffer used by NmapDiff are the
following:
- NmapDiff.changed()
- NmapDiff.added()
- NmapDiff.removed()
- NmapDiff.unchanged()
Each of the returns a python set() of key which have changed in the
compared objects. To check the different keys that could be returned,
refer to the get_dict() method of the objects you which to
compare (i.e: libnmap.objects.NmapHost, NmapService,...).
"""
def __init__(self, nmap_obj1, nmap_obj2):
"""
Constructor of NmapDiff:
- Checks if the two objects are of the same class
- Checks if the objects are "comparable" via a call to id() (dirty)
- Inherits from DictDiffer and
"""
if(nmap_obj1.__class__ != nmap_obj2.__class__ or
nmap_obj1.id != nmap_obj2.id):
raise NmapDiffException("Comparing objects with non-matching id")
self.object1 = nmap_obj1.get_dict()
self.object2 = nmap_obj2.get_dict()
DictDiffer.__init__(self, self.object1, self.object2)
def __repr__(self):
return ("added: [{0}] -- changed: [{1}] -- "
"unchanged: [{2}] -- removed [{3}]".format(self.added(),
self.changed(),
self.unchanged(),
self.removed()))
class NmapDiffException(Exception):
def __init__(self, msg):
self.msg = msg
python-libnmap-0.7.0/libnmap/parser.py 0000664 0001750 0001750 00000063311 12664652334 020550 0 ustar ronald ronald 0000000 0000000 # -*- coding: utf-8 -*-
try:
import xml.etree.cElementTree as ET
except ImportError:
import xml.etree.ElementTree as ET
from libnmap.objects import NmapHost, NmapService, NmapReport
class NmapParser(object):
@classmethod
def parse(cls, nmap_data=None, data_type='XML', incomplete=False):
"""
Generic class method of NmapParser class.
The data to be parsed does not need to be a complete nmap
scan report. You can possibly give ...
or XML tags.
:param nmap_data: any portion of nmap scan result. \
nmap_data should always be a string representing a part \
or a complete nmap scan report.
:type nmap_data: string
:param data_type: specifies the type of data to be parsed.
:type data_type: string ("XML"|"JSON"|"YAML").
:param incomplete: enable you to parse interrupted nmap scans \
and/or incomplete nmap xml blocks by adding a at \
the end of the scan.
:type incomplete: boolean
As of today, only XML parsing is supported.
:return: NmapObject (NmapHost, NmapService or NmapReport)
"""
nmapobj = None
if data_type == "XML":
nmapobj = cls._parse_xml(nmap_data, incomplete)
else:
raise NmapParserException("Unknown data type provided. "
"Please check documentation for "
"supported data types.")
return nmapobj
@classmethod
def _parse_xml(cls, nmap_data=None, incomplete=False):
"""
Protected class method used to process a specific data type.
In this case: XML. This method is called by cls.parse class
method and receives nmap scan results data (in XML).
:param nmap_data: any portion of nmap scan result can be given \
as argument. nmap_data should always be a string representing \
a part or a complete nmap scan report.
:type nmap_data: string
This method checks which portion of a nmap scan is given \
as argument.
It could be:
1. a full nmap scan report;
2. a scanned host: tag in a nmap scan report
3. a scanned service: tag
4. a list of hosts: tag (TODO)
5. a list of ports: tag
:param incomplete: enable you to parse interrupted nmap scans \
and/or incomplete nmap xml blocks by adding a at \
the end of the scan.
:type incomplete: boolean
:return: NmapObject (NmapHost, NmapService or NmapReport) \
or a list of NmapObject
"""
if not nmap_data:
raise NmapParserException("No report data to parse: please "
"provide a valid XML nmap report")
elif not isinstance(nmap_data, str):
raise NmapParserException("wrong nmap_data type given as "
"argument: cannot parse data")
if incomplete is True:
nmap_data += ""
try:
root = ET.fromstring(nmap_data)
except:
raise NmapParserException("Wrong XML structure: cannot parse data")
nmapobj = None
if root.tag == 'nmaprun':
nmapobj = cls._parse_xml_report(root)
elif root.tag == 'host':
nmapobj = cls._parse_xml_host(root)
elif root.tag == 'ports':
nmapobj = cls._parse_xml_ports(root)
elif root.tag == 'port':
nmapobj = cls._parse_xml_port(root)
else:
raise NmapParserException("Unpexpected data structure for XML "
"root node")
return nmapobj
@classmethod
def _parse_xml_report(cls, root=None):
"""
This method parses out a full nmap scan report from its XML root
node: .
:param root: Element from xml.ElementTree (top of XML the document)
:type root: Element
:return: NmapReport object
"""
nmap_scan = {'_nmaprun': {}, '_scaninfo': {},
'_hosts': [], '_runstats': {}}
if root is None:
raise NmapParserException("No root node provided to parse XML "
"report")
nmap_scan['_nmaprun'] = cls.__format_attributes(root)
for el in root:
if el.tag == 'scaninfo':
nmap_scan['_scaninfo'] = cls.__parse_scaninfo(el)
elif el.tag == 'host':
nmap_scan['_hosts'].append(cls._parse_xml_host(el))
elif el.tag == 'runstats':
nmap_scan['_runstats'] = cls.__parse_runstats(el)
# else:
# print "struct pparse unknown attr: {0} value: {1}".format(
# el.tag,
# el.get(el.tag))
return NmapReport(nmap_scan)
@classmethod
def parse_fromstring(cls, nmap_data, data_type="XML", incomplete=False):
"""
Call generic cls.parse() method and ensure that a string is \
passed on as argument. If not, an exception is raised.
:param nmap_data: Same as for parse(), any portion of nmap scan. \
Reports could be passed as argument. Data type _must_ be a string.
:type nmap_data: string
:param data_type: Specifies the type of data passed on as argument.
:param incomplete: enable you to parse interrupted nmap scans \
and/or incomplete nmap xml blocks by adding a at \
the end of the scan.
:type incomplete: boolean
:return: NmapObject
"""
if not isinstance(nmap_data, str):
raise NmapParserException("bad argument type for "
"xarse_fromstring(): should be a string")
return cls.parse(nmap_data, data_type, incomplete)
@classmethod
def parse_fromfile(cls, nmap_report_path,
data_type="XML",
incomplete=False):
"""
Call generic cls.parse() method and ensure that a correct file \
path is given as argument. If not, an exception is raised.
:param nmap_data: Same as for parse(). \
Any portion of nmap scan reports could be passed as argument. \
Data type _must be a valid path to a file containing \
nmap scan results.
:param data_type: Specifies the type of serialization in the file.
:param incomplete: enable you to parse interrupted nmap scans \
and/or incomplete nmap xml blocks by adding a at \
the end of the scan.
:type incomplete: boolean
:return: NmapObject
"""
try:
with open(nmap_report_path, 'r') as fileobj:
fdata = fileobj.read()
rval = cls.parse(fdata, data_type, incomplete)
except IOError:
raise
return rval
@classmethod
def parse_fromdict(cls, rdict):
"""
Strange method which transforms a python dict \
representation of a NmapReport and turns it into an \
NmapReport object. \
Needs to be reviewed and possibly removed.
:param rdict: python dict representation of an NmapReport
:type rdict: dict
:return: NmapReport
"""
nreport = {}
if list(rdict.keys())[0] == '__NmapReport__':
r = rdict['__NmapReport__']
nreport['_runstats'] = r['_runstats']
nreport['_scaninfo'] = r['_scaninfo']
nreport['_nmaprun'] = r['_nmaprun']
hlist = []
for h in r['_hosts']:
slist = []
for s in h['__NmapHost__']['_services']:
cname = '__NmapService__'
slist.append(NmapService(portid=s[cname]['_portid'],
protocol=s[cname]['_protocol'],
state=s[cname]['_state'],
owner=s[cname]['_owner'],
service=s[cname]['_service']))
nh = NmapHost(starttime=h['__NmapHost__']['_starttime'],
endtime=h['__NmapHost__']['_endtime'],
address=h['__NmapHost__']['_address'],
status=h['__NmapHost__']['_status'],
hostnames=h['__NmapHost__']['_hostnames'],
extras=h['__NmapHost__']['_extras'],
services=slist)
hlist.append(nh)
nreport['_hosts'] = hlist
nmapobj = NmapReport(nreport)
return nmapobj
@classmethod
def __parse_scaninfo(cls, scaninfo_data):
"""
Private method parsing a portion of a nmap scan result.
Receives a XML tag.
:param scaninfo_data: XML tag from a nmap scan
:type scaninfo_data: xml.ElementTree.Element or a string
:return: python dict representing the XML scaninfo tag
"""
xelement = cls.__format_element(scaninfo_data)
return cls.__format_attributes(xelement)
@classmethod
def _parse_xml_host(cls, scanhost_data):
"""
Protected method parsing a portion of a nmap scan result.
Receives a XML tag representing a scanned host with
its services.
:param scaninfo_data: XML tag from a nmap scan
:type scaninfo_data: xml.ElementTree.Element or a string
:return: NmapHost object
"""
xelement = cls.__format_element(scanhost_data)
_host_header = cls.__format_attributes(xelement)
_hostnames = []
_services = []
_status = {}
_addresses = []
_host_extras = {}
extra_tags = ['uptime', 'distance', 'tcpsequence',
'ipidsequence', 'tcptssequence', 'times']
for xh in xelement:
if xh.tag == 'hostnames':
for hostname in cls.__parse_hostnames(xh):
_hostnames.append(hostname)
elif xh.tag == 'ports':
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':
_addresses.append(cls.__format_attributes(xh))
elif xh.tag == 'os':
_os_extra = cls.__parse_os_fingerprint(xh)
_host_extras.update({'os': _os_extra})
elif xh.tag == 'hostscript':
_host_scripts = cls.__parse_host_scripts(xh)
_host_extras.update({'hostscript': _host_scripts})
elif xh.tag in extra_tags:
_host_extras[xh.tag] = cls.__format_attributes(xh)
# else:
# print "struct host unknown attr: %s value: %s" %
# (h.tag, h.get(h.tag))
_stime = ''
_etime = ''
if 'starttime' in _host_header:
_stime = _host_header['starttime']
if 'endtime' in _host_header:
_etime = _host_header['endtime']
nhost = NmapHost(_stime,
_etime,
_addresses,
_status,
_hostnames,
_services,
_host_extras)
return nhost
@classmethod
def __parse_hostnames(cls, scanhostnames_data):
"""
Private method parsing the hostnames list within a XML tag.
:param scanhostnames_data: XML tag from a nmap scan
:type scanhostnames_data: xml.ElementTree.Element or a string
:return: list of hostnames
"""
xelement = cls.__format_element(scanhostnames_data)
hostnames = []
for hname in xelement:
if hname.tag == 'hostname':
hostnames.append(hname.get('name'))
return hostnames
@classmethod
def _parse_xml_ports(cls, scanports_data):
"""
Protected method parsing the list of scanned services from
a targeted host. This protected method cannot be called directly
with a string. A tag can be directly passed to parse()
and the below method will be called and return a list of nmap
scanned services.
:param scanports_data: XML tag from a nmap scan
:type scanports_data: xml.ElementTree.Element or a string
:return: list of NmapService
"""
xelement = cls.__format_element(scanports_data)
rdict = {'ports': [], 'extraports': None}
for xservice in xelement:
if xservice.tag == 'port':
nport = cls._parse_xml_port(xservice)
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 rdict
@classmethod
def _parse_xml_port(cls, scanport_data):
"""
Protected method parsing a scanned service from a targeted host.
This protected method cannot be called directly.
A tag can be directly passed to parse() and the below
method will be called and return a NmapService object
representing the state of the service.
:param scanport_data: XML tag from a nmap scan
:type scanport_data: xml.ElementTree.Element or a string
:return: NmapService
"""
xelement = cls.__format_element(scanport_data)
_port = cls.__format_attributes(xelement)
_portid = _port['portid'] if 'portid' in _port else None
_protocol = _port['protocol'] if 'protocol' in _port else None
_state = None
_service = None
_owner = None
_service_scripts = []
_service_extras = {}
for xport in xelement:
if xport.tag == 'state':
_state = cls.__format_attributes(xport)
elif xport.tag == 'service':
_service = cls.__parse_service(xport)
elif xport.tag == 'owner':
_owner = cls.__format_attributes(xport)
elif xport.tag == 'script':
_script_dict = cls.__parse_script(xport)
_service_scripts.append(_script_dict)
_service_extras['scripts'] = _service_scripts
if(_portid is None or _protocol is None or _state is None):
raise NmapParserException("XML tag is incomplete. One "
"of the following tags is missing: "
"portid, protocol or state or tag.")
nport = NmapService(_portid,
_protocol,
_state,
_service,
_owner,
_service_extras)
return nport
@classmethod
def __parse_service(cls, xserv):
"""
Parse tag to manage CPE object
"""
_service = cls.__format_attributes(xserv)
_cpelist = []
for _servnode in xserv:
if _servnode.tag == 'cpe':
_cpe_string = _servnode.text
_cpelist.append(_cpe_string)
_service['cpelist'] = _cpelist
return _service
@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:
: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):
"""
Private method parsing the data from NSE scripts output
:param script_data: portion of XML describing the results of the
script data
:type script_data: xml.ElementTree.Element or a string
:return: python dict holding scripts output
"""
_script_dict = cls.__format_attributes(script_data)
_elt_dict = {}
for script_elem in script_data:
if script_elem.tag == 'elem':
_elt_dict.update({script_elem.get('key'): script_elem.text})
elif script_elem.tag == 'table':
tdict = {}
for telem in script_elem:
# Handle duplicate element keys
tkey = telem.get('key')
if tkey in tdict:
if not isinstance(tdict[tkey], list):
tdict[tkey] = [tdict[tkey], ]
tdict[tkey].append(telem.text)
else:
tdict[tkey] = telem.text
# Handle duplicate table keys
skey = script_elem.get('key')
if skey in _elt_dict:
if not isinstance(_elt_dict[skey], list):
_elt_dict[skey] = [_elt_dict[skey], ]
_elt_dict[skey].append(tdict)
else:
_elt_dict[skey] = tdict
_script_dict['elements'] = _elt_dict
return _script_dict
@classmethod
def __parse_host_scripts(cls, scripts_data):
"""
Private method parsing the data from scripts affecting
the target host.
Contents of is returned as a list of dict.
:param scripts_data: portion of XML describing the results of the
scripts data
:type scripts_data: xml.ElementTree.Element or a string
:return: python list holding scripts output in a dict
"""
_host_scripts = []
for xscript in scripts_data:
if xscript.tag == 'script':
_script_dict = cls.__parse_script(xscript)
_host_scripts.append(_script_dict)
return _host_scripts
@classmethod
def __parse_os_fingerprint(cls, os_data):
"""
Private method parsing the data from an OS fingerprint (-O).
Contents of is returned as a dict.
:param os_data: portion of XML describing the results of the
os fingerprinting attempt
:type os_data: xml.ElementTree.Element or a string
:return: python dict representing the XML os tag
"""
rdict = {}
xelement = cls.__format_element(os_data)
os_class_probability = []
os_match_probability = []
os_ports_used = []
os_fingerprints = []
for xos in xelement:
# for nmap xml version < 1.04, osclass is not
# embedded in osmatch
if xos.tag == 'osclass':
os_class_proba = cls.__parse_osclass(xos)
os_class_probability.append(os_class_proba)
elif xos.tag == 'osmatch':
os_match_proba = cls.__parse_osmatch(xos)
os_match_probability.append(os_match_proba)
elif xos.tag == 'portused':
os_portused = cls.__format_attributes(xos)
os_ports_used.append(os_portused)
elif xos.tag == 'osfingerprint':
os_fp_dict = cls.__format_attributes(xos)
os_fingerprints.append(os_fp_dict)
rdict['osmatches'] = os_match_probability
rdict['osclasses'] = os_class_probability
rdict['ports_used'] = os_ports_used
rdict['osfingerprints'] = os_fingerprints
return rdict
@classmethod
def __parse_osmatch(cls, osmatch_data):
"""
This methods parses osmatch data and returns a dict. Depending
on the nmap xml version, osmatch could contain an osclass
dict.
:param osmatch_data: XML tag from a nmap scan
:type osmatch_data: xml.ElementTree.Element or a string
:return: python dict representing the XML osmatch tag
"""
rdict = {}
xelement = cls.__format_element(osmatch_data)
rdict['osmatch'] = cls.__format_attributes(xelement)
rdict['osclasses'] = []
for xmltag in xelement:
if xmltag.tag == 'osclass':
_osclass_dict = cls.__parse_osclass(xmltag)
rdict['osclasses'].append(_osclass_dict)
else:
exmsg = "Unexcepted node in : {0}".format(xmltag.tag)
raise NmapParserException(exmsg)
return rdict
@classmethod
def __parse_osclass(cls, osclass_data):
"""
This methods parses osclass data and returns a dict. Depending
on the nmap xml version, osclass could contain a cpe
dict.
:param osclass_data: XML tag from a nmap scan
:type osclass_data: xml.ElementTree.Element or a string
:return: python dict representing the XML osclass tag
"""
rdict = {}
xelement = cls.__format_element(osclass_data)
rdict['osclass'] = cls.__format_attributes(xelement)
rdict['cpe'] = []
for xmltag in xelement:
if xmltag.tag == 'cpe':
_cpe_string = xmltag.text
rdict['cpe'].append(_cpe_string)
else:
exmsg = "Unexcepted node in : {0}".format(xmltag.tag)
raise NmapParserException(exmsg)
return rdict
@classmethod
def __parse_runstats(cls, scanrunstats_data):
"""
Private method parsing a portion of a nmap scan result.
Receives a XML tag.
:param scanrunstats_data: XML tag from a nmap scan
:type scanrunstats_data: xml.ElementTree.Element or a string
:return: python dict representing the XML runstats tag
"""
xelement = cls.__format_element(scanrunstats_data)
rdict = {}
for xmltag in xelement:
if xmltag.tag in ['finished', 'hosts']:
rdict[xmltag.tag] = cls.__format_attributes(xmltag)
else:
exmsg = "Unexcepted node in : {0}".format(xmltag.tag)
raise NmapParserException(exmsg)
return rdict
@staticmethod
def __format_element(elt_data):
"""
Private method which ensures that a XML portion to be parsed is
of type xml.etree.ElementTree.Element.
If elt_data is a string, then it is converted to an
XML Element type.
:param elt_data: XML Element to be parsed or string
to be converted to a XML Element
:return: Element
"""
if isinstance(elt_data, str):
try:
xelement = ET.fromstring(elt_data)
except:
raise NmapParserException("Error while trying "
"to instanciate XML Element from "
"string {0}".format(elt_data))
elif ET.iselement(elt_data):
xelement = elt_data
else:
raise NmapParserException("Error while trying to parse supplied "
"data: unsupported format")
return xelement
@staticmethod
def __format_attributes(elt_data):
"""
Private method which converts a single XML tag to a python dict.
It also checks that the elt_data given as argument is of type
xml.etree.ElementTree.Element
:param elt_data: XML Element to be parsed or string
to be converted to a XML Element
:return: Element
"""
rval = {}
if not ET.iselement(elt_data):
raise NmapParserException("Error while trying to parse supplied "
"data attributes: format is not XML or "
"XML tag is empty")
try:
for dkey in elt_data.keys():
rval[dkey] = elt_data.get(dkey)
if rval[dkey] is None:
raise NmapParserException("Error while trying to build-up "
"element attributes: empty "
"attribute {0}".format(dkey))
except:
raise
return rval
class NmapParserException(Exception):
def __init__(self, msg):
self.msg = msg
def __str__(self):
return self.msg
python-libnmap-0.7.0/libnmap/process.py 0000664 0001750 0001750 00000052323 12664652334 020733 0 ustar ronald ronald 0000000 0000000 #!/usr/bin/env python
# -*- coding: utf-8 -*-
import os
import shlex
import subprocess
from threading import Thread
from xml.dom import pulldom
import warnings
import platform
try:
import pwd
except ImportError:
pass
__all__ = [
'NmapProcess'
]
class NmapTask(object):
"""
NmapTask is a internal class used by process. Each time nmap
starts a new task during the scan, a new class will be instanciated.
Classes examples are: "Ping Scan", "NSE script", "DNS Resolve",..
To each class an estimated time to complete is assigned and updated
at least every second within the NmapProcess.
A property NmapProcess.current_task points to the running task at
time T and a dictionnary NmapProcess.tasks with "task name" as key
is built during scan execution
"""
def __init__(self, name, starttime=0, extrainfo=''):
self.name = name
self.etc = 0
self.progress = 0
self.percent = 0
self.remaining = 0
self.status = 'started'
self.starttime = starttime
self.endtime = 0
self.extrainfo = extrainfo
self.updated = 0
class NmapProcess(Thread):
"""
NmapProcess is a class which wraps around the nmap executable.
Consequently, in order to run an NmapProcess, nmap should be installed
on the host running the script. By default NmapProcess will produce
the output of the nmap scan in the nmap XML format. This could be then
parsed out via the NmapParser class from libnmap.parser module.
"""
def __init__(self, targets="127.0.0.1",
options="-sT", event_callback=None, safe_mode=True, fqp=None):
"""
Constructor of NmapProcess class.
:param targets: hosts to be scanned. Could be a string of hosts \
separated with a coma or a python list of hosts/ip.
:type targets: string or list
:param options: list of nmap options to be applied to scan. \
These options are all documented in nmap's man pages.
:param event_callback: callable function which will be ran \
each time nmap process outputs data. This function will receive \
two parameters:
1. the nmap process object
2. the data produced by nmap process. See readme for examples.
:param safe_mode: parameter to protect unsafe options like -oN, -oG, \
-iL, -oA,...
:param fqp: full qualified path, if None, nmap will be searched \
in the PATH
:return: NmapProcess object
"""
Thread.__init__(self)
unsafe_opts = set(['-oG', '-oN', '-iL', '-oA', '-oS', '-oX',
'--iflist', '--resume', '--stylesheet',
'--datadir'])
# more reliable than just using os.name() (cygwin)
self.__is_windows = platform.system() == 'Windows'
if fqp:
if os.path.isfile(fqp) and os.access(fqp, os.X_OK):
self.__nmap_binary = fqp
else:
raise EnvironmentError(1, "wrong path or not executable", fqp)
else:
nmap_binary_name = "nmap"
self.__nmap_binary = self._whereis(nmap_binary_name)
self.__nmap_fixed_options = "-oX - -vvv --stats-every 1s"
if self.__nmap_binary is None:
raise EnvironmentError(1, "nmap is not installed or could "
"not be found in system path")
if isinstance(targets, str):
self.__nmap_targets = targets.replace(" ", "").split(',')
elif isinstance(targets, list):
self.__nmap_targets = targets
else:
raise Exception("Supplied target list should be either a "
"string or a list")
self._nmap_options = set(options.split())
if safe_mode and not self._nmap_options.isdisjoint(unsafe_opts):
raise Exception("unsafe options activated while safe_mode "
"is set True")
self.__nmap_dynamic_options = options
self.__sudo_run = ''
self.__nmap_command_line = self.get_command_line()
if event_callback and callable(event_callback):
self.__nmap_event_callback = event_callback
else:
self.__nmap_event_callback = None
(self.DONE, self.READY, self.RUNNING,
self.CANCELLED, self.FAILED) = range(5)
self._run_init()
def _run_init(self):
self.__nmap_command_line = self.get_command_line()
# API usable in callback function
self.__nmap_proc = None
self.__nmap_rc = 0
self.__state = self.RUNNING
self.__starttime = 0
self.__endtime = 0
self.__version = ''
self.__elapsed = ''
self.__summary = ''
self.__stdout = ''
self.__stderr = ''
self.__current_task = ''
self.__nmap_tasks = {}
def _whereis(self, program):
"""
Protected method enabling the object to find the full path of a binary
from its PATH environment variable.
:param program: name of a binary for which the full path needs to
be discovered.
:return: the full path to the binary.
:todo: add a default path list in case PATH is empty.
"""
split_char = ';' if self.__is_windows else ':'
program = program + '.exe' if self.__is_windows else program
for path in os.environ.get('PATH', '').split(split_char):
if (os.path.exists(os.path.join(path, program)) and not
os.path.isdir(os.path.join(path, program))):
return os.path.join(path, program)
return None
def get_command_line(self):
"""
Public method returning the reconstructed command line ran via the lib
:return: the full nmap command line to run
:rtype: string
"""
return ("{0} {1} {2} {3} {4}".format(self.__sudo_run,
self.__nmap_binary,
self.__nmap_fixed_options,
self.__nmap_dynamic_options,
" ".join(self.__nmap_targets)))
def sudo_run(self, run_as='root'):
"""
Public method enabling the library's user to run the scan with
priviledges via sudo. The sudo configuration should be set manually
on the local system otherwise sudo will prompt for a password.
This method alters the command line by prefixing the sudo command to
nmap and will then call self.run()
:param run_as: user name to which the lib needs to sudo to run the scan
:return: return code from nmap execution
"""
sudo_user = run_as.split().pop()
try:
pwd.getpwnam(sudo_user).pw_uid
except KeyError:
_exmsg = ("Username {0} does not exists. Please supply"
" a valid username".format(run_as))
raise EnvironmentError(_exmsg)
sudo_path = self._whereis("sudo")
if sudo_path is None:
raise EnvironmentError(2, "sudo is not installed or "
"could not be found in system path: "
"cannot run nmap with sudo")
self.__sudo_run = "{0} -u {1}".format(sudo_path, sudo_user)
rc = self.run()
self.__sudo_run = ""
return rc
def sudo_run_background(self, run_as='root'):
"""
Public method enabling the library's user to run in background a
nmap scan with priviledges via sudo.
The sudo configuration should be set manually on the local system
otherwise sudo will prompt for a password.
This method alters the command line by prefixing the sudo command to
nmap and will then call self.run()
:param run_as: user name to which the lib needs to sudo to run the scan
:return: return code from nmap execution
"""
sudo_user = run_as.split().pop()
try:
pwd.getpwnam(sudo_user).pw_uid
except KeyError:
_exmsg = ("Username {0} does not exists. Please supply"
" a valid username".format(run_as))
raise EnvironmentError(_exmsg)
sudo_path = self._whereis("sudo")
if sudo_path is None:
raise EnvironmentError(2, "sudo is not installed or "
"could not be found in system path: "
"cannot run nmap with sudo")
self.__sudo_run = "{0} -u {1}".format(sudo_path, sudo_user)
super(NmapProcess, self).start()
def run(self):
"""
Public method which is usually called right after the constructor
of NmapProcess. This method starts the nmap executable's subprocess.
It will also bind a Process that will read from subprocess' stdout
and stderr and push the lines read in a python queue for futher
processing. This processing is waken-up each time data is pushed
from the nmap binary into the stdout reading routine. Processing
could be performed by a user-provided callback. The whole
NmapProcess object could be accessible asynchroneously.
return: return code from nmap execution
"""
self._run_init()
_tmp_cmdline = self.__build_windows_cmdline() if self.__is_windows \
else shlex.split(self.__nmap_command_line)
try:
self.__nmap_proc = subprocess.Popen(args=_tmp_cmdline,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
universal_newlines=True,
bufsize=0)
self.__state = self.RUNNING
except OSError:
self.__state = self.FAILED
raise EnvironmentError(1, "nmap is not installed or could "
"not be found in system path")
while self.__nmap_proc.poll() is None:
for streamline in iter(self.__nmap_proc.stdout.readline, ''):
self.__stdout += streamline
evnt = self.__process_event(streamline)
if self.__nmap_event_callback and evnt:
self.__nmap_event_callback(self)
self.__stderr += self.__nmap_proc.stderr.read()
self.__nmap_rc = self.__nmap_proc.poll()
if self.rc is None:
self.__state = self.CANCELLED
elif self.rc == 0:
self.__state = self.DONE
if self.current_task:
self.__nmap_tasks[self.current_task.name].progress = 100
else:
self.__state = self.FAILED
# Call the callback one last time to signal the new state
if self.__nmap_event_callback:
self.__nmap_event_callback(self)
return self.rc
def run_background(self):
"""
run nmap scan in background as a thread.
For privileged scans, consider NmapProcess.sudo_run_background()
"""
self.__state = self.RUNNING
super(NmapProcess, self).start()
def is_running(self):
"""
Checks if nmap is still running.
:return: True if nmap is still running
"""
return self.state == self.RUNNING
def has_terminated(self):
"""
Checks if nmap has terminated. Could have failed or succeeded
:return: True if nmap process is not running anymore.
"""
return (self.state == self.DONE or self.state == self.FAILED or
self.state == self.CANCELLED)
def has_failed(self):
"""
Checks if nmap has failed.
:return: True if nmap process errored.
"""
return self.state == self.FAILED
def is_successful(self):
"""
Checks if nmap terminated successfully.
:return: True if nmap terminated successfully.
"""
return self.state == self.DONE
def stop(self):
"""
Send KILL -15 to the nmap subprocess and gently ask the threads to
stop.
"""
self.__state = self.CANCELLED
if self.__nmap_proc.poll() is None:
self.__nmap_proc.kill()
def __process_event(self, eventdata):
"""
Private method called while nmap process is running. It enables the
library to handle specific data/events produced by nmap process.
So far, the following events are supported:
1. task progress: updates estimated time to completion and percentage
done while scan is running. Could be used in combination with a
callback function which could then handle this data while scan is
running.
2. nmap run: header of the scan. Usually displayed when nmap is started
3. finished: when nmap scan ends.
:return: True is event is known.
:todo: handle parsing directly via NmapParser.parse()
"""
rval = False
try:
edomdoc = pulldom.parseString(eventdata)
for xlmnt, xmlnode in edomdoc:
if xlmnt is not None and xlmnt == pulldom.START_ELEMENT:
if (xmlnode.nodeName == 'taskbegin' and
xmlnode.attributes.keys()):
xt = xmlnode.attributes
taskname = xt['task'].value
starttime = xt['time'].value
xinfo = ''
if 'extrainfo' in xt.keys():
xinfo = xt['extrainfo'].value
newtask = NmapTask(taskname, starttime, xinfo)
self.__nmap_tasks[newtask.name] = newtask
self.__current_task = newtask.name
rval = True
elif (xmlnode.nodeName == 'taskend' and
xmlnode.attributes.keys()):
xt = xmlnode.attributes
tname = xt['task'].value
xinfo = ''
self.__nmap_tasks[tname].endtime = xt['time'].value
if 'extrainfo' in xt.keys():
xinfo = xt['extrainfo'].value
self.__nmap_tasks[tname].extrainfo = xinfo
self.__nmap_tasks[tname].status = "ended"
rval = True
elif (xmlnode.nodeName == 'taskprogress' and
xmlnode.attributes.keys()):
xt = xmlnode.attributes
tname = xt['task'].value
percent = xt['percent'].value
etc = xt['etc'].value
remaining = xt['remaining'].value
updated = xt['time'].value
self.__nmap_tasks[tname].percent = percent
self.__nmap_tasks[tname].progress = percent
self.__nmap_tasks[tname].etc = etc
self.__nmap_tasks[tname].remaining = remaining
self.__nmap_tasks[tname].updated = updated
rval = True
elif (xmlnode.nodeName == 'nmaprun' and
xmlnode.attributes.keys()):
self.__starttime = xmlnode.attributes['start'].value
self.__version = xmlnode.attributes['version'].value
rval = True
elif (xmlnode.nodeName == 'finished' and
xmlnode.attributes.keys()):
self.__endtime = xmlnode.attributes['time'].value
self.__elapsed = xmlnode.attributes['elapsed'].value
self.__summary = xmlnode.attributes['summary'].value
rval = True
except:
pass
return rval
def __build_windows_cmdline(self):
cmdline = []
cmdline.append(self.__nmap_binary)
if self.__nmap_fixed_options:
cmdline += self.__nmap_fixed_options.split()
if self.__nmap_dynamic_options:
cmdline += self.__nmap_dynamic_options.split()
if self.__nmap_targets:
cmdline += self.__nmap_targets # already a list
return cmdline
@property
def command(self):
"""
return the constructed nmap command or empty string if not
constructed yet.
:return: string
"""
return self.__nmap_command_line or ''
@property
def targets(self):
"""
Provides the list of targets to scan
:return: list of string
"""
return self.__nmap_targets
@property
def options(self):
"""
Provides the list of options for that scan
:return: list of string (nmap options)
"""
return self._nmap_options
@property
def state(self):
"""
Accessor for nmap execution state. Possible states are:
- self.READY
- self.RUNNING
- self.FAILED
- self.CANCELLED
- self.DONE
:return: integer (from above documented enum)
"""
return self.__state
@property
def starttime(self):
"""
Accessor for time when scan started
:return: string. Unix timestamp
"""
return self.__starttime
@property
def endtime(self):
"""
Accessor for time when scan ended
:return: string. Unix timestamp
"""
warnings.warn("data collected from finished events are deprecated."
"Use NmapParser.parse()", DeprecationWarning)
return self.__endtime
@property
def elapsed(self):
"""
Accessor returning for how long the scan ran (in seconds)
:return: string
"""
warnings.warn("data collected from finished events are deprecated."
"Use NmapParser.parse()", DeprecationWarning)
return self.__elapsed
@property
def summary(self):
"""
Accessor returning a short summary of the scan's results
:return: string
"""
warnings.warn("data collected from finished events are deprecated."
"Use NmapParser.parse()", DeprecationWarning)
return self.__summary
@property
def tasks(self):
"""
Accessor returning for the list of tasks ran during nmap scan
:return: dict of NmapTask object
"""
return self.__nmap_tasks
@property
def version(self):
"""
Accessor for nmap binary version number
:return: version number of nmap binary
:rtype: string
"""
return self.__version
@property
def current_task(self):
"""
Accessor for the current NmapTask beeing run
:return: NmapTask or None if no task started yet
"""
rval = None
if len(self.__current_task):
rval = self.tasks[self.__current_task]
return rval
@property
def etc(self):
"""
Accessor for estimated time to completion
:return: estimated time to completion
"""
rval = 0
if self.current_task:
rval = self.current_task.etc
return rval
@property
def progress(self):
"""
Accessor for progress status in percentage
:return: percentage of job processed.
"""
rval = 0
if self.current_task:
rval = self.current_task.progress
return rval
@property
def rc(self):
"""
Accessor for nmap execution's return code
:return: nmap execution's return code
"""
return self.__nmap_rc
@property
def stdout(self):
"""
Accessor for nmap standart output
:return: output from nmap scan in XML
:rtype: string
"""
return self.__stdout
@property
def stderr(self):
"""
Accessor for nmap standart error
:return: output from nmap when errors occured.
:rtype: string
"""
return self.__stderr
def main():
def mycallback(nmapscan=None):
if nmapscan.is_running() and nmapscan.current_task:
ntask = nmapscan.current_task
print("Task {0} ({1}): ETC: {2} DONE: {3}%".format(ntask.name,
ntask.status,
ntask.etc,
ntask.progress))
nm = NmapProcess("scanme.nmap.org",
options="-A",
event_callback=mycallback)
rc = nm.run()
if rc == 0:
print("Scan started at {0} nmap version: {1}").format(nm.starttime,
nm.version)
print("state: {0} (rc: {1})").format(nm.state, nm.rc)
print("results size: {0}").format(len(nm.stdout))
print("Scan ended {0}: {1}").format(nm.endtime, nm.summary)
else:
print("state: {0} (rc: {1})").format(nm.state, nm.rc)
print("Error: {stderr}").format(stderr=nm.stderr)
print("Result: {0}").format(nm.stdout)
if __name__ == '__main__':
main()
python-libnmap-0.7.0/libnmap/objects/ 0000775 0001750 0001750 00000000000 12664653634 020333 5 ustar ronald ronald 0000000 0000000 python-libnmap-0.7.0/libnmap/objects/os.py 0000664 0001750 0001750 00000031430 12664652334 021323 0 ustar ronald ronald 0000000 0000000 # -*- coding: utf-8 -*-
import warnings
from libnmap.objects.cpe import CPE
class OSFPPortUsed(object):
"""
Port used class: this enables the user of NmapOSFingerprint class
to have a common and clear interface to access portused data which
were collected and used during os fingerprint scan
"""
def __init__(self, port_used_dict):
try:
self._state = port_used_dict['state']
self._proto = port_used_dict['proto']
self._portid = port_used_dict['portid']
except KeyError:
raise Exception("Cannot create OSFPPortUsed: missing required key")
@property
def state(self):
"""
Accessor for the portused state (closed, open,...)
"""
return self._state
@property
def proto(self):
"""
Accessor for the portused protocol (tcp, udp,...)
"""
return self._proto
@property
def portid(self):
"""
Accessor for the referenced port number used
"""
return self._portid
class NmapOSMatch(object):
"""
NmapOSMatch is an internal class used for offering results
from an nmap os fingerprint. This common interfaces makes
a compatibility between old nmap xml (<1.04) and new nmap
xml versions (used in nmapv6 for instance).
In previous xml version, osclass tags from nmap fingerprints
were not directly mapped to a osmatch. In new xml version,
osclass could be embedded in osmatch tag.
The approach to solve this is to create a common class
which will, for older xml version, match based on the accuracy
osclass to an osmatch. If no match, an osmatch will be made up
from a concat of os class attributes: vendor and osfamily.
Unmatched osclass will have a line attribute of -1.
More info, see issue #26 or http://seclists.org/nmap-dev/2012/q2/252
"""
def __init__(self, osmatch_dict):
_osmatch_dict = osmatch_dict['osmatch']
if('name' not in _osmatch_dict or
'line' not in _osmatch_dict or
'accuracy' not in _osmatch_dict):
raise Exception("Cannot create NmapOSClass: missing required key")
self._name = _osmatch_dict['name']
self._line = _osmatch_dict['line']
self._accuracy = _osmatch_dict['accuracy']
# create osclass list
self._osclasses = []
try:
for _osclass in osmatch_dict['osclasses']:
try:
_osclassobj = NmapOSClass(_osclass)
except:
raise Exception("Could not create NmapOSClass object")
self._osclasses.append(_osclassobj)
except KeyError:
pass
def add_osclass(self, osclass_obj):
"""
Add a NmapOSClass object to the OSMatch object. This method is
useful to implement compatibility with older versions of NMAP
by providing a common interface to access os fingerprint data.
"""
self._osclasses.append(osclass_obj)
@property
def osclasses(self):
"""
Accessor for all NmapOSClass objects matching with this OS Match
"""
return self._osclasses
@property
def name(self):
"""
Accessor for name attribute (e.g.: Linux 2.4.26 (Slackware 10.0.0))
"""
return self._name
@property
def line(self):
"""
Accessor for line attribute as integer. value equals -1 if this
osmatch holds orphans NmapOSClass objects. This could happen with
older version of nmap xml engine (<1.04 (e.g: nmapv6)).
:return: int
"""
return int(self._line)
@property
def accuracy(self):
"""
Accessor for accuracy
:return: int
"""
return int(self._accuracy)
def get_cpe(self):
"""
This method return a list of cpe stings and not CPE objects as
the NmapOSClass.cpelist property. This method is a helper to
simplify data management.
For more advanced handling of CPE data, use NmapOSClass.cpelist
and use the methods from CPE class
"""
_cpelist = []
for osc in self.osclasses:
for cpe in osc.cpelist:
_cpelist.append(cpe.cpestring)
return _cpelist
def __repr__(self):
rval = "{0}: {1}".format(self.name, self.accuracy)
for _osclass in self._osclasses:
rval += "\r\n |__ os class: {0}".format(str(_osclass))
return rval
class NmapOSClass(object):
"""
NmapOSClass offers an unified API to access data from analysed
osclass tag. As implemented in libnmap and newer version of nmap,
osclass objects will always be embedded in a NmapOSMatch.
Unmatched NmapOSClass will be stored in "dummy" NmapOSMatch objects
which will have the particularity of have a line attribute of -1.
On top of this, NmapOSClass will have optional CPE objects
embedded.
"""
def __init__(self, osclass_dict):
_osclass = osclass_dict['osclass']
if('vendor' not in _osclass or
'osfamily' not in _osclass or
'accuracy' not in _osclass):
raise Exception("Wrong osclass structure: missing required key")
self._vendor = _osclass['vendor']
self._osfamily = _osclass['osfamily']
self._accuracy = _osclass['accuracy']
self._osgen = ''
self._type = ''
# optional data
if 'osgen' in _osclass:
self._osgen = _osclass['osgen']
if 'type' in _osclass:
self._type = _osclass['type']
self._cpelist = []
for _cpe in osclass_dict['cpe']:
self._cpelist.append(CPE(_cpe))
@property
def cpelist(self):
"""
Returns a list of CPE Objects matching with this os class
:return: list of CPE objects
:rtype: Array
"""
return self._cpelist
@property
def vendor(self):
"""
Accessor for vendor information (Microsoft, Linux,...)
:return: string
"""
return self._vendor
@property
def osfamily(self):
"""
Accessor for OS family information (Windows, Linux,...)
:return: string
"""
return self._osfamily
@property
def accuracy(self):
"""
Accessor for OS class detection accuracy (int)
:return: int
"""
return int(self._accuracy)
@property
def osgen(self):
"""
Accessor for OS class generation (7, 8, 2.4.X,...).
:return: string
"""
return self._osgen
@property
def type(self):
"""
Accessor for OS class type (general purpose,...)
:return: string
"""
return self._type
@property
def description(self):
"""
Accessor helper which returns a concataned string of
the valuable attributes from NmapOSClass object
:return: string
"""
rval = "{0}: {1}, {2}".format(self.type, self.vendor, self.osfamily)
if len(self.osgen):
rval += "({0})".format(self.osgen)
return rval
def __repr__(self):
rval = "{0}: {1}, {2}".format(self.type, self.vendor, self.osfamily)
if len(self.osgen):
rval += "({0})".format(self.osgen)
for _cpe in self._cpelist:
rval += "\r\n |__ {0}".format(str(_cpe))
return rval
class NmapOSFingerprint(object):
"""
NmapOSFingerprint is a easier API for using os fingerprinting.
Data for OS fingerprint ( tag) is instanciated from
a NmapOSFingerprint which is accessible in NmapHost via NmapHost.os
"""
def __init__(self, osfp_data):
self.__osmatches = []
self.__ports_used = []
self.__fingerprints = []
if 'osmatches' in osfp_data:
for _osmatch in osfp_data['osmatches']:
_osmatch_obj = NmapOSMatch(_osmatch)
self.__osmatches.append(_osmatch_obj)
if 'osclasses' in osfp_data:
for _osclass in osfp_data['osclasses']:
_osclass_obj = NmapOSClass(_osclass)
_osmatched = self.get_osmatch(_osclass_obj)
if _osmatched is not None:
_osmatched.add_osclass(_osclass_obj)
else:
self._add_dummy_osmatch(_osclass_obj)
if 'osfingerprints' in osfp_data:
for _osfp in osfp_data['osfingerprints']:
if 'fingerprint' in _osfp:
self.__fingerprints.append(_osfp['fingerprint'])
if 'ports_used' in osfp_data:
for _pused_dict in osfp_data['ports_used']:
_pused = OSFPPortUsed(_pused_dict)
self.__ports_used.append(_pused)
def get_osmatch(self, osclass_obj):
"""
This function enables NmapOSFingerprint to determine if an
NmapOSClass object could be attached to an existing NmapOSMatch
object in order to respect the common interface for
the nmap xml version < 1.04 and >= 1.04
This method will return an NmapOSMatch object matching with
the NmapOSClass provided in parameter
(match is performed based on accuracy)
:return: NmapOSMatch object
"""
rval = None
for _osmatch in self.__osmatches:
if _osmatch.accuracy == osclass_obj.accuracy:
rval = _osmatch
break # sorry
return rval
def _add_dummy_osmatch(self, osclass_obj):
"""
This functions creates a dummy NmapOSMatch object in order to
encapsulate an NmapOSClass object which was not matched with an
existing NmapOSMatch object
"""
_dname = "{0}:{1}:{2}".format(osclass_obj.type,
osclass_obj.vendor,
osclass_obj.osfamily)
_dummy_dict = {'osmatch': {'name': _dname,
'accuracy': osclass_obj.accuracy,
'line': -1},
'osclasses': []}
_dummy_osmatch = NmapOSMatch(_dummy_dict)
self.__osmatches.append(_dummy_osmatch)
@property
def osmatches(self, min_accuracy=0):
_osmatches = []
for _osmatch in self.__osmatches:
if _osmatch.accuracy >= min_accuracy:
_osmatches.append(_osmatch)
return _osmatches
@property
def osclasses(self, min_accuracy=0):
osc_array = []
for _osm in self.osmatches:
for _osc in _osm.osclasses:
if _osc.accuracy >= min_accuracy:
osc_array.append(_osc)
return osc_array
@property
def fingerprint(self):
return "\r\n".join(self.__fingerprints)
@property
def fingerprints(self):
return self.__fingerprints
@property
def ports_used(self):
"""
Return an array of OSFPPortUsed object with the ports used to
perform the os fingerprint. This dict might contain another dict
embedded containing the ports_reason values.
"""
return self.__ports_used
def osmatch(self, min_accuracy=90):
warnings.warn("NmapOSFingerprint.osmatch is deprecated: "
"use NmapOSFingerprint.osmatches", DeprecationWarning)
os_array = []
for _osmatch in self.__osmatches:
if _osmatch.accuracy >= min_accuracy:
os_array.append(_osmatch.name)
return os_array
def osclass(self, min_accuracy=90):
warnings.warn("NmapOSFingerprint.osclass() is deprecated: "
"use NmapOSFingerprint.osclasses() if applicable",
DeprecationWarning)
os_array = []
for osmatch_entry in self.osmatches():
if osmatch_entry.accuracy >= min_accuracy:
for oclass in osmatch_entry.osclasses:
_ftstr = "type:{0}|vendor:{1}|osfamily{2}".format(
oclass.type,
oclass.vendor,
oclass.osfamily)
os_array.append(_ftstr)
return os_array
def os_cpelist(self):
cpelist = []
for _osmatch in self.osmatches:
for oclass in _osmatch.osclasses:
cpelist.extend(oclass.cpelist)
return cpelist
def __repr__(self):
rval = ""
for _osmatch in self.osmatches:
rval += "\r\n{0}".format(_osmatch)
rval += "Fingerprints: ".format(self.fingerprint)
return rval
python-libnmap-0.7.0/libnmap/objects/__init__.py 0000664 0001750 0001750 00000000326 12664652334 022441 0 ustar ronald ronald 0000000 0000000 # -*- coding: utf-8 -*-
from libnmap.objects.report import NmapReport
from libnmap.objects.host import NmapHost
from libnmap.objects.service import NmapService
__all__ = ['NmapReport', 'NmapHost', 'NmapService']
python-libnmap-0.7.0/libnmap/objects/service.py 0000664 0001750 0001750 00000021573 12664652334 022351 0 ustar ronald ronald 0000000 0000000 # -*- coding: utf-8 -*-
from libnmap.diff import NmapDiff
from libnmap.objects.os import CPE
class NmapService(object):
"""
NmapService represents a nmap scanned service. Its id() is comprised
of the protocol and the port.
Depending on the scanning options, some additional details might be
available or not. Like banner or extra datas from NSE (nmap scripts).
"""
def __init__(self, portid, protocol='tcp', state=None,
service=None, owner=None, service_extras=None):
"""
Constructor
:param portid: port number
:type portid: string
:param protocol: protocol of port scanned (tcp, udp)
:type protocol: string
:param state: python dict describing the service status
:type state: python dict
:param service: python dict describing the service name and banner
:type service: python dict
:param service_extras: additional info about the tested service
like scripts' data
"""
try:
self._portid = int(portid or -1)
except (ValueError, TypeError):
raise
if self._portid < 0 or self._portid > 65535:
raise ValueError
self._protocol = protocol
self._state = state if state is not None else {}
self._service = service if service is not None else {}
self._cpelist = []
if 'cpelist' in self._service:
for _cpe in self._service['cpelist']:
_cpeobj = CPE(_cpe)
self._cpelist.append(_cpeobj)
self._owner = ''
if owner is not None and 'name' in owner:
self._owner = owner['name']
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._tunnel = self._service['tunnel']
self._service_extras = []
if service_extras is not None:
self._service_extras = service_extras
def __eq__(self, other):
"""
Compares two NmapService objects to see if they are the same or
if one of them changed.
:param other: NmapService
: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):
"""
Compares two NmapService objects to see if they are different
if one of them changed.
:param other: NmapService
:return: boolean
"""
rval = True
if(self.__class__ == other.__class__ and self.id == other.id):
rval = (self.changed(other) > 0)
return rval
def __repr__(self):
return "{0}: [{1} {2}/{3} {4} ({5})]".format(self.__class__.__name__,
self.state,
str(self.port),
self.protocol,
self.service,
self.banner)
def __hash__(self):
return (hash(self.port) ^ hash(self.protocol) ^ hash(self.state) ^
hash(self.reason) ^ hash(self.service) ^ hash(self.banner))
def changed(self, other):
"""
Checks if a NmapService is different from another.
:param other: NmapService
:return: boolean
"""
return len(self.diff(other).changed())
@property
def port(self):
"""
Accessor for port.
:return: integer or -1
"""
return self._portid
@property
def protocol(self):
"""
Accessor for protocol
:return: string
"""
return self._protocol
@property
def state(self):
"""
Accessor for service's state (open, filtered, closed,...)
:return: string
"""
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):
"""
Accessor for service name.
:return: string or empty
"""
return self._service['name'] if 'name' in self._service else ''
@property
def service_dict(self):
"""
Accessor for service dictionary.
:return: dict or None
"""
return self._service
def open(self):
"""
Tells if the port was open or not
:return: boolean
"""
return 'state' in self._state and self._state['state'] == 'open'
@property
def owner(self):
"""
Accessor for service owner if available
"""
return self._owner
@property
def banner(self):
"""
Accessor for the service's banner. Only available
if the nmap option -sV or similar was used.
:return: string
"""
notrelevant = ['name', 'method', 'conf', 'cpelist',
'servicefp', 'tunnel']
relevant = ['product', 'version', 'extrainfo']
b = ''
skeys = self._service.keys()
if 'method' in self._service and self._service['method'] == "probed":
for relk in relevant:
if relk in skeys:
b += '{0}: {1} '.format(relk, self._service[relk])
for mkey in skeys:
if mkey not in notrelevant and mkey not in relevant:
b += '{0}: {1} '.format(mkey, self._service[mkey])
return b.rstrip()
@property
def cpelist(self):
"""
Accessor for list of CPE for this particular service
"""
return self._cpelist
@property
def scripts_results(self):
"""
Gives a python list of the nse scripts results.
The dict key is the name (id) of the nse script and
the value is the output of the script.
:return: dict
"""
scripts_dict = None
try:
scripts_dict = self._service_extras['scripts']
except (KeyError, TypeError):
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):
"""
Accessor for the id() of the NmapService.
This is used for diff()ing NmapService object via NmapDiff.
:return: tuple
"""
return "{0}.{1}".format(self.protocol, self.port)
def get_dict(self):
"""
Return a python dict representation of the NmapService object.
This is used to diff() NmapService objects via NmapDiff.
:return: dict
"""
return ({'id': str(self.id), 'port': str(self.port),
'protocol': self.protocol, 'banner': self.banner,
'service': self.service, 'state': self.state,
'reason': self.reason})
def diff(self, other):
"""
Calls NmapDiff to check the difference between self and
another NmapService 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.
:return: NmapDiff object
"""
return NmapDiff(self, other)
python-libnmap-0.7.0/libnmap/objects/report.py 0000664 0001750 0001750 00000024054 12664652334 022221 0 ustar ronald ronald 0000000 0000000 # -*- coding: utf-8 -*-
from libnmap.diff import NmapDiff
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)
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
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 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 scan_type(self):
"""
Accessor returning a string which identifies what type of scan
was launched (syn, ack, tcp,...).
:return: string
"""
return self._scaninfo['type']
@property
def hosts(self):
"""
Accessor returning an array of scanned hosts.
Scanned hosts are NmapHost objects.
:return: array of NmapHost
"""
return self._hosts
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
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']
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
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)
python-libnmap-0.7.0/libnmap/objects/host.py 0000664 0001750 0001750 00000033521 12664652334 021662 0 ustar ronald ronald 0000000 0000000 # -*- coding: utf-8 -*-
from libnmap.diff import NmapDiff
from libnmap.objects.os import NmapOSFingerprint
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))))
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
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
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]
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'])
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
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
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
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
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