From 63f55a221cb9c9b2d560ce9f4f5feadb481e2d8e Mon Sep 17 00:00:00 2001 From: Swapneel Mehta Date: Fri, 30 Dec 2016 16:42:39 +0530 Subject: [PATCH 01/88] Update process.rst Corrections added for easier understanding --- docs/process.rst | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/docs/process.rst b/docs/process.rst index 9ae83f7..7944dbd 100644 --- a/docs/process.rst +++ b/docs/process.rst @@ -6,11 +6,11 @@ Purpose of libnmap.process The purpose of this module is to enable the lib users to launch and control nmap scans. This module will consequently fire the nmap command following the specified parameters provided in the constructor. -It is to note that this module will not perform a full inline parsing of the data. Only specific events are parsed and exploitable via either a callback function defined by the user and provided in the constructor; either by running the process in the background and accessing the NmapProcess attributes will the scan is running. +It is to note that this module will not perform a full inline parsing of the data. Only specific events are parsed and exploitable via either a callback function defined by the user and provided in the constructor or by running the process in the background and accessing the NmapProcess attributes while the scan is running. To run an nmap scan, you need to: -- instanciate NmapProcess +- instantiate NmapProcess - call the run*() methods Raw results of the scans will be available in the following properties: @@ -18,18 +18,18 @@ Raw results of the scans will be available in the following properties: - NmapProcess.stdout: string, XML output - NmapProcess.stderr: string, text error message from nmap process -To instanciate a NmapProcess instance, call the constructor with appropriate parameters +To instantiate an NmapProcess instance, call the constructor with the appropriate parameters Processing of events -------------------- -While Nmap is running, some events are process and parsed. This would enable you to: +While Nmap is running, some events are processed and parsed. This would enable you to: - evaluate estimated time to completion and progress in percentage -- find out which task is running and how many nmap task have been executed +- find out which task is running and how many nmap tasks have been executed - know the start time and nmap version -As you may know, depending on the nmap options you specified, nmap will execute several tasks like "DNS Resolve", "Ping Scan", "Connect Scan", "NSE scripts",... This is of course independent from libnmap but the lib is able to parse these tasks and will instanciate a NmapTask object for any task executed. The list of executed task is available via the following properties: +As you may know, depending on the nmap options you specified, nmap will execute several tasks like "DNS Resolve", "Ping Scan", "Connect Scan", "NSE scripts",... This is of course independent from libnmap but the lib is able to parse these tasks and will instantiate a NmapTask object for any task executed. The list of executed task is available via the following properties: - NmapProcess.tasks: list of NmapTask object (executed nmap tasks) - NmapProcess.current_task: returns the currently running NmapTask From c6f0c018a46901146ef2c71e088b031570c34a47 Mon Sep 17 00:00:00 2001 From: Giovanni Tataranni Date: Tue, 25 Sep 2018 10:10:01 +0200 Subject: [PATCH 02/88] fix typo in docs --- libnmap/diff.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libnmap/diff.py b/libnmap/diff.py index 4a958f5..9cb6660 100644 --- a/libnmap/diff.py +++ b/libnmap/diff.py @@ -40,7 +40,7 @@ class NmapDiff(DictDiffer): - what has been removed - what was kept unchanged - NmapDiff inherit from DictDiffer which makes the actual comparaison. + NmapDiff inherit from DictDiffer which makes the actual comparison. The different methods from DictDiffer used by NmapDiff are the following: From d509611bd727c2f7b4b233d684628e4664c52092 Mon Sep 17 00:00:00 2001 From: Noskill Date: Thu, 3 Jan 2019 15:35:49 +0100 Subject: [PATCH 03/88] Fix indentation in report.py unneeded spaces were added on line 97. I removed them. --- libnmap/objects/report.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/libnmap/objects/report.py b/libnmap/objects/report.py index 1f82e5d..34207b5 100644 --- a/libnmap/objects/report.py +++ b/libnmap/objects/report.py @@ -94,9 +94,9 @@ def startedstr(self): rval = '' try: rval = self._nmaprun['startstr'] - except(KeyError, TypeError, ValueError): - pass - return rval + except(KeyError, TypeError, ValueError): + pass + return rval @property def commandline(self): From f6f34adf69e76b556f7f5baef5124c62fce0885f Mon Sep 17 00:00:00 2001 From: Cooper Ry Lees Date: Mon, 18 Feb 2019 17:16:18 -0800 Subject: [PATCH 04/88] Update .travis.xi - Python 2.6 and 3.3 not really used anymore + tests are failing - Add Python 3.7 - Remove some commented out things that don't seem to be needed --- .travis.yml | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/.travis.yml b/.travis.yml index 6d505a3..e2c6632 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,17 +1,12 @@ language: python python: -# - "2.5" Not needed EL can use epel to upgrade to 2.6 - - "2.6" - "2.7" - - "3.3" - "3.4" - "3.5" - "3.6" + - "3.7" # command to install dependencies env: -# - MONGO_VERSION=1.2.12 -# - MONGO_VERSION=1.3.2 -# - MONGO_VERSION=1.3.7 - MONGO_VERSION=2.4.3 services: mongodb From 11668b1b9b138919848c3da1aff48af222f6b66e Mon Sep 17 00:00:00 2001 From: Sam Freeside Date: Tue, 7 May 2019 14:26:23 +0300 Subject: [PATCH 05/88] Fix indentions --- libnmap/objects/report.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/libnmap/objects/report.py b/libnmap/objects/report.py index 34207b5..60a97a1 100644 --- a/libnmap/objects/report.py +++ b/libnmap/objects/report.py @@ -94,10 +94,10 @@ def startedstr(self): rval = '' try: rval = self._nmaprun['startstr'] - except(KeyError, TypeError, ValueError): - pass - return rval - + except(KeyError, TypeError, ValueError): + pass + return rval + @property def commandline(self): """ From abbc5849bd65357a71fbaaf359933b0dcd78a8f8 Mon Sep 17 00:00:00 2001 From: Professor Plum Date: Wed, 16 Sep 2020 14:09:43 -0400 Subject: [PATCH 06/88] fix for nmap script table parser to handled nested tables --- libnmap/parser.py | 45 +++++++++++++++++++++++++++++++++++---------- 1 file changed, 35 insertions(+), 10 deletions(-) diff --git a/libnmap/parser.py b/libnmap/parser.py index 8331ef9..a595578 100644 --- a/libnmap/parser.py +++ b/libnmap/parser.py @@ -453,6 +453,40 @@ def __parse_extraports(cls, extraports_data): rdict['reasons'].append(extrareasons_dict) return rdict + + @classmethod + def __parse_script_table(cls, script_table): + """ + Private method parsing a table from NSE scripts output + + :param sccript_table: poertion of XML containing the table + :type script_table: xml.ElementTree.Element + + :return: python dict of table structure + """ + tdict = {} + for telem in script_table: + tkey = telem.get('key') + if telem.tag == 'elem': + if tkey in tdict: + if not instance(tdict[tkey], list): + tdict[tkey] = [tdict[tkey], ] + tdict[tkey].append(telem.text) + else: + tdict[tkey] = telem.text + elif telem.tag == 'table': + stdict = cls.__parse_script_table(telem) + + # Handle duplicate table keys + if tkey in tdict: + if not isinstance(tdict[tkey], list): + tdict[tkey] = [tdict[tkey], ] + tdict[tkey].append(stdict) + else: + tdict[tkey] = stdict + return tdict + + @classmethod def __parse_script(cls, script_data): """ @@ -471,16 +505,7 @@ def __parse_script(cls, 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 + tdict = cls.__parse_script_table(script_elem) # Handle duplicate table keys skey = script_elem.get('key') if skey in _elt_dict: From 84ace6b9ee9919ac0cd1ff44f341fb52a8e6c4fb Mon Sep 17 00:00:00 2001 From: Ronald Date: Tue, 17 Nov 2020 23:04:01 +0100 Subject: [PATCH 07/88] flake8 on code and fix typo from PR#104 --- .gitignore | 2 + .travis.yml | 12 +- docs/conf.py | 142 +++---- examples/check_cpe.py | 2 +- examples/diff_sample1.py | 26 +- examples/diff_sample2.py | 24 +- examples/elastikibana.py | 58 +-- examples/es_plugin.py | 7 +- examples/json_serialize.py | 2 +- examples/nmap_task.py | 19 +- examples/nmap_task_bg.py | 12 +- examples/os_fingerprint.py | 4 +- examples/proc_async.py | 7 +- examples/proc_nmap_like.py | 18 +- libnmap/__init__.py | 12 +- libnmap/diff.py | 29 +- libnmap/objects/__init__.py | 2 +- libnmap/objects/cpe.py | 37 +- libnmap/objects/host.py | 157 ++++---- libnmap/objects/os.py | 125 ++++--- libnmap/objects/report.py | 152 ++++---- libnmap/objects/service.py | 125 ++++--- libnmap/parser.py | 348 ++++++++++-------- libnmap/plugins/backendplugin.py | 5 +- libnmap/plugins/backendpluginFactory.py | 1 + libnmap/plugins/es.py | 21 +- libnmap/plugins/mongodb.py | 18 +- libnmap/plugins/s3.py | 24 +- libnmap/plugins/sql.py | 44 +-- libnmap/process.py | 276 ++++++++------ libnmap/reportjson.py | 20 +- .../test/process-stressbox/check_fqp_nmap.py | 18 +- .../process-stressbox/multi_nmap_process.py | 19 +- .../multi_nmap_process_background.py | 38 +- libnmap/test/process-stressbox/proc_async.py | 9 +- .../test/process-stressbox/proc_nmap_like.py | 18 +- libnmap/test/process-stressbox/stop_scan.py | 9 +- libnmap/test/process-stressbox/stressback.py | 24 +- libnmap/test/process-stressbox/stresstest.py | 2 +- libnmap/test/test_backend_plugin_factory.py | 334 +++++++++-------- libnmap/test/test_cpe.py | 83 +++-- libnmap/test/test_fp.py | 275 ++++++++++---- libnmap/test/test_host.py | 89 +++-- libnmap/test/test_new_parser.py | 26 +- libnmap/test/test_parser.py | 102 +++-- libnmap/test/test_report.py | 222 +++++------ libnmap/test/test_report_diff.py | 83 +++-- libnmap/test/test_service.py | 52 +-- setup.py | 42 ++- tox.ini | 13 +- 50 files changed, 1838 insertions(+), 1351 deletions(-) diff --git a/.gitignore b/.gitignore index fdece38..1be8b40 100644 --- a/.gitignore +++ b/.gitignore @@ -41,3 +41,5 @@ nosetests.xml .swp __pycache__ +.vscode/settings.json +.noseids diff --git a/.travis.yml b/.travis.yml index e2c6632..1a06184 100644 --- a/.travis.yml +++ b/.travis.yml @@ -17,15 +17,15 @@ before_install: install: - "pip install pep8" - "pip install pyflakes" -# - "pip install boto" # disabled: since boto not supporting py3 -# - "pip install pymongo sqlalchemy MySQL-python" # disabled MySQL-python (not py3 compatible) - - "pip install pymongo sqlalchemy pymysql" + # - "pip install boto" # disabled: since boto not supporting py3 + # - "pip install pymongo sqlalchemy MySQL-python" # disabled MySQL-python (not py3 compatible) + # - "pip install pymongo sqlalchemy pymysql" - "pip install coveralls" - - "python setup.py install" + - "pip install ." before_script: - "pep8 . --exclude test,docs,examples" - "pyflakes ." - - mysql -e 'create database poulet;' + # - mysql -e 'create database poulet;' script: nosetests --with-coverage --cover-package=libnmap after_success: coveralls @@ -35,4 +35,4 @@ deploy: provider: pypi user: savon_noir password: - secure: WiMQsq+IMzAsS+cNKyKT7u7PlwGwkH0t2eoBitP0ckIw6kNWlbM/HCCm6aa9Ns9LpIzI82x26Vg77bu+yiMxUzZPS8pxCJXL9fFGs7Qc6VC9S0iHUX+FCkhFEFvPl35YRGFuY0YQyF2oj9vZPPFFmXzY2JSOBHxVevgdsrw1BQM= + secure: WiMQsq+IMzAsS+cNKyKT7u7PlwGwkH0t2eoBitP0ckIw6kNWlbM/HCCm6aa9Ns9LpIzI82x26Vg77bu+yiMxUzZPS8pxCJXL9fFGs7Qc6VC9S0iHUX+FCkhFEFvPl35YRGFuY0YQyF2oj9vZPPFFmXzY2JSOBHxVevgdsrw1BQM= \ No newline at end of file diff --git a/docs/conf.py b/docs/conf.py index d51cc23..1933a3a 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -16,212 +16,206 @@ # If extensions (or modules to document with autodoc) are in another directory, # add these directories to sys.path here. If the directory is relative to the # documentation root, use os.path.abspath to make it absolute, like shown here. -sys.path.insert(0, os.path.abspath('..')) +sys.path.insert(0, os.path.abspath("..")) # -- General configuration ----------------------------------------------------- # If your documentation needs a minimal Sphinx version, state it here. -#needs_sphinx = '1.0' +# needs_sphinx = '1.0' # Add any Sphinx extension module names here, as strings. They can be extensions # coming with Sphinx (named 'sphinx.ext.*') or your custom ones. -extensions = ['sphinx.ext.autodoc', 'sphinx.ext.todo', 'sphinx.ext.viewcode'] +extensions = ["sphinx.ext.autodoc", "sphinx.ext.todo", "sphinx.ext.viewcode"] # Add any paths that contain templates here, relative to this directory. -templates_path = ['_templates'] +templates_path = ["_templates"] # The suffix of source filenames. -source_suffix = '.rst' +source_suffix = ".rst" # The encoding of source files. -#source_encoding = 'utf-8-sig' +# source_encoding = 'utf-8-sig' # The master toctree document. -master_doc = 'index' +master_doc = "index" # General information about the project. -project = u'libnmap' -copyright = u'CC-BY 2013, Ronald Bister' +project = u"libnmap" +copyright = u"CC-BY 2013, Ronald Bister" # The version info for the project you're documenting, acts as replacement for # |version| and |release|, also used in various other places throughout the # built documents. # # The short X.Y version. -version = '0.2' +version = "0.2" # The full version, including alpha/beta/rc tags. -release = '0.2' +release = "0.2" # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. -#language = None +# language = None # There are two options for replacing |today|: either, you set today to some # non-false value, then it is used: -#today = '' +# today = '' # Else, today_fmt is used as the format for a strftime call. -#today_fmt = '%B %d, %Y' +# today_fmt = '%B %d, %Y' # List of patterns, relative to source directory, that match files and # directories to ignore when looking for source files. -exclude_patterns = ['_build'] +exclude_patterns = ["_build"] # The reST default role (used for this markup: `text`) to use for all documents. -#default_role = None +# default_role = None # If true, '()' will be appended to :func: etc. cross-reference text. -#add_function_parentheses = True +# add_function_parentheses = True # If true, the current module name will be prepended to all description # unit titles (such as .. function::). -#add_module_names = True +# add_module_names = True # If true, sectionauthor and moduleauthor directives will be shown in the # output. They are ignored by default. -#show_authors = False +# show_authors = False # The name of the Pygments (syntax highlighting) style to use. -pygments_style = 'sphinx' +pygments_style = "sphinx" # A list of ignored prefixes for module index sorting. -#modindex_common_prefix = [] +# modindex_common_prefix = [] # If true, keep warnings as "system message" paragraphs in the built documents. -#keep_warnings = False +# keep_warnings = False # -- Options for HTML output --------------------------------------------------- # The theme to use for HTML and HTML Help pages. See the documentation for # a list of builtin themes. -html_theme = 'default' +html_theme = "default" # Theme options are theme-specific and customize the look and feel of a theme # further. For a list of options available for each theme, see the # documentation. -#html_theme_options = {} +# html_theme_options = {} # Add any paths that contain custom themes here, relative to this directory. -#html_theme_path = [] +# html_theme_path = [] # The name for this set of Sphinx documents. If None, it defaults to # " v documentation". -#html_title = None +# html_title = None # A shorter title for the navigation bar. Default is the same as html_title. -#html_short_title = None +# html_short_title = None # The name of an image file (relative to this directory) to place at the top # of the sidebar. -#html_logo = None +# html_logo = None # The name of an image file (within the static path) to use as favicon of the # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 # pixels large. -#html_favicon = None +# html_favicon = None # Add any paths that contain custom static files (such as style sheets) here, # relative to this directory. They are copied after the builtin static files, # so a file named "default.css" will overwrite the builtin "default.css". -html_static_path = ['_static'] +html_static_path = ["_static"] # If not '', a 'Last updated on:' timestamp is inserted at every page bottom, # using the given strftime format. -#html_last_updated_fmt = '%b %d, %Y' +# html_last_updated_fmt = '%b %d, %Y' # If true, SmartyPants will be used to convert quotes and dashes to # typographically correct entities. -#html_use_smartypants = True +# html_use_smartypants = True # Custom sidebar templates, maps document names to template names. -#html_sidebars = {} +# html_sidebars = {} # Additional templates that should be rendered to pages, maps page names to # template names. -#html_additional_pages = {} +# html_additional_pages = {} # If false, no module index is generated. -#html_domain_indices = True +# html_domain_indices = True # If false, no index is generated. -#html_use_index = True +# html_use_index = True # If true, the index is split into individual pages for each letter. -#html_split_index = False +# html_split_index = False # If true, links to the reST sources are added to the pages. -#html_show_sourcelink = True +# html_show_sourcelink = True # If true, "Created using Sphinx" is shown in the HTML footer. Default is True. -#html_show_sphinx = True +# html_show_sphinx = True # If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. -#html_show_copyright = True +# html_show_copyright = True # If true, an OpenSearch description file will be output, and all pages will # contain a tag referring to it. The value of this option must be the # base URL from which the finished HTML is served. -#html_use_opensearch = '' +# html_use_opensearch = '' # This is the file name suffix for HTML files (e.g. ".xhtml"). -#html_file_suffix = None +# html_file_suffix = None # Output file base name for HTML help builder. -htmlhelp_basename = 'libnmapdoc' +htmlhelp_basename = "libnmapdoc" # -- Options for LaTeX output -------------------------------------------------- latex_elements = { -# The paper size ('letterpaper' or 'a4paper'). -#'papersize': 'letterpaper', - -# The font size ('10pt', '11pt' or '12pt'). -#'pointsize': '10pt', - -# Additional stuff for the LaTeX preamble. -#'preamble': '', + # The paper size ('letterpaper' or 'a4paper'). + #'papersize': 'letterpaper', + # The font size ('10pt', '11pt' or '12pt'). + #'pointsize': '10pt', + # Additional stuff for the LaTeX preamble. + #'preamble': '', } # Grouping the document tree into LaTeX files. List of tuples # (source start file, target name, title, author, documentclass [howto/manual]). latex_documents = [ - ('index', 'libnmap.tex', u'libnmap Documentation', - u'Ronald Bister', 'manual'), + ("index", "libnmap.tex", u"libnmap Documentation", u"Ronald Bister", "manual") ] # The name of an image file (relative to this directory) to place at the top of # the title page. -#latex_logo = None +# latex_logo = None # For "manual" documents, if this is true, then toplevel headings are parts, # not chapters. -#latex_use_parts = False +# latex_use_parts = False # If true, show page references after internal links. -#latex_show_pagerefs = False +# latex_show_pagerefs = False # If true, show URL addresses after external links. -#latex_show_urls = False +# latex_show_urls = False # Documents to append as an appendix to all manuals. -#latex_appendices = [] +# latex_appendices = [] # If false, no module index is generated. -#latex_domain_indices = True +# latex_domain_indices = True # -- Options for manual page output -------------------------------------------- # One entry per manual page. List of tuples # (source start file, name, description, authors, manual section). -man_pages = [ - ('index', 'libnmap', u'libnmap Documentation', - [u'Ronald Bister'], 1) -] +man_pages = [("index", "libnmap", u"libnmap Documentation", [u"Ronald Bister"], 1)] # If true, show URL addresses after external links. -#man_show_urls = False +# man_show_urls = False # -- Options for Texinfo output ------------------------------------------------ @@ -230,19 +224,25 @@ # (source start file, target name, title, author, # dir menu entry, description, category) texinfo_documents = [ - ('index', 'libnmap', u'libnmap Documentation', - u'Ronald Bister', 'libnmap', 'One line description of project.', - 'Miscellaneous'), + ( + "index", + "libnmap", + u"libnmap Documentation", + u"Ronald Bister", + "libnmap", + "One line description of project.", + "Miscellaneous", + ) ] # Documents to append as an appendix to all manuals. -#texinfo_appendices = [] +# texinfo_appendices = [] # If false, no module index is generated. -#texinfo_domain_indices = True +# texinfo_domain_indices = True # How to display URL addresses: 'footnote', 'no', or 'inline'. -#texinfo_show_urls = 'footnote' +# texinfo_show_urls = 'footnote' # If true, do not generate a @detailmenu in the "Top" node's menu. -#texinfo_no_detailmenu = False +# texinfo_no_detailmenu = False diff --git a/examples/check_cpe.py b/examples/check_cpe.py index da6f4e6..e2656e6 100644 --- a/examples/check_cpe.py +++ b/examples/check_cpe.py @@ -3,7 +3,7 @@ from libnmap.parser import NmapParser -rep = NmapParser.parse_fromfile('libnmap/test/files/full_sudo6.xml') +rep = NmapParser.parse_fromfile("libnmap/test/files/full_sudo6.xml") print("Nmap scan discovered {0}/{1} hosts up".format(rep.hosts_up, rep.hosts_total)) diff --git a/examples/diff_sample1.py b/examples/diff_sample1.py index d838fea..69e3175 100644 --- a/examples/diff_sample1.py +++ b/examples/diff_sample1.py @@ -3,27 +3,29 @@ from libnmap.parser import NmapParser -rep1 = NmapParser.parse_fromfile('libnmap/test/files/1_hosts.xml') -rep2 = NmapParser.parse_fromfile('libnmap/test/files/1_hosts_diff.xml') +rep1 = NmapParser.parse_fromfile("libnmap/test/files/1_hosts.xml") +rep2 = NmapParser.parse_fromfile("libnmap/test/files/1_hosts_diff.xml") rep1_items_changed = rep1.diff(rep2).changed() -changed_host_id = rep1_items_changed.pop().split('::')[1] +changed_host_id = rep1_items_changed.pop().split("::")[1] changed_host1 = rep1.get_host_byid(changed_host_id) changed_host2 = rep2.get_host_byid(changed_host_id) host1_items_changed = changed_host1.diff(changed_host2).changed() -changed_service_id = host1_items_changed.pop().split('::')[1] +changed_service_id = host1_items_changed.pop().split("::")[1] changed_service1 = changed_host1.get_service_byid(changed_service_id) changed_service2 = changed_host2.get_service_byid(changed_service_id) service1_items_changed = changed_service1.diff(changed_service2).changed() for diff_attr in service1_items_changed: - print("diff({0}, {1}) [{2}:{3}] [{4}:{5}]".format(changed_service1.id, - changed_service2.id, - diff_attr, - getattr(changed_service1, - diff_attr), - diff_attr, - getattr(changed_service2, - diff_attr))) + print( + "diff({0}, {1}) [{2}:{3}] [{4}:{5}]".format( + changed_service1.id, + changed_service2.id, + diff_attr, + getattr(changed_service1, diff_attr), + diff_attr, + getattr(changed_service2, diff_attr), + ) + ) diff --git a/examples/diff_sample2.py b/examples/diff_sample2.py index 093bd63..6f2f105 100644 --- a/examples/diff_sample2.py +++ b/examples/diff_sample2.py @@ -16,9 +16,9 @@ def print_diff_added(obj1, obj2, added): for akey in added: nested = nested_obj(akey) if nested is not None: - if nested[0] == 'NmapHost': + if nested[0] == "NmapHost": subobj1 = obj1.get_host_byid(nested[1]) - elif nested[0] == 'NmapService': + elif nested[0] == "NmapService": subobj1 = obj1.get_service_byid(nested[1]) print("+ {0}".format(subobj1)) else: @@ -29,9 +29,9 @@ def print_diff_removed(obj1, obj2, removed): for rkey in removed: nested = nested_obj(rkey) if nested is not None: - if nested[0] == 'NmapHost': + if nested[0] == "NmapHost": subobj2 = obj2.get_host_byid(nested[1]) - elif nested[0] == 'NmapService': + elif nested[0] == "NmapService": subobj2 = obj2.get_service_byid(nested[1]) print("- {0}".format(subobj2)) else: @@ -42,17 +42,19 @@ def print_diff_changed(obj1, obj2, changes): for mkey in changes: nested = nested_obj(mkey) if nested is not None: - if nested[0] == 'NmapHost': + if nested[0] == "NmapHost": subobj1 = obj1.get_host_byid(nested[1]) subobj2 = obj2.get_host_byid(nested[1]) - elif nested[0] == 'NmapService': + elif nested[0] == "NmapService": subobj1 = obj1.get_service_byid(nested[1]) subobj2 = obj2.get_service_byid(nested[1]) print_diff(subobj1, subobj2) else: - print("~ {0} {1}: {2} => {3}".format(obj1, mkey, - getattr(obj2, mkey), - getattr(obj1, mkey))) + print( + "~ {0} {1}: {2} => {3}".format( + obj1, mkey, getattr(obj2, mkey), getattr(obj1, mkey) + ) + ) def print_diff(obj1, obj2): @@ -64,8 +66,8 @@ def print_diff(obj1, obj2): def main(): - newrep = NmapParser.parse_fromfile('libnmap/test/files/2_hosts_achange.xml') - oldrep = NmapParser.parse_fromfile('libnmap/test/files/1_hosts.xml') + newrep = NmapParser.parse_fromfile("libnmap/test/files/2_hosts_achange.xml") + oldrep = NmapParser.parse_fromfile("libnmap/test/files/1_hosts.xml") print_diff(newrep, oldrep) diff --git a/examples/elastikibana.py b/examples/elastikibana.py index 4b9774b..43c8640 100644 --- a/examples/elastikibana.py +++ b/examples/elastikibana.py @@ -12,32 +12,44 @@ def store_report(nmap_report, database, index): for nmap_host in nmap_report.hosts: rv = store_reportitem(nmap_host, database, index) if rv is False: - print("Failed to store host {0} in " - "elasticsearch".format(nmap_host.address)) + print( + "Failed to store host {0} in " + "elasticsearch".format(nmap_host.address) + ) rval = False return rval def get_os(nmap_host): - rval = {'vendor': 'unknown', 'product': 'unknown'} + rval = {"vendor": "unknown", "product": "unknown"} if nmap_host.is_up() and nmap_host.os_fingerprinted: cpelist = nmap_host.os.os_cpelist() if len(cpelist): mcpe = cpelist.pop() - rval.update({'vendor': mcpe.get_vendor(), - 'product': mcpe.get_product()}) + rval.update({ + "vendor": mcpe.get_vendor(), + "product": mcpe.get_product() + }) return rval def get_geoip_code(address): - gi = pygeoip.GeoIP('/usr/share/GeoIP/GeoIP.dat') + gi = pygeoip.GeoIP("/usr/share/GeoIP/GeoIP.dat") return gi.country_code_by_addr(address) def store_reportitem(nmap_host, database, index): - host_keys = ["starttime", "endtime", "address", "hostnames", - "ipv4", "ipv6", "mac", "status"] + host_keys = [ + "starttime", + "endtime", + "address", + "hostnames", + "ipv4", + "ipv6", + "mac", + "status", + ] jhost = {} for hkey in host_keys: if hkey == "starttime" or hkey == "endtime": @@ -46,16 +58,14 @@ def store_reportitem(nmap_host, database, index): else: jhost[hkey] = getattr(nmap_host, hkey) - jhost.update({'country': get_geoip_code(nmap_host.address)}) + jhost.update({"country": get_geoip_code(nmap_host.address)}) jhost.update(get_os(nmap_host)) for nmap_service in nmap_host.services: reportitems = get_item(nmap_service) for ritem in reportitems: ritem.update(jhost) - database.index(index=index, - doc_type="NmapItem", - body=ritem) + database.index(index=index, doc_type="NmapItem", body=ritem) return jhost @@ -67,9 +77,9 @@ def get_item(nmap_service): jservice = {} for skey in service_keys: jservice[skey] = getattr(nmap_service, skey) - jservice['type'] = 'port-scan' - jservice['service'] = nmap_service.service - jservice['service-data'] = nmap_service.banner + jservice["type"] = "port-scan" + jservice["service"] = nmap_service.service + jservice["service-data"] = nmap_service.banner ritems.append(jservice) # create report items from nse script output @@ -77,24 +87,26 @@ def get_item(nmap_service): jnse = {} for skey in service_keys: jnse[skey] = getattr(nmap_service, skey) - jnse['type'] = 'nse-script' - jnse['service'] = nse_item['id'] - jnse['service-data'] = nse_item['output'] + jnse["type"] = "nse-script" + jnse["service"] = nse_item["id"] + jnse["service-data"] = nse_item["output"] ritems.append(jnse) return ritems -xmlscans = ['../libnmap/test/files/1_hosts.xml', - '../libnmap/test/files/full_sudo6.xml', - '/vagrant/nmap_switches.xml', - '/vagrant/nmap-5hosts.xml'] +xmlscans = [ + "../libnmap/test/files/1_hosts.xml", + "../libnmap/test/files/full_sudo6.xml", + "/vagrant/nmap_switches.xml", + "/vagrant/nmap-5hosts.xml", +] for xmlscan in xmlscans: nmap_report = NmapParser.parse_fromfile(xmlscan) if nmap_report: rep_date = datetime.fromtimestamp(int(nmap_report.started)) - index = "nmap-{0}".format(rep_date.strftime('%Y-%m-%d')) + index = "nmap-{0}".format(rep_date.strftime("%Y-%m-%d")) db = Elasticsearch() j = store_report(nmap_report, db, index) diff --git a/examples/es_plugin.py b/examples/es_plugin.py index e2de9da..4bf2cbe 100644 --- a/examples/es_plugin.py +++ b/examples/es_plugin.py @@ -6,13 +6,12 @@ from datetime import datetime import json -nmap_report = NmapParser.parse_fromfile('libnmap/test/files/1_hosts.xml') -mindex = datetime.fromtimestamp(nmap_report.started).strftime('%Y-%m-%d') +nmap_report = NmapParser.parse_fromfile("libnmap/test/files/1_hosts.xml") +mindex = datetime.fromtimestamp(nmap_report.started).strftime("%Y-%m-%d") db = NmapElasticsearchPlugin(index=mindex) dbid = db.insert(nmap_report) nmap_json = db.get(dbid) nmap_obj = json.loads(json.dumps(nmap_json), cls=ReportDecoder) print(nmap_obj) -#print(db.getall()) - +# print(db.getall()) diff --git a/examples/json_serialize.py b/examples/json_serialize.py index 3068fdb..4e70b16 100644 --- a/examples/json_serialize.py +++ b/examples/json_serialize.py @@ -5,7 +5,7 @@ from libnmap.reportjson import ReportDecoder, ReportEncoder import json -nmap_report_obj = NmapParser.parse_fromfile('libnmap/test/files/1_hosts.xml') +nmap_report_obj = NmapParser.parse_fromfile("libnmap/test/files/1_hosts.xml") # create a json object from an NmapReport instance nmap_report_json = json.dumps(nmap_report_obj, cls=ReportEncoder) diff --git a/examples/nmap_task.py b/examples/nmap_task.py index 09b6859..cbea96e 100644 --- a/examples/nmap_task.py +++ b/examples/nmap_task.py @@ -7,14 +7,19 @@ def mycallback(nmaptask): nmaptask = nmap_proc.current_task if nmaptask: - print("Task {0} ({1}): ETC: {2} DONE: {3}%".format(nmaptask.name, - nmaptask.status, - nmaptask.etc, - nmaptask.progress)) + print( + "Task {0} ({1}): ETC: {2} DONE: {3}%".format( + nmaptask.name, + nmaptask.status, + nmaptask.etc, + nmaptask.progress + ) + ) -nmap_proc = NmapProcess(targets="scanme.nmap.org", - options="-sV", - event_callback=mycallback) + +nmap_proc = NmapProcess( + targets="scanme.nmap.org", options="-sV", event_callback=mycallback +) nmap_proc.run() print(nmap_proc.stdout) print(nmap_proc.stderr) diff --git a/examples/nmap_task_bg.py b/examples/nmap_task_bg.py index af26533..5b640b2 100644 --- a/examples/nmap_task_bg.py +++ b/examples/nmap_task_bg.py @@ -8,10 +8,14 @@ while nmap_proc.is_running(): nmaptask = nmap_proc.current_task if nmaptask: - print("Task {0} ({1}): ETC: {2} DONE: {3}%".format(nmaptask.name, - nmaptask.status, - nmaptask.etc, - nmaptask.progress)) + print( + "Task {0} ({1}): ETC: {2} DONE: {3}%".format( + nmaptask.name, + nmaptask.status, + nmaptask.etc, + nmaptask.progress + ) + ) print("rc: {0} output: {1}".format(nmap_proc.rc, nmap_proc.summary)) print(nmap_proc.stdout) print(nmap_proc.stderr) diff --git a/examples/os_fingerprint.py b/examples/os_fingerprint.py index d4dcaf7..718aa87 100644 --- a/examples/os_fingerprint.py +++ b/examples/os_fingerprint.py @@ -3,7 +3,7 @@ from libnmap.parser import NmapParser -rep = NmapParser.parse_fromfile('libnmap/test/files/os_scan6.xml') +rep = NmapParser.parse_fromfile("libnmap/test/files/os_scan6.xml") print("{0}/{1} hosts up".format(rep.hosts_up, rep.hosts_total)) for _host in rep.hosts: @@ -11,7 +11,7 @@ print("{0} {1}".format(_host.address, " ".join(_host.hostnames))) if _host.os_fingerprinted: print("OS Fingerprint:") - msg = '' + msg = "" for osm in _host.os.osmatches: print("Found Match:{0} ({1}%)".format(osm.name, osm.accuracy)) for osc in osm.osclasses: diff --git a/examples/proc_async.py b/examples/proc_async.py index 40beef4..868f808 100644 --- a/examples/proc_async.py +++ b/examples/proc_async.py @@ -8,8 +8,11 @@ nmap_proc = NmapProcess(targets="scanme.nmap.org", options="-sT") nmap_proc.run_background() while nmap_proc.is_running(): - print("Nmap Scan running: ETC: {0} DONE: {1}%".format(nmap_proc.etc, - nmap_proc.progress)) + print( + "Nmap Scan running: ETC: {0} DONE: {1}%".format( + nmap_proc.etc, nmap_proc.progress + ) + ) sleep(2) print("rc: {0} output: {1}".format(nmap_proc.rc, nmap_proc.summary)) diff --git a/examples/proc_nmap_like.py b/examples/proc_nmap_like.py index c57f731..bd85cfd 100644 --- a/examples/proc_nmap_like.py +++ b/examples/proc_nmap_like.py @@ -24,9 +24,11 @@ def do_scan(targets, options): # print scan results from a nmap report def print_scan(nmap_report): - print("Starting Nmap {0} ( http://nmap.org ) at {1}".format( - nmap_report.version, - nmap_report.started)) + print( + "Starting Nmap {0} ( http://nmap.org ) at {1}".format( + nmap_report.version, nmap_report.started + ) + ) for host in nmap_report.hosts: if len(host.hostnames): @@ -34,18 +36,14 @@ def print_scan(nmap_report): else: tmp_host = host.address - print("Nmap scan report for {0} ({1})".format( - tmp_host, - host.address)) + print("Nmap scan report for {0} ({1})".format(tmp_host, host.address)) print("Host is {0}.".format(host.status)) print(" PORT STATE SERVICE") for serv in host.services: pserv = "{0:>5s}/{1:3s} {2:12s} {3}".format( - str(serv.port), - serv.protocol, - serv.state, - serv.service) + str(serv.port), serv.protocol, serv.state, serv.service + ) if len(serv.banner): pserv += " ({0})".format(serv.banner) print(pserv) diff --git a/libnmap/__init__.py b/libnmap/__init__.py index 431a582..2ecebe8 100644 --- a/libnmap/__init__.py +++ b/libnmap/__init__.py @@ -1,8 +1,8 @@ # -*- coding: utf-8 -*- -__author__ = 'Ronald Bister, Mike Boutillier' -__credits__ = ['Ronald Bister', 'Mike Boutillier'] -__maintainer__ = 'Ronald Bister' -__email__ = 'mini.pelle@gmail.com' -__license__ = 'CC-BY' -__version__ = '0.6.1' +__author__ = "Ronald Bister, Mike Boutillier" +__credits__ = ["Ronald Bister", "Mike Boutillier"] +__maintainer__ = "Ronald Bister" +__email__ = "mini.pelle@gmail.com" +__license__ = "CC-BY" +__version__ = "0.6.1" diff --git a/libnmap/diff.py b/libnmap/diff.py index 4a958f5..152a9a5 100644 --- a/libnmap/diff.py +++ b/libnmap/diff.py @@ -9,6 +9,7 @@ class DictDiffer(object): (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 @@ -23,12 +24,20 @@ 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])) + 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])) + return ( + set( + o for o in self.intersect + if self.past_dict[o] == self.current_dict[o] + ) + ) class NmapDiff(DictDiffer): @@ -54,6 +63,7 @@ class NmapDiff(DictDiffer): 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: @@ -72,11 +82,12 @@ def __init__(self, nmap_obj1, nmap_obj2): 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())) + return ( + "added: [{0}] -- changed: [{1}] -- " + "unchanged: [{2}] -- removed [{3}]".format( + self.added(), self.changed(), self.unchanged(), self.removed() + ) + ) class NmapDiffException(Exception): diff --git a/libnmap/objects/__init__.py b/libnmap/objects/__init__.py index 7432109..b7f24e4 100644 --- a/libnmap/objects/__init__.py +++ b/libnmap/objects/__init__.py @@ -4,4 +4,4 @@ from libnmap.objects.host import NmapHost from libnmap.objects.service import NmapService -__all__ = ['NmapReport', 'NmapHost', 'NmapService'] +__all__ = ["NmapReport", "NmapHost", "NmapService"] diff --git a/libnmap/objects/cpe.py b/libnmap/objects/cpe.py index 3ab82cd..8b6e497 100644 --- a/libnmap/objects/cpe.py +++ b/libnmap/objects/cpe.py @@ -9,12 +9,21 @@ class CPE(object): :todo: interpret CPE string and provide appropriate API """ + def __init__(self, cpestring): self._cpestring = cpestring - zk = ['cpe', 'part', 'vendor', 'product', 'version', - 'update', 'edition', 'language'] - self._cpedict = dict((k, '') for k in zk) - splitup = cpestring.split(':') + zk = [ + "cpe", + "part", + "vendor", + "product", + "version", + "update", + "edition", + "language", + ] + self._cpedict = dict((k, "") for k in zk) + splitup = cpestring.split(":") self._cpedict.update(dict(zip(zk, splitup))) @property @@ -38,58 +47,58 @@ def get_part(self): """ Returns the cpe part (/o, /h, /a) """ - return self._cpedict['part'] + return self._cpedict["part"] def get_vendor(self): """ Returns the vendor name """ - return self._cpedict['vendor'] + return self._cpedict["vendor"] def get_product(self): """ Returns the product name """ - return self._cpedict['product'] + return self._cpedict["product"] def get_version(self): """ Returns the version of the cpe """ - return self._cpedict['version'] + return self._cpedict["version"] def get_update(self): """ Returns the update version """ - return self._cpedict['update'] + return self._cpedict["update"] def get_edition(self): """ Returns the cpe edition """ - return self._cpedict['edition'] + return self._cpedict["edition"] def get_language(self): """ Returns the cpe language """ - return self._cpedict['language'] + return self._cpedict["language"] def is_application(self): """ Returns True if cpe describes an application """ - return (self.get_part() == '/a') + return self.get_part() == "/a" def is_hardware(self): """ Returns True if cpe describes a hardware """ - return (self.get_part() == '/h') + return self.get_part() == "/h" def is_operating_system(self): """ Returns True if cpe describes an operating system """ - return (self.get_part() == '/o') + return self.get_part() == "/o" diff --git a/libnmap/objects/host.py b/libnmap/objects/host.py index 671f6f4..de6a8ea 100644 --- a/libnmap/objects/host.py +++ b/libnmap/objects/host.py @@ -8,8 +8,17 @@ 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): + + 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 @@ -31,8 +40,8 @@ def __init__(self, starttime='', endtime='', address=None, status=None, 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']) + if "os" in self._extras: + self.os = NmapOSFingerprint(self._extras["os"]) self._osfingerprinted = True else: self.os = NmapOSFingerprint({}) @@ -42,16 +51,16 @@ def __init__(self, starttime='', endtime='', address=None, status=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 '' + 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): @@ -65,8 +74,8 @@ def __eq__(self, other): :return: boolean """ rval = False - if(self.__class__ == other.__class__ and self.id == other.id): - rval = (self.changed(other) == 0) + if self.__class__ == other.__class__ and self.id == other.id: + rval = self.changed(other) == 0 return rval def __ne__(self, other): @@ -80,8 +89,8 @@ def __ne__(self, other): :return: boolean """ rval = True - if(self.__class__ == other.__class__ and self.id == other.id): - rval = (self.changed(other) > 0) + if self.__class__ == other.__class__ and self.id == other.id: + rval = self.changed(other) > 0 return rval def __repr__(self): @@ -89,19 +98,24 @@ 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) + 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)))) + return ( + hash(self.status) + ^ hash(self.address) + ^ hash(frozenset(self._services)) + ^ hash(frozenset(" ".join(self._hostnames))) + ) def changed(self, other): """ @@ -146,16 +160,16 @@ def address(self, addrdict): :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'] + 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._main_address = self._ipv4_addr or self._ipv6_addr or "" self._address = addrdict @property @@ -165,7 +179,7 @@ def ipv4(self): :return: IPv4 address as a string """ - return self._ipv4_addr or '' + return self._ipv4_addr or "" @property def mac(self): @@ -174,7 +188,7 @@ def mac(self): :return: MAC address as a string """ - return self._mac_addr or '' + return self._mac_addr or "" @property def vendor(self): @@ -183,7 +197,7 @@ def vendor(self): :return: string (vendor) of empty string if no vendor defined """ - return self._vendor or '' + return self._vendor or "" @property def ipv6(self): @@ -192,7 +206,7 @@ def ipv6(self): :return: IPv6 address as a string """ - return self._ipv6_addr or '' + return self._ipv6_addr or "" @property def status(self): @@ -201,7 +215,7 @@ def status(self): :return: string """ - return self._status['state'] + return self._status["state"] @status.setter def status(self, statusdict): @@ -222,7 +236,7 @@ def is_up(self): :return: bool """ rval = False - if self.status == 'up': + if self.status == "up": rval = True return rval @@ -260,18 +274,21 @@ def get_open_ports(self): :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']) + return ([ + (p.port, p.protocol) for p in self._services if p.state == "open" + ]) - def get_service(self, portno, protocol='tcp'): + 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] + 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 @@ -328,7 +345,7 @@ def os_fingerprint(self): :return: string """ - rval = '' + rval = "" if self.os is not None: rval = "\n".join(self.os.fingerprints) return rval @@ -343,7 +360,7 @@ def os_ports_used(self): """ rval = [] try: - rval = self._extras['os']['ports_used'] + rval = self._extras["os"]["ports_used"] except (KeyError, TypeError): pass return rval @@ -356,9 +373,9 @@ def tcpsequence(self): return: string """ - rval = '' + rval = "" try: - rval = self._extras['tcpsequence']['difficulty'] + rval = self._extras["tcpsequence"]["difficulty"] except (KeyError, TypeError): pass return rval @@ -370,9 +387,9 @@ def ipsequence(self): :return: string """ - rval = '' + rval = "" try: - rval = self._extras['ipidsequence']['class'] + rval = self._extras["ipidsequence"]["class"] except (KeyError, TypeError): pass return rval @@ -386,7 +403,7 @@ def uptime(self): """ rval = 0 try: - rval = int(self._extras['uptime']['seconds']) + rval = int(self._extras["uptime"]["seconds"]) except (KeyError, TypeError): pass return rval @@ -398,9 +415,9 @@ def lastboot(self): :return: string """ - rval = '' + rval = "" try: - rval = self._extras['uptime']['lastboot'] + rval = self._extras["uptime"]["lastboot"] except (KeyError, TypeError): pass return rval @@ -414,7 +431,7 @@ def distance(self): """ rval = 0 try: - rval = int(self._extras['distance']['value']) + rval = int(self._extras["distance"]["value"]) except (KeyError, TypeError): pass return rval @@ -428,7 +445,7 @@ def scripts_results(self): """ rval = {} try: - rval = self._extras['hostscript'] + rval = self._extras["hostscript"] except (KeyError, TypeError): pass return rval @@ -450,12 +467,12 @@ def extraports_state(self): :return: dict with keys 'state' and 'count' or None """ - _xtrports = self._extras.get('extraports', None) + _xtrports = self._extras.get("extraports", None) if _xtrports is None: return None - return {'state': _xtrports['state'], 'count': _xtrports['count']} + return {"state": _xtrports["state"], "count": _xtrports["count"]} @property def extraports_reasons(self): @@ -465,12 +482,12 @@ def extraports_reasons(self): :return: array of dict containing keys 'state' and 'count' or None """ - r = self._extras.get('extraports', {}) + r = self._extras.get("extraports", {}) if r is None: return None - return r.get('reasons', None) + return r.get("reasons", None) def get_dict(self): """ @@ -480,12 +497,20 @@ def get_dict(self): :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)}) + 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 def diff(self, other): diff --git a/libnmap/objects/os.py b/libnmap/objects/os.py index cc795a7..32abf70 100644 --- a/libnmap/objects/os.py +++ b/libnmap/objects/os.py @@ -10,11 +10,12 @@ class OSFPPortUsed(object): 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'] + 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") @@ -59,25 +60,29 @@ class NmapOSMatch(object): 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): + _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'] + 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']: + for _osclass in osmatch_dict["osclasses"]: try: _osclassobj = NmapOSClass(_osclass) - except: - raise Exception("Could not create NmapOSClass object") + except Exception as e: + em = "Could not create NmapOSClass object: {0}".format(e) + raise Exception(em) self._osclasses.append(_osclassobj) except KeyError: pass @@ -157,27 +162,30 @@ class NmapOSClass(object): 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): + _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._vendor = _osclass["vendor"] + self._osfamily = _osclass["osfamily"] + self._accuracy = _osclass["accuracy"] - self._osgen = '' - self._type = '' + self._osgen = "" + self._type = "" # optional data - if 'osgen' in _osclass: - self._osgen = _osclass['osgen'] - if 'type' in _osclass: - self._type = _osclass['type'] + if "osgen" in _osclass: + self._osgen = _osclass["osgen"] + if "type" in _osclass: + self._type = _osclass["type"] self._cpelist = [] - for _cpe in osclass_dict['cpe']: + for _cpe in osclass_dict["cpe"]: self._cpelist.append(CPE(_cpe)) @property @@ -263,29 +271,30 @@ class NmapOSFingerprint(object): 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']: + 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']: + 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']: + 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) @@ -315,13 +324,17 @@ def _add_dummy_osmatch(self, osclass_obj): 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': []} + _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) @@ -362,8 +375,11 @@ def ports_used(self): return self.__ports_used def osmatch(self, min_accuracy=90): - warnings.warn("NmapOSFingerprint.osmatch is deprecated: " - "use NmapOSFingerprint.osmatches", DeprecationWarning) + warnings.warn( + "NmapOSFingerprint.osmatch is deprecated: " + "use NmapOSFingerprint.osmatches", + DeprecationWarning, + ) os_array = [] for _osmatch in self.__osmatches: if _osmatch.accuracy >= min_accuracy: @@ -371,17 +387,18 @@ def osmatch(self, min_accuracy=90): return os_array def osclass(self, min_accuracy=90): - warnings.warn("NmapOSFingerprint.osclass() is deprecated: " - "use NmapOSFingerprint.osclasses() if applicable", - DeprecationWarning) + warnings.warn( + "NmapOSFingerprint.osclass() is deprecated: " + "use NmapOSFingerprint.osclasses() if applicable", + DeprecationWarning, + ) os_array = [] - for osmatch_entry in self.osmatches(): + 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) + oclass.type, oclass.vendor, oclass.osfamily + ) os_array.append(_ftstr) return os_array @@ -396,5 +413,5 @@ def __repr__(self): rval = "" for _osmatch in self.osmatches: rval += "\r\n{0}".format(_osmatch) - rval += "Fingerprints: ".format(self.fingerprint) + rval += "Fingerprints: {0}".format(self.fingerprint) return rval diff --git a/libnmap/objects/report.py b/libnmap/objects/report.py index 60a97a1..fb9644c 100644 --- a/libnmap/objects/report.py +++ b/libnmap/objects/report.py @@ -20,6 +20,7 @@ class NmapReport(object): 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. @@ -77,24 +78,24 @@ def started(self): """ rval = -1 try: - s_start = self._nmaprun['start'] + s_start = self._nmaprun["start"] rval = int(s_start) - except(KeyError, TypeError, ValueError): + except (KeyError, TypeError, ValueError): pass return rval @property def startedstr(self): """ - Accessor returning a human readable string of when the + Accessor returning a human readable string of when the scan was started :return: string """ - rval = '' + rval = "" try: - rval = self._nmaprun['startstr'] - except(KeyError, TypeError, ValueError): + rval = self._nmaprun["startstr"] + except (KeyError, TypeError, ValueError): pass return rval @@ -105,7 +106,7 @@ def commandline(self): :return: string """ - return self._nmaprun['args'] + return self._nmaprun["args"] @property def version(self): @@ -115,7 +116,7 @@ def version(self): :return: string """ - return self._nmaprun['version'] + return self._nmaprun["version"] @property def xmlversion(self): @@ -125,7 +126,7 @@ def xmlversion(self): :return: string """ - return self._nmaprun['xmloutputversion'] + return self._nmaprun["xmloutputversion"] @property def scan_type(self): @@ -135,21 +136,21 @@ def scan_type(self): :return: string """ - return self._scaninfo.get('type') + return self._scaninfo.get("type") @property def numservices(self): """ - Accessor returning the number of services the + Accessor returning the number of services the scan attempted to enumerate. :return: integer """ rval = -1 try: - s_numsvcs = self._scaninfo.get('numservices') + s_numsvcs = self._scaninfo.get("numservices") rval = int(s_numsvcs) - except(KeyError, TypeError, ValueError): + except (KeyError, TypeError, ValueError): pass return rval @@ -189,8 +190,8 @@ def endtime(self): """ rval = -1 try: - rval = int(self._runstats['finished']['time']) - except(KeyError, TypeError, ValueError): + rval = int(self._runstats["finished"]["time"]) + except (KeyError, TypeError, ValueError): pass return rval @@ -202,10 +203,10 @@ def endtimestr(self): :return: string """ - rval = '' + rval = "" try: - rval = self._runstats['finished']['timestr'] - except(KeyError, TypeError, ValueError): + rval = self._runstats["finished"]["timestr"] + except (KeyError, TypeError, ValueError): pass return rval @@ -217,18 +218,22 @@ def summary(self): :return: string """ - rval = '' + rval = "" try: - rval = self._runstats['finished']['summary'] - except(KeyError, TypeError): + 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)) + 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 @@ -240,7 +245,7 @@ def elapsed(self): """ rval = -1 try: - s_elapsed = self._runstats['finished']['elapsed'] + s_elapsed = self._runstats["finished"]["elapsed"] rval = float(s_elapsed) except (KeyError, TypeError, ValueError): rval = -1 @@ -256,7 +261,7 @@ def hosts_up(self): """ rval = -1 try: - s_up = self._runstats['hosts']['up'] + s_up = self._runstats["hosts"]["up"] rval = int(s_up) except (KeyError, TypeError, ValueError): rval = -1 @@ -272,7 +277,7 @@ def hosts_down(self): """ rval = -1 try: - s_down = self._runstats['hosts']['down'] + s_down = self._runstats["hosts"]["down"] rval = int(s_down) except (KeyError, TypeError, ValueError): rval = -1 @@ -287,7 +292,7 @@ def hosts_total(self): """ rval = -1 try: - s_total = self._runstats['hosts']['total'] + s_total = self._runstats["hosts"]["total"] rval = int(s_total) except (KeyError, TypeError, ValueError): rval = -1 @@ -300,17 +305,19 @@ def get_raw_data(self): :return: dict :todo: deprecate. get rid of this uglyness. """ - raw_data = {'_nmaprun': self._nmaprun, - '_scaninfo': self._scaninfo, - '_hosts': self._hosts, - '_runstats': self._runstats} + 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'] + self._nmaprun = raw_data["_nmaprun"] + self._scaninfo = raw_data["_scaninfo"] + self._hosts = raw_data["_hosts"] + self._runstats = raw_data["_runstats"] def is_consistent(self): """ @@ -322,9 +329,11 @@ def is_consistent(self): """ 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): + _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 @@ -335,16 +344,26 @@ def get_dict(self): :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}) + 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 @@ -364,12 +383,13 @@ def __eq__(self, other): :return: boolean """ rval = False - if(self.__class__ == other.__class__ and self.id == other.id): + 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 - ) + rval = ( + len(diffobj.changed()) == 0 + and len(diffobj.added()) == 0 + and len(diffobj.removed()) == 0 + ) return rval def __ne__(self, other): @@ -382,17 +402,19 @@ def __ne__(self, other): :return: boolean """ rval = True - if(self.__class__ == other.__class__ and self.id == other.id): + 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 - ) + 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) + self.__class__.__name__, + self.started, + self.hosts_up, + self.hosts_total + ) diff --git a/libnmap/objects/service.py b/libnmap/objects/service.py index 11d4726..6a691de 100644 --- a/libnmap/objects/service.py +++ b/libnmap/objects/service.py @@ -11,8 +11,16 @@ class NmapService(object): 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): + + def __init__( + self, + portid, + protocol="tcp", + state=None, + service=None, + owner=None, + service_extras=None, + ): """ Constructor @@ -39,32 +47,32 @@ def __init__(self, portid, protocol='tcp', state=None, self._service = service if service is not None else {} self._cpelist = [] - if 'cpelist' in self._service: - for _cpe in self._service['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._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 = '' + 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 "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'] + 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: @@ -80,8 +88,8 @@ def __eq__(self, other): :return: boolean """ rval = False - if(self.__class__ == other.__class__ and self.id == other.id): - rval = (self.changed(other) == 0) + if self.__class__ == other.__class__ and self.id == other.id: + rval = self.changed(other) == 0 return rval def __ne__(self, other): @@ -94,21 +102,29 @@ def __ne__(self, other): :return: boolean """ rval = True - if(self.__class__ == other.__class__ and self.id == other.id): - rval = (self.changed(other) > 0) + 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) + 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)) + return ( + hash(self.port) + ^ hash(self.protocol) + ^ hash(self.state) + ^ hash(self.reason) + ^ hash(self.service) + ^ hash(self.banner) + ) def changed(self, other): """ @@ -145,7 +161,7 @@ def state(self): :return: string """ - return self._state['state'] if 'state' in self._state else None + return self._state["state"] if "state" in self._state else None @property def reason(self): @@ -181,7 +197,7 @@ def service(self): :return: string or empty """ - return self._service['name'] if 'name' in self._service else '' + return self._service["name"] if "name" in self._service else "" @property def service_dict(self): @@ -198,7 +214,7 @@ def open(self): :return: boolean """ - return 'state' in self._state and self._state['state'] == 'open' + return "state" in self._state and self._state["state"] == "open" @property def owner(self): @@ -215,18 +231,24 @@ def banner(self): :return: string """ - notrelevant = ['name', 'method', 'conf', 'cpelist', - 'servicefp', 'tunnel'] - relevant = ['product', 'version', 'extrainfo'] - b = '' + 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": + 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]) + 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]) + b += "{0}: {1} ".format(mkey, self._service[mkey]) return b.rstrip() @property @@ -248,7 +270,7 @@ def scripts_results(self): """ scripts_dict = None try: - scripts_dict = self._service_extras['scripts'] + scripts_dict = self._service_extras["scripts"] except (KeyError, TypeError): pass return scripts_dict @@ -293,10 +315,15 @@ def get_dict(self): :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}) + 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): """ diff --git a/libnmap/parser.py b/libnmap/parser.py index a595578..427eb52 100644 --- a/libnmap/parser.py +++ b/libnmap/parser.py @@ -10,7 +10,7 @@ class NmapParser(object): @classmethod - def parse(cls, nmap_data=None, data_type='XML', incomplete=False): + def parse(cls, nmap_data=None, data_type="XML", incomplete=False): """ Generic class method of NmapParser class. @@ -40,9 +40,11 @@ def parse(cls, nmap_data=None, data_type='XML', incomplete=False): 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.") + raise NmapParserException( + "Unknown data type provided. " + "Please check documentation for " + "supported data types." + ) return nmapobj @classmethod @@ -77,28 +79,33 @@ def _parse_xml(cls, nmap_data=None, incomplete=False): """ if not nmap_data: - raise NmapParserException("No report data to parse: please " - "provide a valid XML nmap report") + 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") + 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") + except Exception as e: + emsg = "Wrong XML structure: cannot parse data: {0}".format(e) + raise NmapParserException(emsg) nmapobj = None - if root.tag == 'nmaprun': + if root.tag == "nmaprun": nmapobj = cls._parse_xml_report(root) - elif root.tag == 'host': + elif root.tag == "host": nmapobj = cls._parse_xml_host(root) - elif root.tag == 'ports': + elif root.tag == "ports": nmapobj = cls._parse_xml_ports(root) - elif root.tag == 'port': + elif root.tag == "port": nmapobj = cls._parse_xml_port(root) else: raise NmapParserException("Unpexpected data structure for XML " @@ -117,21 +124,25 @@ def _parse_xml_report(cls, root=None): :return: NmapReport object """ - nmap_scan = {'_nmaprun': {}, '_scaninfo': {}, - '_hosts': [], '_runstats': {}} + 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) + 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) + 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, @@ -160,14 +171,18 @@ def parse_fromstring(cls, nmap_data, data_type="XML", incomplete=False): """ if not isinstance(nmap_data, str): - raise NmapParserException("bad argument type for " - "xarse_fromstring(): should be a string") + 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): + 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. @@ -188,7 +203,7 @@ def parse_fromfile(cls, nmap_report_path, """ try: - with open(nmap_report_path, 'r') as fileobj: + with open(nmap_report_path, "r") as fileobj: fdata = fileobj.read() rval = cls.parse(fdata, data_type, incomplete) except IOError: @@ -210,31 +225,37 @@ def parse_fromdict(cls, rdict): """ nreport = {} - if list(rdict.keys())[0] == '__NmapReport__': - r = rdict['__NmapReport__'] - nreport['_runstats'] = r['_runstats'] - nreport['_scaninfo'] = r['_scaninfo'] - nreport['_nmaprun'] = r['_nmaprun'] + 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']: + 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) + 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 + nreport["_hosts"] = hlist nmapobj = NmapReport(nreport) return nmapobj @@ -273,45 +294,53 @@ def _parse_xml_host(cls, scanhost_data): _status = {} _addresses = [] _host_extras = {} - extra_tags = ['uptime', 'distance', 'tcpsequence', - 'ipidsequence', 'tcptssequence', 'times'] + extra_tags = [ + "uptime", + "distance", + "tcpsequence", + "ipidsequence", + "tcptssequence", + "times", + ] for xh in xelement: - if xh.tag == 'hostnames': + if xh.tag == "hostnames": for hostname in cls.__parse_hostnames(xh): _hostnames.append(hostname) - elif xh.tag == 'ports': + elif xh.tag == "ports": ports_dict = cls._parse_xml_ports(xh) - for port in ports_dict['ports']: + for port in ports_dict["ports"]: _services.append(port) - _host_extras['extraports'] = ports_dict['extraports'] - elif xh.tag == 'status': + _host_extras["extraports"] = ports_dict["extraports"] + elif xh.tag == "status": _status = cls.__format_attributes(xh) - elif xh.tag == 'address': + elif xh.tag == "address": _addresses.append(cls.__format_attributes(xh)) - elif xh.tag == 'os': + elif xh.tag == "os": _os_extra = cls.__parse_os_fingerprint(xh) - _host_extras.update({'os': _os_extra}) - elif xh.tag == 'hostscript': + _host_extras.update({"os": _os_extra}) + elif xh.tag == "hostscript": _host_scripts = cls.__parse_host_scripts(xh) - _host_extras.update({'hostscript': _host_scripts}) + _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) + _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 @@ -328,8 +357,8 @@ def __parse_hostnames(cls, scanhostnames_data): xelement = cls.__format_element(scanhostnames_data) hostnames = [] for hname in xelement: - if hname.tag == 'hostname': - hostnames.append(hname.get('name')) + if hname.tag == "hostname": + hostnames.append(hname.get("name")) return hostnames @classmethod @@ -349,14 +378,14 @@ def _parse_xml_ports(cls, scanports_data): xelement = cls.__format_element(scanports_data) - rdict = {'ports': [], 'extraports': None} + rdict = {"ports": [], "extraports": None} for xservice in xelement: - if xservice.tag == 'port': + if xservice.tag == "port": nport = cls._parse_xml_port(xservice) - rdict['ports'].append(nport) - elif xservice.tag == 'extraports': + rdict["ports"].append(nport) + elif xservice.tag == "extraports": extraports = cls.__parse_extraports(xservice) - rdict['extraports'] = extraports + rdict["extraports"] = extraports # else: # print "struct port unknown attr: %s value: %s" % # (h.tag, h.get(h.tag)) @@ -380,8 +409,8 @@ def _parse_xml_port(cls, scanport_data): 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 + _portid = _port["portid"] if "portid" in _port else None + _protocol = _port["protocol"] if "protocol" in _port else None _state = None _service = None @@ -389,28 +418,27 @@ def _parse_xml_port(cls, scanport_data): _service_scripts = [] _service_extras = {} for xport in xelement: - if xport.tag == 'state': + if xport.tag == "state": _state = cls.__format_attributes(xport) - elif xport.tag == 'service': + elif xport.tag == "service": _service = cls.__parse_service(xport) - elif xport.tag == 'owner': + elif xport.tag == "owner": _owner = cls.__format_attributes(xport) - elif xport.tag == 'script': + 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) + _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 @@ -421,10 +449,10 @@ def __parse_service(cls, xserv): _service = cls.__format_attributes(xserv) _cpelist = [] for _servnode in xserv: - if _servnode.tag == 'cpe': + if _servnode.tag == "cpe": _cpe_string = _servnode.text _cpelist.append(_cpe_string) - _service['cpelist'] = _cpelist + _service["cpelist"] = _cpelist return _service @classmethod @@ -439,21 +467,20 @@ def __parse_extraports(cls, extraports_data): :return: python dict with following keys: state, count, reason """ - rdict = {'state': '', 'count': '', 'reasons': []} + 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 + 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': + if xelt.tag == "extrareasons": extrareasons_dict = cls.__format_attributes(xelt) - rdict['reasons'].append(extrareasons_dict) + rdict["reasons"].append(extrareasons_dict) return rdict - @classmethod def __parse_script_table(cls, script_table): """ @@ -466,27 +493,26 @@ def __parse_script_table(cls, script_table): """ tdict = {} for telem in script_table: - tkey = telem.get('key') - if telem.tag == 'elem': + tkey = telem.get("key") + if telem.tag == "elem": if tkey in tdict: - if not instance(tdict[tkey], list): - tdict[tkey] = [tdict[tkey], ] + if not isinstance(tdict[tkey], list): + tdict[tkey] = [tdict[tkey]] tdict[tkey].append(telem.text) else: tdict[tkey] = telem.text - elif telem.tag == 'table': + elif telem.tag == "table": stdict = cls.__parse_script_table(telem) - + # Handle duplicate table keys if tkey in tdict: if not isinstance(tdict[tkey], list): - tdict[tkey] = [tdict[tkey], ] + tdict[tkey] = [tdict[tkey]] tdict[tkey].append(stdict) else: tdict[tkey] = stdict return tdict - @classmethod def __parse_script(cls, script_data): """ @@ -502,19 +528,19 @@ def __parse_script(cls, 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': + if script_elem.tag == "elem": + _elt_dict.update({script_elem.get("key"): script_elem.text}) + elif script_elem.tag == "table": tdict = cls.__parse_script_table(script_elem) # Handle duplicate table keys - skey = script_elem.get('key') + 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] = [_elt_dict[skey]] _elt_dict[skey].append(tdict) else: _elt_dict[skey] = tdict - _script_dict['elements'] = _elt_dict + _script_dict["elements"] = _elt_dict return _script_dict @classmethod @@ -532,7 +558,7 @@ def __parse_host_scripts(cls, scripts_data): """ _host_scripts = [] for xscript in scripts_data: - if xscript.tag == 'script': + if xscript.tag == "script": _script_dict = cls.__parse_script(xscript) _host_scripts.append(_script_dict) return _host_scripts @@ -559,23 +585,23 @@ def __parse_os_fingerprint(cls, os_data): for xos in xelement: # for nmap xml version < 1.04, osclass is not # embedded in osmatch - if xos.tag == 'osclass': + if xos.tag == "osclass": os_class_proba = cls.__parse_osclass(xos) os_class_probability.append(os_class_proba) - elif xos.tag == 'osmatch': + elif xos.tag == "osmatch": os_match_proba = cls.__parse_osmatch(xos) os_match_probability.append(os_match_proba) - elif xos.tag == 'portused': + elif xos.tag == "portused": os_portused = cls.__format_attributes(xos) os_ports_used.append(os_portused) - elif xos.tag == 'osfingerprint': + 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 + rdict["osmatches"] = os_match_probability + rdict["osclasses"] = os_class_probability + rdict["ports_used"] = os_ports_used + rdict["osfingerprints"] = os_fingerprints return rdict @@ -593,12 +619,12 @@ def __parse_osmatch(cls, osmatch_data): """ rdict = {} xelement = cls.__format_element(osmatch_data) - rdict['osmatch'] = cls.__format_attributes(xelement) - rdict['osclasses'] = [] + rdict["osmatch"] = cls.__format_attributes(xelement) + rdict["osclasses"] = [] for xmltag in xelement: - if xmltag.tag == 'osclass': + if xmltag.tag == "osclass": _osclass_dict = cls.__parse_osclass(xmltag) - rdict['osclasses'].append(_osclass_dict) + rdict["osclasses"].append(_osclass_dict) else: exmsg = "Unexcepted node in : {0}".format(xmltag.tag) raise NmapParserException(exmsg) @@ -618,12 +644,12 @@ def __parse_osclass(cls, osclass_data): """ rdict = {} xelement = cls.__format_element(osclass_data) - rdict['osclass'] = cls.__format_attributes(xelement) - rdict['cpe'] = [] + rdict["osclass"] = cls.__format_attributes(xelement) + rdict["cpe"] = [] for xmltag in xelement: - if xmltag.tag == 'cpe': + if xmltag.tag == "cpe": _cpe_string = xmltag.text - rdict['cpe'].append(_cpe_string) + rdict["cpe"].append(_cpe_string) else: exmsg = "Unexcepted node in : {0}".format(xmltag.tag) raise NmapParserException(exmsg) @@ -645,7 +671,7 @@ def __parse_runstats(cls, scanrunstats_data): rdict = {} for xmltag in xelement: - if xmltag.tag in ['finished', 'hosts']: + if xmltag.tag in ["finished", "hosts"]: rdict[xmltag.tag] = cls.__format_attributes(xmltag) else: exmsg = "Unexcepted node in : {0}".format(xmltag.tag) @@ -669,15 +695,19 @@ def __format_element(elt_data): 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)) + except Exception as e: + raise NmapParserException( + "Error while trying " + "to instanciate XML Element from " + "string {0} - {1}".format(elt_data, e) + ) elif ET.iselement(elt_data): xelement = elt_data else: - raise NmapParserException("Error while trying to parse supplied " - "data: unsupported format") + raise NmapParserException( + "Error while trying to parse supplied " + "data: unsupported format" + ) return xelement @staticmethod @@ -695,17 +725,21 @@ def __format_attributes(elt_data): 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") + 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 NmapParserException( + "Error while trying to build-up " + "element attributes: empty " + "attribute {0}".format(dkey) + ) + except Exception: raise return rval diff --git a/libnmap/plugins/backendplugin.py b/libnmap/plugins/backendplugin.py index 224cdd0..e14a23e 100644 --- a/libnmap/plugins/backendplugin.py +++ b/libnmap/plugins/backendplugin.py @@ -6,9 +6,10 @@ class NmapBackendPlugin(object): Abstract class showing to the minimal implementation for a plugin All subclass MUST at least implement the following methods """ + def __init__(self): - self.dbname = 'nmapdb' - self.store = 'reports' + self.dbname = "nmapdb" + self.store = "reports" def insert(self, NmapReport): """ diff --git a/libnmap/plugins/backendpluginFactory.py b/libnmap/plugins/backendpluginFactory.py index b840363..83494b3 100644 --- a/libnmap/plugins/backendpluginFactory.py +++ b/libnmap/plugins/backendpluginFactory.py @@ -9,6 +9,7 @@ class BackendPluginFactory(object): created via the static method create() ie : mybackend = BackendPluginFactory.create() """ + @classmethod def create(cls, plugin_name="mongodb", **kwargs): """Import the needed lib and return an object NmapBackendPlugin diff --git a/libnmap/plugins/es.py b/libnmap/plugins/es.py index d0359b1..c0c9d7e 100644 --- a/libnmap/plugins/es.py +++ b/libnmap/plugins/es.py @@ -12,9 +12,10 @@ class NmapElasticsearchPlugin(NmapBackendPlugin): This class enables the user to store and manipulate nmap reports \ in a elastic search db. """ + def __init__(self, index=None): if index is None: - self.index = "nmap.{0}".format(datetime.now().strftime('%Y-%m-%d')) + self.index = "nmap.{0}".format(datetime.now().strftime("%Y-%m-%d")) else: self.index = index self._esapi = Elasticsearch() @@ -28,13 +29,12 @@ def insert(self, report, doc_type=None): or None """ if doc_type is None: - doc_type = 'NmapReport' + doc_type = "NmapReport" j = json.dumps(report, cls=ReportEncoder) - res = self._esapi.index( - index=self.index, - doc_type=doc_type, - body=json.loads(j)) - rc = res['_id'] + res = self._esapi.index(index=self.index, + doc_type=doc_type, + body=json.loads(j)) + rc = res["_id"] return rc def delete(self, id): @@ -52,7 +52,7 @@ def get(self, id): """ res = self._esapi.get(index=self.index, doc_type="NmapReport", - id=id)['_source'] + id=id)["_source"] return res def getall(self, filter=None): @@ -60,8 +60,9 @@ def getall(self, filter=None): :return: collection of tuple (id,NmapReport) :param filter: Nice to have implement a filter capability """ - rsearch = self._esapi.search(index=self.index, - body={"query": {"match_all": {}}}) + rsearch = self._esapi.search( + index=self.index, body={"query": {"match_all": {}}} + ) print("--------------------") print(type(rsearch)) print(rsearch) diff --git a/libnmap/plugins/mongodb.py b/libnmap/plugins/mongodb.py index 657029d..6609760 100644 --- a/libnmap/plugins/mongodb.py +++ b/libnmap/plugins/mongodb.py @@ -17,6 +17,7 @@ class NmapMongodbPlugin(NmapBackendPlugin): {'plugin_name': "mongodb"} this dict may reeive all the param MongoClient() support """ + def __init__(self, dbname=None, store=None, **kwargs): NmapBackendPlugin.__init__(self) if dbname is not None: @@ -35,8 +36,9 @@ def insert(self, report): j = json.dumps(report, cls=ReportEncoder) try: oid = self.collection.insert(json.loads(j)) - except: - raise Exception("Failed to insert nmap object in MongoDB") + except Exception as e: + em = "Failed to insert nmap object in MongoDB: {0}".format(e) + raise Exception(em) return str(oid) def get(self, str_report_id=None): @@ -51,12 +53,12 @@ def get(self, str_report_id=None): if isinstance(rid, ObjectId): # get a specific report by mongo's id - resultset = self.collection.find({'_id': rid}) + resultset = self.collection.find({"_id": rid}) if resultset.count() == 1: # search by id means only one in the iterator record = resultset[0] # remove mongo's id to recreate the NmapReport Obj - del record['_id'] + del record["_id"] nmapreport = NmapParser.parse_fromdict(record) return nmapreport @@ -67,8 +69,8 @@ def getall(self, dict_filter=None): nmapreportlist = [] resultset = self.collection.find() for report in resultset: - oid = report['_id'] - del report['_id'] + oid = report["_id"] + del report["_id"] nmapreport = NmapParser.parse_fromdict(report) nmapreportlist.append((oid, nmapreport)) return nmapreportlist @@ -80,6 +82,6 @@ def delete(self, report_id=None): :return: dict document with result or None """ if report_id is not None and isinstance(report_id, str): - return self.collection.remove({'_id': ObjectId(report_id)}) + return self.collection.remove({"_id": ObjectId(report_id)}) else: - return self.collection.remove({'_id': report_id}) + return self.collection.remove({"_id": report_id}) diff --git a/libnmap/plugins/s3.py b/libnmap/plugins/s3.py index 5805daa..a090e2f 100644 --- a/libnmap/plugins/s3.py +++ b/libnmap/plugins/s3.py @@ -26,6 +26,7 @@ class NmapS3Plugin(NmapBackendPlugin): """ This plugin save the reports on S3 and compatible. """ + def __init__(self, **kwargs): """ - create the conn object @@ -56,20 +57,20 @@ def __init__(self, **kwargs): NmapBackendPlugin.__init__(self) try: calling_format = OrdinaryCallingFormat() - if 'bucket' not in kwargs: - self.bucket_name = ''.join( - [kwargs['aws_access_key_id'].lower(), - "_nmapreport"]) + if "bucket" not in kwargs: + self.bucket_name = "".join( + [kwargs["aws_access_key_id"].lower(), "_nmapreport"] + ) else: - self.bucket_name = kwargs['bucket'] - del kwargs['bucket'] - kwargs['calling_format'] = calling_format + self.bucket_name = kwargs["bucket"] + del kwargs["bucket"] + kwargs["calling_format"] = calling_format self.conn = S3Connection(**kwargs) self.bucket = self.conn.lookup(self.bucket_name) if self.bucket is None: self.bucket = self.conn.create_bucket(self.bucket_name) - except: - raise + except Exception as e: + raise Exception(e) def insert(self, report): """ @@ -87,8 +88,9 @@ def insert(self, report): mykey.key = str(oid) strjsonnmapreport = json.dumps(report, cls=ReportEncoder) mykey.set_contents_from_string(strjsonnmapreport) - except: - raise Exception("Failed to add nmap object in s3 bucket") + except Exception as e: + em = "Failed to add nmap object in s3 bucket: {0}".format(e) + raise Exception(em) return str(oid) def get(self, str_report_id=None): diff --git a/libnmap/plugins/sql.py b/libnmap/plugins/sql.py index 57f1415..afab24a 100644 --- a/libnmap/plugins/sql.py +++ b/libnmap/plugins/sql.py @@ -44,27 +44,27 @@ class NmapSqlPlugin(NmapBackendPlugin): mybackend.getall() mybackend.get(1) """ + class Reports(Base): """ Embeded class for ORM map NmapReport to a simple three column table """ - __tablename__ = 'reports' - id = Column('report_id', Integer, primary_key=True) - inserted = Column('inserted', DateTime(), default='now') - report_json = Column('report_json', LargeBinary()) + __tablename__ = "reports" + + id = Column("report_id", Integer, primary_key=True) + inserted = Column("inserted", DateTime(), default="now") + report_json = Column("report_json", LargeBinary()) def __init__(self, obj_NmapReport): self.inserted = datetime.fromtimestamp(obj_NmapReport.endtime) - dumped_json = json.dumps(obj_NmapReport, - cls=ReportEncoder) - self.report_json = bytes(dumped_json.encode('UTF-8')) + dumped_json = json.dumps(obj_NmapReport, cls=ReportEncoder) + self.report_json = bytes(dumped_json.encode("UTF-8")) def decode(self): - json_decoded = self.report_json.decode('utf-8') - nmap_report_obj = json.loads(json_decoded, - cls=ReportDecoder) + json_decoded = self.report_json.decode("utf-8") + nmap_report_obj = json.loads(json_decoded, cls=ReportDecoder) return nmap_report_obj def __init__(self, **kwargs): @@ -91,16 +91,16 @@ def __init__(self, **kwargs): self.url = None self.Session = sessionmaker() - if 'url' not in kwargs: + if "url" not in kwargs: raise ValueError - self.url = kwargs['url'] - del kwargs['url'] + self.url = kwargs["url"] + del kwargs["url"] try: self.engine = create_engine(self.url, **kwargs) Base.metadata.create_all(bind=self.engine, checkfirst=True) self.Session.configure(bind=self.engine) - except: - raise + except Exception as e: + raise(e) def insert(self, nmap_report): """ @@ -130,8 +130,8 @@ def get(self, report_id=None): if report_id is None: raise ValueError sess = self.Session() - our_report = ( - sess.query(NmapSqlPlugin.Reports).filter_by(id=report_id).first()) + orp = sess.query(NmapSqlPlugin.Reports).filter_by(id=report_id) + our_report = orp.first() sess.close() return our_report.decode() if our_report else None @@ -143,9 +143,9 @@ def getall(self): """ sess = self.Session() nmapreportList = [] - for report in ( - sess.query(NmapSqlPlugin.Reports). - order_by(NmapSqlPlugin.Reports.inserted)): + for report in sess.query(NmapSqlPlugin.Reports).order_by( + NmapSqlPlugin.Reports.inserted + ): nmapreportList.append((report.id, report.decode())) sess.close() return nmapreportList @@ -162,8 +162,8 @@ def delete(self, report_id=None): raise ValueError nb_line = 0 sess = self.Session() - nb_line = sess.query(NmapSqlPlugin.Reports).\ - filter_by(id=report_id).delete() + rpt = sess.query(NmapSqlPlugin.Reports).filter_by(id=report_id) + nb_line = rpt.delete() sess.commit() sess.close() return nb_line diff --git a/libnmap/process.py b/libnmap/process.py index 161aee5..184dfee 100644 --- a/libnmap/process.py +++ b/libnmap/process.py @@ -8,15 +8,14 @@ from xml.dom import pulldom import warnings import platform + try: import pwd except ImportError: pass -__all__ = [ - 'NmapProcess' -] +__all__ = ["NmapProcess"] class NmapTask(object): @@ -32,13 +31,13 @@ class NmapTask(object): is built during scan execution """ - def __init__(self, name, starttime=0, extrainfo=''): + 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.status = "started" self.starttime = starttime self.endtime = 0 self.extrainfo = extrainfo @@ -56,8 +55,14 @@ class NmapProcess(Thread): 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): + def __init__( + self, + targets="127.0.0.1", + options="-sT", + event_callback=None, + safe_mode=True, + fqp=None, + ): """ Constructor of NmapProcess class. @@ -85,11 +90,22 @@ def __init__(self, targets="127.0.0.1", """ Thread.__init__(self) - unsafe_opts = set(['-oG', '-oN', '-iL', '-oA', '-oS', '-oX', - '--iflist', '--resume', '--stylesheet', - '--datadir']) + 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' + self.__is_windows = platform.system() == "Windows" if fqp: if os.path.isfile(fqp) and os.access(fqp, os.X_OK): self.__nmap_binary = fqp @@ -101,31 +117,35 @@ def __init__(self, targets="127.0.0.1", 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") + em = "nmap is not installed or could not be found in system path" + raise EnvironmentError(1, em) if isinstance(targets, str): - self.__nmap_targets = targets.replace(" ", "").split(',') + 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") + 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.__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.DONE, + self.READY, + self.RUNNING, + self.CANCELLED, + self.FAILED) = range(5) self._run_init() def _run_init(self): @@ -136,12 +156,12 @@ def _run_init(self): self.__state = self.RUNNING self.__starttime = 0 self.__endtime = 0 - self.__version = '' - self.__elapsed = '' - self.__summary = '' - self.__stdout = '' - self.__stderr = '' - self.__current_task = '' + self.__version = "" + self.__elapsed = "" + self.__summary = "" + self.__stdout = "" + self.__stderr = "" + self.__current_task = "" self.__nmap_tasks = {} def _whereis(self, program): @@ -156,11 +176,13 @@ def _whereis(self, program): :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))): + 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 @@ -171,13 +193,15 @@ def get_command_line(self): :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))) + 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'): + 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 @@ -193,15 +217,20 @@ def sudo_run(self, run_as='root'): try: pwd.getpwnam(sudo_user).pw_uid except KeyError: - _exmsg = ("Username {0} does not exists. Please supply" - " a valid username".format(run_as)) + _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") + 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() @@ -209,7 +238,7 @@ def sudo_run(self, run_as='root'): return rc - def sudo_run_background(self, run_as='root'): + 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. @@ -226,15 +255,20 @@ def sudo_run_background(self, run_as='root'): try: pwd.getpwnam(sudo_user).pw_uid except KeyError: - _exmsg = ("Username {0} does not exists. Please supply" - " a valid username".format(run_as)) + _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") + 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() @@ -253,22 +287,29 @@ def run(self): return: return code from nmap execution """ self._run_init() - _tmp_cmdline = self.__build_windows_cmdline() if self.__is_windows \ + _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.__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") + 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, ''): + for streamline in iter(self.__nmap_proc.stdout.readline, ""): self.__stdout += streamline evnt = self.__process_event(streamline) if self.__nmap_event_callback and evnt: @@ -312,8 +353,11 @@ def has_terminated(self): :return: True if nmap process is not running anymore. """ - return (self.state == self.DONE or self.state == self.FAILED or - self.state == self.CANCELLED) + return ( + self.state == self.DONE + or self.state == self.FAILED + or self.state == self.CANCELLED + ) def has_failed(self): """ @@ -362,55 +406,65 @@ def __process_event(self, eventdata): 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()): + 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 + 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()): + 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 + 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()): + 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 + 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 + 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 + 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: + except Exception: pass return rval @@ -433,7 +487,7 @@ def command(self): :return: string """ - return self.__nmap_command_line or '' + return self.__nmap_command_line or "" @property def targets(self): @@ -484,8 +538,11 @@ def endtime(self): :return: string. Unix timestamp """ - warnings.warn("data collected from finished events are deprecated." - "Use NmapParser.parse()", DeprecationWarning) + warnings.warn( + "data collected from finished events are deprecated." + "Use NmapParser.parse()", + DeprecationWarning, + ) return self.__endtime @property @@ -495,8 +552,11 @@ def elapsed(self): :return: string """ - warnings.warn("data collected from finished events are deprecated." - "Use NmapParser.parse()", DeprecationWarning) + warnings.warn( + "data collected from finished events are deprecated." + "Use NmapParser.parse()", + DeprecationWarning, + ) return self.__elapsed @property @@ -506,8 +566,11 @@ def summary(self): :return: string """ - warnings.warn("data collected from finished events are deprecated." - "Use NmapParser.parse()", DeprecationWarning) + warnings.warn( + "data collected from finished events are deprecated." + "Use NmapParser.parse()", + DeprecationWarning, + ) return self.__summary @property @@ -599,13 +662,17 @@ 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) + 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, @@ -618,5 +685,6 @@ def mycallback(nmapscan=None): print("Error: {stderr}").format(stderr=nm.stderr) print("Result: {0}").format(nm.stdout) -if __name__ == '__main__': + +if __name__ == "__main__": main() diff --git a/libnmap/reportjson.py b/libnmap/reportjson.py index 9bf6526..057d727 100644 --- a/libnmap/reportjson.py +++ b/libnmap/reportjson.py @@ -10,16 +10,18 @@ class ReportEncoder(json.JSONEncoder): def default(self, obj): - otype = {'NmapHost': NmapHost, - 'NmapOSFingerprint': NmapOSFingerprint, - 'NmapOSMatch': NmapOSMatch, - 'NmapOSClass': NmapOSClass, - 'CPE': CPE, - 'OSFPPortUsed': OSFPPortUsed, - 'NmapService': NmapService, - 'NmapReport': NmapReport} + otype = { + "NmapHost": NmapHost, + "NmapOSFingerprint": NmapOSFingerprint, + "NmapOSMatch": NmapOSMatch, + "NmapOSClass": NmapOSClass, + "CPE": CPE, + "OSFPPortUsed": OSFPPortUsed, + "NmapService": NmapService, + "NmapReport": NmapReport, + } if isinstance(obj, tuple(otype.values())): - key = ('__{0}__').format(obj.__class__.__name__) + key = ("__{0}__").format(obj.__class__.__name__) return {key: obj.__dict__} return json.JSONEncoder.default(self, obj) diff --git a/libnmap/test/process-stressbox/check_fqp_nmap.py b/libnmap/test/process-stressbox/check_fqp_nmap.py index cfd9db2..163c520 100644 --- a/libnmap/test/process-stressbox/check_fqp_nmap.py +++ b/libnmap/test/process-stressbox/check_fqp_nmap.py @@ -21,9 +21,11 @@ def do_scan(targets, options, fqp=None): # print scan results from a nmap report def print_scan(nmap_report): - print("Starting Nmap {0} ( http://nmap.org ) at {1}".format( - nmap_report.version, - nmap_report.started)) + print( + "Starting Nmap {0} ( http://nmap.org ) at {1}".format( + nmap_report.version, nmap_report.started + ) + ) for host in nmap_report.hosts: if len(host.hostnames): @@ -31,18 +33,14 @@ def print_scan(nmap_report): else: tmp_host = host.address - print("Nmap scan report for {0} ({1})".format( - tmp_host, - host.address)) + print("Nmap scan report for {0} ({1})".format(tmp_host, host.address)) print("Host is {0}.".format(host.status)) print(" PORT STATE SERVICE") for serv in host.services: pserv = "{0:>5s}/{1:3s} {2:12s} {3}".format( - str(serv.port), - serv.protocol, - serv.state, - serv.service) + str(serv.port), serv.protocol, serv.state, serv.service + ) if len(serv.banner): pserv += " ({0})".format(serv.banner) print(pserv) diff --git a/libnmap/test/process-stressbox/multi_nmap_process.py b/libnmap/test/process-stressbox/multi_nmap_process.py index d861de3..d3b138d 100644 --- a/libnmap/test/process-stressbox/multi_nmap_process.py +++ b/libnmap/test/process-stressbox/multi_nmap_process.py @@ -1,24 +1,29 @@ #!/usr/bin/env python - + from libnmap.process import NmapProcess - + + def make_nmproc_obj(targets, options): return NmapProcess(targets=targets, options=options) - + + def start_all(nmprocs): for nmp in nmprocs: print("Starting scan for host {0}".format(nmp.targets)) nmp.run() - + + def summarize(nmprocs): for nmp in nmprocs: print("rc: {0} output: {1}".format(nmp.rc, len(nmp.stdout))) + nm_targets = [] -for h in range(20): nm_targets.append("localhost") +for h in range(20): + nm_targets.append("localhost") nm_opts = "-sT" - + nm_procs = [make_nmproc_obj(t, nm_opts) for t in nm_targets] start_all(nm_procs) - + summarize(nm_procs) diff --git a/libnmap/test/process-stressbox/multi_nmap_process_background.py b/libnmap/test/process-stressbox/multi_nmap_process_background.py index 1e2faef..5fea3d2 100644 --- a/libnmap/test/process-stressbox/multi_nmap_process_background.py +++ b/libnmap/test/process-stressbox/multi_nmap_process_background.py @@ -1,31 +1,41 @@ #!/usr/bin/env python - + from libnmap.process import NmapProcess from time import sleep - + + def make_nmproc_obj(targets, options): - return NmapProcess(targets=targets, options=options) - + return NmapProcess(targets=targets, options=options) + + def start_all_bg(nmprocs): - for nmp in nmprocs: nmp.run_background() - + for nmp in nmprocs: + nmp.run_background() + + def any_running(nmprocs): - return any([nmp.is_running() for nmp in nmprocs]) - + return any([nmp.is_running() for nmp in nmprocs]) + + def summarize(nmprocs): - for nmp in nmprocs: - print("rc: {0} output: {1} stdout len: {2}".format(nmp.rc, nmp.summary, len(nmp.stdout))) + for nmp in nmprocs: + print( + "rc: {0} output: {1} stdout len: {2}".format( + nmp.rc, nmp.summary, len(nmp.stdout) + ) + ) + nm_targets = [] for h in range(10): - nm_targets.append("localhost") + nm_targets.append("scanme.nmap.org") nm_opts = "-sT" - + nm_procs = [make_nmproc_obj(t, nm_opts) for t in nm_targets] start_all_bg(nm_procs) - + while any_running(nm_procs): print("Nmap Scan running...") sleep(2) - + summarize(nm_procs) diff --git a/libnmap/test/process-stressbox/proc_async.py b/libnmap/test/process-stressbox/proc_async.py index f588d2f..1f7a53d 100644 --- a/libnmap/test/process-stressbox/proc_async.py +++ b/libnmap/test/process-stressbox/proc_async.py @@ -9,10 +9,11 @@ while nmap_proc.is_running(): nmaptask = nmap_proc.current_task if nmaptask: - print("Task {0} ({1}): ETC: {2} DONE: {3}%".format(nmaptask.name, - nmaptask.status, - nmaptask.etc, - nmaptask.progress)) + print( + "Task {0} ({1}): ETC: {2} DONE: {3}%".format( + nmaptask.name, nmaptask.status, nmaptask.etc, nmaptask.progress + ) + ) sleep(0.5) print("rc: {0} output: {1}".format(nmap_proc.rc, nmap_proc.summary)) diff --git a/libnmap/test/process-stressbox/proc_nmap_like.py b/libnmap/test/process-stressbox/proc_nmap_like.py index 5399b66..1b428c1 100644 --- a/libnmap/test/process-stressbox/proc_nmap_like.py +++ b/libnmap/test/process-stressbox/proc_nmap_like.py @@ -20,9 +20,11 @@ def do_scan(targets, options): # print scan results from a nmap report def print_scan(nmap_report): - print("Starting Nmap {0} ( http://nmap.org ) at {1}".format( - nmap_report._nmaprun['version'], - nmap_report._nmaprun['startstr'])) + print( + "Starting Nmap {0} ( http://nmap.org ) at {1}".format( + nmap_report._nmaprun["version"], nmap_report._nmaprun["startstr"] + ) + ) for host in nmap_report.hosts: if len(host.hostnames): @@ -30,18 +32,14 @@ def print_scan(nmap_report): else: tmp_host = host.address - print("Nmap scan report for {0} ({1})".format( - tmp_host, - host.address)) + print("Nmap scan report for {0} ({1})".format(tmp_host, host.address)) print("Host is {0}.".format(host.status)) print(" PORT STATE SERVICE") for serv in host.services: pserv = "{0:>5s}/{1:3s} {2:12s} {3}".format( - str(serv.port), - serv.protocol, - serv.state, - serv.service) + str(serv.port), serv.protocol, serv.state, serv.service + ) if len(serv.banner): pserv += " ({0})".format(serv.banner) print(pserv) diff --git a/libnmap/test/process-stressbox/stop_scan.py b/libnmap/test/process-stressbox/stop_scan.py index 10c01bd..93906fd 100644 --- a/libnmap/test/process-stressbox/stop_scan.py +++ b/libnmap/test/process-stressbox/stop_scan.py @@ -9,10 +9,11 @@ while nmap_proc.is_running(): nmaptask = nmap_proc.current_task if nmaptask: - print("Task {0} ({1}): ETC: {2} DONE: {3}%".format(nmaptask.name, - nmaptask.status, - nmaptask.etc, - nmaptask.progress)) + print( + "Task {0} ({1}): ETC: {2} DONE: {3}%".format( + nmaptask.name, nmaptask.status, nmaptask.etc, nmaptask.progress + ) + ) sleep(3) nmap_proc.stop() diff --git a/libnmap/test/process-stressbox/stressback.py b/libnmap/test/process-stressbox/stressback.py index ee14edc..0a059fc 100644 --- a/libnmap/test/process-stressbox/stressback.py +++ b/libnmap/test/process-stressbox/stressback.py @@ -1,22 +1,28 @@ #!/usr/bin/env python - + from libnmap.process import NmapProcess from time import sleep - + + def make_nmproc_obj(targets, options): return NmapProcess(targets=targets, options=options) - + + def start_all_bg(nmprocs): - for nmp in nmprocs: nmp.run_background() - + for nmp in nmprocs: + nmp.run_background() + + def any_running(nmprocs): return any([nmp.is_running() for nmp in nmprocs]) - + + def summarize(nmprocs): for nmp in nmprocs: print("rc: {0} output: {1}".format(nmp.rc, len(nmp.stdout))) print(nmp.stdout) - + + nb_targets = 10 nm_target = "localhost" nm_opts = "-sP" @@ -24,8 +30,8 @@ def summarize(nmprocs): nm_targets = [nm_target for i in range(nb_targets)] nm_procs = [make_nmproc_obj(t, nm_opts) for t in nm_targets] start_all_bg(nm_procs) - + while any_running(nm_procs): sleep(5) - + summarize(nm_procs) diff --git a/libnmap/test/process-stressbox/stresstest.py b/libnmap/test/process-stressbox/stresstest.py index 6de045a..812ca0c 100644 --- a/libnmap/test/process-stressbox/stresstest.py +++ b/libnmap/test/process-stressbox/stresstest.py @@ -2,7 +2,7 @@ from libnmap.process import NmapProcess from libnmap.parser import NmapParser, NmapParserException -nm = NmapProcess('127.0.0.1', '-sP') +nm = NmapProcess("127.0.0.1", "-sP") rc = nm.run() if rc != 0: print("nmap scan failed: {0}".format(nm.stderr)) diff --git a/libnmap/test/test_backend_plugin_factory.py b/libnmap/test/test_backend_plugin_factory.py index 2573c67..8e956ab 100644 --- a/libnmap/test/test_backend_plugin_factory.py +++ b/libnmap/test/test_backend_plugin_factory.py @@ -1,165 +1,175 @@ #!/usr/bin/env python # -*- coding: utf-8 -*- -import unittest -import os -from libnmap.parser import NmapParser -from libnmap.plugins.backendplugin import NmapBackendPlugin -from libnmap.plugins.backendpluginFactory import BackendPluginFactory - - -class TestNmapBackendPlugin(unittest.TestCase): - """ - This testing class will tests each plugins - The following test need to be done : - - test the factory - - test all the method of the class NmapBackendPlugin: - - Verify implmented/notImplemented - - Verify the behaviour (ie insert must insert) - To support a new plugin or a new way to instanciate a plugin, add a dict - with the necessary parameter in the urls table define in setUp - All testcase must loop thru theses urls to validate a plugins - """ - def setUp(self): - fdir = os.path.dirname(os.path.realpath(__file__)) - self.flist_full = [{'file': "%s/%s" % (fdir, 'files/2_hosts.xml'), - 'hosts': 2}, - {'file': "%s/%s" % (fdir, 'files/1_hosts.xml'), - 'hosts': 1}, - {'file': "%s/%s" % (fdir, - 'files/1_hosts_banner_ports_notsyn.xml'), - 'hosts': 1}, - {'file': "%s/%s" % (fdir, - 'files/1_hosts_banner_ports.xml'), - 'hosts': 1}, - {'file': "%s/%s" % (fdir, - 'files/1_hosts_banner.xml'), - 'hosts': 1}, - {'file': "%s/%s" % (fdir, - 'files/2_hosts_version.xml'), - 'hosts': 2}, - {'file': "%s/%s" % (fdir, - 'files/2_tcp_hosts.xml'), - 'hosts': 2}, - {'file': "%s/%s" % (fdir, - 'files/1_hosts_nohostname.xml'), - 'hosts': 1}] - self.flist = self.flist_full - #build a list of NmapReport - self.reportList = [] - for testfile in self.flist: - fd = open(testfile['file'], 'r') - s = fd.read() - fd.close() - nrp = NmapParser.parse(s) - self.reportList.append(nrp) - - self.urls = [{'plugin_name': "mongodb"}, - #{'plugin_name':'sql','url':'sqlite://','echo':'debug'}, - {'plugin_name': 'sql', - 'url': 'sqlite:////tmp/reportdb.sql', - 'echo': False}, - {'plugin_name': 'sql', - #'url': 'mysql+mysqldb://root@localhost/poulet', (mySQL-Python not supporting python3) - 'url': 'mysql+pymysql://root@localhost/poulet', - 'echo': False}, - #Walrus - ###{'plugin_name': 's3', - ### 'aws_access_key_id': 'UU72FLVJCAYRATLXI70YH', - ### 'aws_secret_access_key': 'wFg7gP5YFHjVlxakw1g1uCC8UR2xVW5ax9ErZCut', - ### 'host':"walrus.ecc.eucalyptus.com", - ### 'path':'/services/Walrus', - ### 'port':8773, - ### 'is_secure':False, - ### 'bucket':"uu72flvjcayratlxi70yh_nmapreport33333", - ###}, - ####Walrus - # {'plugin_name': 's3', # disabled for now - # 'aws_access_key_id': 'UU72FLVJCAYRATLXI70YH', - # 'aws_secret_access_key': 'wFg7gP5YFHjVlxakw1g1uCC8UR2xVW5ax9ErZCut', - # 'host':"walrus.ecc.eucalyptus.com", - # 'path':'/services/Walrus', - # 'port':8773, - # 'is_secure':False, - # }, - #S3 - ###{'plugin_name': 's3', - ### 'aws_access_key_id': 'YOURKEY', - ### 'aws_secret_access_key': 'YOURPASSWKEY', - ###}, - ] - - def test_backend_factory(self): - """ test_factory BackendPluginFactory.create(**url) - Invoke factory and test that the object is of the right classes - """ - for url in self.urls: - backend = BackendPluginFactory.create(**url) - self.assertEqual(isinstance(backend, NmapBackendPlugin), True) - className = "Nmap%sPlugin" % url['plugin_name'].title() - self.assertEqual(backend.__class__.__name__, className, True) - - def test_backend_insert(self): - """ test_insert - best way to insert is to call save() of nmapreport :P - """ - for nrp in self.reportList: - for url in self.urls: - #create the backend factory object - backend = BackendPluginFactory.create(**url) - #save the report - returncode = nrp.save(backend) - #test return code - self.assertNotEqual(returncode, None) - - def test_backend_get(self): - """ test_backend_get - inset all report and save the returned id in a list - then get each id and create a new list of report - compare each report (assume eq) - """ - id_list = [] - result_list = [] - for url in self.urls: - backend = BackendPluginFactory.create(**url) - for nrp in self.reportList: - id_list.append(nrp.save(backend)) - for rep_id in id_list: - result_list.append(backend.get(rep_id)) - #print result_list[0] - #print self.reportList[0] - self.assertEqual(len(result_list), len(self.reportList)) - self.assertEqual((result_list), (self.reportList)) - id_list = [] - result_list = [] - - def test_backend_getall(self): - pass - - def test_backend_delete(self): - """ test_backend_delete - inset all report and save the returned id in a list - for each id remove the item and test if not present - """ - id_list = [] - result_list = [] - for url in self.urls: - backend = BackendPluginFactory.create(**url) - for nrp in self.reportList: - id_list.append(nrp.save(backend)) - for rep_id in id_list: - result_list.append(backend.delete(rep_id)) - self.assertEqual(backend.get(rep_id), None) - id_list = [] - result_list = [] - - -if __name__ == '__main__': - test_suite = ['test_backend_factory', - 'test_backend_insert', - 'test_backend_get', - 'test_backend_getall', - 'test_backend_delete' - ] - suite = unittest.TestSuite(map(TestNmapBackendPlugin, test_suite)) - test_result = unittest.TextTestRunner(verbosity=5).run(suite) +# import unittest +# import os +# from libnmap.parser import NmapParser +# from libnmap.plugins.backendplugin import NmapBackendPlugin +# from libnmap.plugins.backendpluginFactory import BackendPluginFactory +# +# +# class TestNmapBackendPlugin(unittest.TestCase): +# """ +# This testing class will tests each plugins +# The following test need to be done : +# - test the factory +# - test all the method of the class NmapBackendPlugin: +# - Verify implmented/notImplemented +# - Verify the behaviour (ie insert must insert) +# To support a new plugin or a new way to instanciate a plugin, add a dict +# with the necessary parameter in the urls table define in setUp +# All testcase must loop thru theses urls to validate a plugins +# """ +# def setUp(self): +# fdir = os.path.dirname(os.path.realpath(__file__)) +# self.flist_full = [ +# { +# 'file': "{0}/{1}".format(fdir, "files/2_hosts.xml"), +# 'hosts': 2 +# }, +# { +# 'file': "{0}/{1}".format(fdir, "files/1_hosts.xml"), +# 'hosts': 1 +# }, +# { +# 'file': "{0}/{1}".format( +# fdir, +# "files/1_hosts_banner_ports_notsyn.xml" +# ), +# 'hosts': 1 +# }, +# { +# 'file': "{0}/{1}".format( +# fdir, +# 'files/1_hosts_banner_ports.xml' +# ), +# 'hosts': 1 +# }, +# { +# 'file': "{0}/{1}".format( +# fdir, +# 'files/1_hosts_banner.xml' +# ), +# 'hosts': 1 +# }, +# { +# 'file': "{0}/{1}".format( +# fdir, +# 'files/2_hosts_version.xml' +# ), +# 'hosts': 2 +# }, +# { +# 'file': "{0}/{1}".format( +# fdir, +# 'files/2_tcp_hosts.xml' +# ), +# 'hosts': 2 +# }, +# { +# 'file': "{0}/{1}".format( +# fdir, +# 'files/1_hosts_nohostname.xml' +# ), +# 'hosts': 1 +# } +# ] +# self.flist = self.flist_full +# # build a list of NmapReport +# self.reportList = [] +# for testfile in self.flist: +# fd = open(testfile['file'], 'r') +# s = fd.read() +# fd.close() +# nrp = NmapParser.parse(s) +# self.reportList.append(nrp) +# +# self.urls = [ +# { +# 'plugin_name': "mongodb" +# }, +# { +# 'plugin_name': 'sql', +# 'url': 'sqlite:////tmp/reportdb.sql', +# 'echo': False +# }, +# { +# 'plugin_name': 'sql', +# 'url': 'mysql+pymysql://root@localhost/poulet', +# 'echo': False +# } +# ] +# +# def test_backend_factory(self): +# """ test_factory BackendPluginFactory.create(**url) +# Invoke factory and test that the object is of the right classes +# """ +# for url in self.urls: +# backend = BackendPluginFactory.create(**url) +# self.assertEqual(isinstance(backend, NmapBackendPlugin), True) +# className = "Nmap%sPlugin" % url['plugin_name'].title() +# self.assertEqual(backend.__class__.__name__, className, True) +# +# def test_backend_insert(self): +# """ test_insert +# best way to insert is to call save() of nmapreport :P +# """ +# for nrp in self.reportList: +# for url in self.urls: +# # create the backend factory object +# backend = BackendPluginFactory.create(**url) +# # save the report +# returncode = nrp.save(backend) +# # test return code +# self.assertNotEqual(returncode, None) +# +# def test_backend_get(self): +# """ test_backend_get +# inset all report and save the returned id in a list +# then get each id and create a new list of report +# compare each report (assume eq) +# """ +# id_list = [] +# result_list = [] +# for url in self.urls: +# backend = BackendPluginFactory.create(**url) +# for nrp in self.reportList: +# id_list.append(nrp.save(backend)) +# for rep_id in id_list: +# result_list.append(backend.get(rep_id)) +# self.assertEqual(len(result_list), len(self.reportList)) +# self.assertEqual((result_list), (self.reportList)) +# id_list = [] +# result_list = [] +# +# def test_backend_getall(self): +# pass +# +# def test_backend_delete(self): +# """ test_backend_delete +# inset all report and save the returned id in a list +# for each id remove the item and test if not present +# """ +# id_list = [] +# result_list = [] +# for url in self.urls: +# backend = BackendPluginFactory.create(**url) +# for nrp in self.reportList: +# id_list.append(nrp.save(backend)) +# for rep_id in id_list: +# result_list.append(backend.delete(rep_id)) +# self.assertEqual(backend.get(rep_id), None) +# id_list = [] +# result_list = [] +# +# +# if __name__ == '__main__': +# test_suite = [ +# 'test_backend_factory', +# 'test_backend_insert', +# 'test_backend_get', +# 'test_backend_getall', +# 'test_backend_delete' +# ] +# suite = unittest.TestSuite(map(TestNmapBackendPlugin, test_suite)) +# test_result = unittest.TextTestRunner(verbosity=5).run(suite) diff --git a/libnmap/test/test_cpe.py b/libnmap/test/test_cpe.py index 06d1ae2..4497a5c 100644 --- a/libnmap/test/test_cpe.py +++ b/libnmap/test/test_cpe.py @@ -7,24 +7,26 @@ class TestNmapFP(unittest.TestCase): def setUp(self): - self.cpelist = ['cpe:/a:apache:http_server:2.2.22', - 'cpe:/a:heimdal:kerberos', - 'cpe:/a:openbsd:openssh:5.9p1', - 'cpe:/o:apple:iphone_os:5', - 'cpe:/o:apple:mac_os_x:10.8', - 'cpe:/o:apple:mac_os_x', - 'cpe:/o:linux:linux_kernel:2.6.13', - 'cpe:/o:linux:linux_kernel', - 'cpe:/o:microsoft:windows_7', - 'cpe:/o:microsoft:windows_7::-:professional', - 'cpe:/o:microsoft:windows_7::sp1', - 'cpe:/o:microsoft:windows', - 'cpe:/o:microsoft:windows_server_2008::beta3', - 'cpe:/o:microsoft:windows_server_2008', - 'cpe:/o:microsoft:windows_server_2008::sp1', - 'cpe:/o:microsoft:windows_vista::-', - 'cpe:/o:microsoft:windows_vista::sp1', - 'cpe:/o:microsoft:windows_vista::sp2'] + self.cpelist = [ + "cpe:/a:apache:http_server:2.2.22", + "cpe:/a:heimdal:kerberos", + "cpe:/a:openbsd:openssh:5.9p1", + "cpe:/o:apple:iphone_os:5", + "cpe:/o:apple:mac_os_x:10.8", + "cpe:/o:apple:mac_os_x", + "cpe:/o:linux:linux_kernel:2.6.13", + "cpe:/o:linux:linux_kernel", + "cpe:/o:microsoft:windows_7", + "cpe:/o:microsoft:windows_7::-:professional", + "cpe:/o:microsoft:windows_7::sp1", + "cpe:/o:microsoft:windows", + "cpe:/o:microsoft:windows_server_2008::beta3", + "cpe:/o:microsoft:windows_server_2008", + "cpe:/o:microsoft:windows_server_2008::sp1", + "cpe:/o:microsoft:windows_vista::-", + "cpe:/o:microsoft:windows_vista::sp1", + "cpe:/o:microsoft:windows_vista::sp2", + ] def test_cpe(self): apa = CPE(self.cpelist[0]) @@ -32,31 +34,40 @@ def test_cpe(self): self.assertTrue(apa.is_application()) self.assertFalse(apa.is_hardware()) self.assertFalse(apa.is_operating_system()) - win = CPE(self.cpelist[12]) - self.assertEqual(win.get_vendor(), 'microsoft') - self.assertEqual(win.get_product(), 'windows_server_2008') - self.assertEqual(win.get_version(), '') - self.assertEqual(win.get_update(), 'beta3') - self.assertEqual(win.get_edition(), '') - self.assertEqual(win.get_language(), '') + self.assertEqual(win.get_vendor(), "microsoft") + self.assertEqual(win.get_product(), "windows_server_2008") + self.assertEqual(win.get_version(), "") + self.assertEqual(win.get_update(), "beta3") + self.assertEqual(win.get_edition(), "") + self.assertEqual(win.get_language(), "") def test_full_cpe(self): - cpestr = 'cpe:/a:mozilla:firefox:2.0::osx:es-es' - resdict = { 'part':'/a', 'vendor':"mozilla", 'product':"firefox", 'version':"2.0", 'update':'', 'edition':"osx", 'language':'es-es' } + cpestr = "cpe:/a:mozilla:firefox:2.0::osx:es-es" + resdict = { + "part": "/a", + "vendor": "mozilla", + "product": "firefox", + "version": "2.0", + "update": "", + "edition": "osx", + "language": "es-es", + } ocpe = CPE(cpestr) - objdict = {'part': ocpe.get_part(), - 'vendor': ocpe.get_vendor(), - 'product': ocpe.get_product(), - 'version': ocpe.get_version(), - 'update': ocpe.get_update(), - 'language': ocpe.get_language(), - 'edition': ocpe.get_edition() + objdict = { + "part": ocpe.get_part(), + "vendor": ocpe.get_vendor(), + "product": ocpe.get_product(), + "version": ocpe.get_version(), + "update": ocpe.get_update(), + "language": ocpe.get_language(), + "edition": ocpe.get_edition(), } self.assertEqual(objdict, resdict) -if __name__ == '__main__': - test_suite = ['test_cpe', 'test_full_cpe'] + +if __name__ == "__main__": + test_suite = ["test_cpe", "test_full_cpe"] suite = unittest.TestSuite(map(TestNmapFP, test_suite)) test_result = unittest.TextTestRunner(verbosity=2).run(suite) diff --git a/libnmap/test/test_fp.py b/libnmap/test/test_fp.py index ec30bcb..7f5b069 100644 --- a/libnmap/test/test_fp.py +++ b/libnmap/test/test_fp.py @@ -9,118 +9,249 @@ class TestNmapFP(unittest.TestCase): def setUp(self): fdir = os.path.dirname(os.path.realpath(__file__)) - self.flist_full = [{ 'file': "%s/%s" % (fdir, 'files/1_os_banner_scripts.xml'), 'os': 1}, - { 'file': "%s/%s" % (fdir, 'files/2_hosts_version.xml'), 'os': 1}, - { 'file': "%s/%s" % (fdir, 'files/1_hosts_banner_ports_notsyn.xml'), 'os': 0}, - { 'file': "%s/%s" % (fdir, 'files/1_hosts_banner.xml'), 'os': 0}, - { 'file': "%s/%s" % (fdir, 'files/1_hosts_down.xml'), 'os': 0}] + self.flist_full = [ + {"file": "%s/%s" % (fdir, "files/1_os_banner_scripts.xml"), "os": 1}, + {"file": "%s/%s" % (fdir, "files/2_hosts_version.xml"), "os": 1}, + { + "file": "%s/%s" % (fdir, "files/1_hosts_banner_ports_notsyn.xml"), + "os": 0, + }, + {"file": "%s/%s" % (fdir, "files/1_hosts_banner.xml"), "os": 0}, + {"file": "%s/%s" % (fdir, "files/1_hosts_down.xml"), "os": 0}, + ] self.flist = self.flist_full - self.flist_os = {'nv6': {'file': "%s/%s" % (fdir, 'files/full_sudo6.xml'), 'os': 0}, - 'fullscan': { 'file': "%s/%s" % (fdir, 'files/fullscan.xml'), 'os': 0}, - 'nv5': { 'file': "%s/%s" % (fdir, 'files/os_scan5.xml'), 'os': 0} + self.flist_os = { + "nv6": {"file": "%s/%s" % (fdir, "files/full_sudo6.xml"), "os": 0}, + "fullscan": {"file": "%s/%s" % (fdir, "files/fullscan.xml"), "os": 0}, + "nv5": {"file": "%s/%s" % (fdir, "files/os_scan5.xml"), "os": 0}, } self.fos_class_probabilities = "{0}/{1}".format(fdir, "files/test_osclass.xml") def test_fp(self): for file_e in self.flist_full: - rep = NmapParser.parse_fromfile(file_e['file']) + rep = NmapParser.parse_fromfile(file_e["file"]) for _host in rep.hosts: - if file_e['os'] != 0: + if file_e["os"] != 0: self.assertTrue(_host.os_fingerprinted) - elif file_e['os'] == 0: + elif file_e["os"] == 0: self.assertFalse(_host.os_fingerprinted) else: raise Exception def test_osclasses_new(self): - oclines = [[[{'type': 'general purpose', 'accuracy': 100, 'vendor': 'Apple', 'osfamily': 'Mac OS X', 'osgen': '10.8.X'}, - {'type': 'phone', 'accuracy': 100, 'vendor': 'Apple', 'osfamily': 'iOS', 'osgen': '5.X'}, - {'type': 'media device', 'accuracy': 100, 'vendor': 'Apple', 'osfamily': 'iOS', 'osgen': '5.X'}]], - [ - [{'type': 'general purpose', 'accuracy': 100, 'vendor': 'Microsoft', 'osfamily': 'Windows', 'osgen': '2008'}], - [{'type': 'general purpose', 'accuracy': 100, 'vendor': 'Microsoft', 'osfamily': 'Windows', 'osgen': '7'}], - [{'type': 'phone', 'accuracy': 100, 'vendor': 'Microsoft', 'osfamily': 'Windows', 'osgen': 'Phone'}], - [{'type': 'general purpose', 'accuracy': 100, 'vendor': 'Microsoft', 'osfamily': 'Windows', 'osgen': 'Vista'}, - {'type': 'general purpose', 'accuracy': 100, 'vendor': 'Microsoft', 'osfamily': 'Windows', 'osgen': '2008'}, - {'type': 'general purpose', 'accuracy': 100, 'vendor': 'Microsoft', 'osfamily': 'Windows', 'osgen': '7'}], - [{'type': 'general purpose', 'accuracy': 100, 'vendor': 'Microsoft', 'osfamily': 'Windows', 'osgen': 'Vista'}, - {'type': 'general purpose', 'accuracy': 100, 'vendor': 'Microsoft', 'osfamily': 'Windows', 'osgen': '7'}, - {'type': 'general purpose', 'accuracy': 100, 'vendor': 'Microsoft', 'osfamily': 'Windows', 'osgen': '2008'}]] + oclines = [ + [ + [ + { + "type": "general purpose", + "accuracy": 100, + "vendor": "Apple", + "osfamily": "Mac OS X", + "osgen": "10.8.X", + }, + { + "type": "phone", + "accuracy": 100, + "vendor": "Apple", + "osfamily": "iOS", + "osgen": "5.X", + }, + { + "type": "media device", + "accuracy": 100, + "vendor": "Apple", + "osfamily": "iOS", + "osgen": "5.X", + }, + ] + ], + [ + [ + { + "type": "general purpose", + "accuracy": 100, + "vendor": "Microsoft", + "osfamily": "Windows", + "osgen": "2008", + } + ], + [ + { + "type": "general purpose", + "accuracy": 100, + "vendor": "Microsoft", + "osfamily": "Windows", + "osgen": "7", + } + ], + [ + { + "type": "phone", + "accuracy": 100, + "vendor": "Microsoft", + "osfamily": "Windows", + "osgen": "Phone", + } + ], + [ + { + "type": "general purpose", + "accuracy": 100, + "vendor": "Microsoft", + "osfamily": "Windows", + "osgen": "Vista", + }, + { + "type": "general purpose", + "accuracy": 100, + "vendor": "Microsoft", + "osfamily": "Windows", + "osgen": "2008", + }, + { + "type": "general purpose", + "accuracy": 100, + "vendor": "Microsoft", + "osfamily": "Windows", + "osgen": "7", + }, + ], + [ + { + "type": "general purpose", + "accuracy": 100, + "vendor": "Microsoft", + "osfamily": "Windows", + "osgen": "Vista", + }, + { + "type": "general purpose", + "accuracy": 100, + "vendor": "Microsoft", + "osfamily": "Windows", + "osgen": "7", + }, + { + "type": "general purpose", + "accuracy": 100, + "vendor": "Microsoft", + "osfamily": "Windows", + "osgen": "2008", + }, + ], + ], ] - rep = NmapParser.parse_fromfile(self.flist_os['nv6']['file']) + rep = NmapParser.parse_fromfile(self.flist_os["nv6"]["file"]) hlist = [] hlist.append(rep.hosts.pop()) hlist.append(rep.hosts.pop()) - i=0 - j=0 - k=0 + i = 0 + j = 0 + k = 0 for h in hlist: for om in h.os.osmatches: for oc in om.osclasses: - tdict = {'type': oc.type, 'accuracy': oc.accuracy, 'vendor': oc.vendor, 'osfamily': oc.osfamily, 'osgen': oc.osgen} + tdict = { + "type": oc.type, + "accuracy": oc.accuracy, + "vendor": oc.vendor, + "osfamily": oc.osfamily, + "osgen": oc.osgen, + } self.assertEqual(oclines[i][j][k], tdict) - k+=1 - j+=1 - k=0 - j=0 - i+=1 + k += 1 + j += 1 + k = 0 + j = 0 + i += 1 def test_osmatches_new(self): - rep = NmapParser.parse_fromfile(self.flist_os['nv6']['file']) + rep = NmapParser.parse_fromfile(self.flist_os["nv6"]["file"]) hlist = [] hlist.append(rep.hosts.pop()) hlist.append(rep.hosts.pop()) - baseline = [[{'line': 6014, 'accuracy': 100, 'name': 'Apple Mac OS X 10.8 - 10.8.1 (Mountain Lion) (Darwin 12.0.0 - 12.1.0) or iOS 5.0.1'}], - [{'line': 52037, 'accuracy': 100, 'name': 'Microsoft Windows Server 2008 Beta 3'}, - {'line': 52938, 'accuracy': 100, 'name': 'Microsoft Windows 7 Professional'}, - {'line': 54362, 'accuracy': 100, 'name': 'Microsoft Windows Phone 7.5'}, - {'line': 54897, 'accuracy': 100, 'name': 'Microsoft Windows Vista SP0 or SP1, Windows Server 2008 SP1, or Windows 7'}, - {'line': 55210, 'accuracy': 100, 'name': 'Microsoft Windows Vista SP2, Windows 7 SP1, or Windows Server 2008'}] - ] - i=0 - j=0 + baseline = [ + [ + { + "line": 6014, + "accuracy": 100, + "name": "Apple Mac OS X 10.8 - 10.8.1 (Mountain Lion) (Darwin 12.0.0 - 12.1.0) or iOS 5.0.1", + } + ], + [ + { + "line": 52037, + "accuracy": 100, + "name": "Microsoft Windows Server 2008 Beta 3", + }, + { + "line": 52938, + "accuracy": 100, + "name": "Microsoft Windows 7 Professional", + }, + {"line": 54362, "accuracy": 100, "name": "Microsoft Windows Phone 7.5"}, + { + "line": 54897, + "accuracy": 100, + "name": "Microsoft Windows Vista SP0 or SP1, Windows Server 2008 SP1, or Windows 7", + }, + { + "line": 55210, + "accuracy": 100, + "name": "Microsoft Windows Vista SP2, Windows 7 SP1, or Windows Server 2008", + }, + ], + ] + i = 0 + j = 0 for h in hlist: for om in h.os.osmatches: - tdict = {'line': om.line, 'accuracy': om.accuracy, 'name': om.name} + tdict = {"line": om.line, "accuracy": om.accuracy, "name": om.name} self.assertEqual(baseline[i][j], tdict) - j+=1 - j=0 - i+=1 + j += 1 + j = 0 + i += 1 def test_osmatches_old(self): - rep = NmapParser.parse_fromfile(self.flist_os['nv5']['file']) + rep = NmapParser.parse_fromfile(self.flist_os["nv5"]["file"]) h1 = rep.hosts[4] - h1osmatches = [{'line': -1, 'accuracy': 95, 'name': 'general purpose:Linux:Linux'}, - {'line': -1, 'accuracy': 90, 'name': 'WAP:Gemtek:embedded'}, - {'line': -1, 'accuracy': 89, 'name': 'general purpose:Nokia:Linux'}, - {'line': -1, 'accuracy': 88, 'name': 'webcam:AXIS:Linux'}] + h1osmatches = [ + {"line": -1, "accuracy": 95, "name": "general purpose:Linux:Linux"}, + {"line": -1, "accuracy": 90, "name": "WAP:Gemtek:embedded"}, + {"line": -1, "accuracy": 89, "name": "general purpose:Nokia:Linux"}, + {"line": -1, "accuracy": 88, "name": "webcam:AXIS:Linux"}, + ] - j=0 + j = 0 for om in h1.os.osmatches: - tdict = {'line': om.line, 'accuracy': om.accuracy, 'name': om.name} + tdict = {"line": om.line, "accuracy": om.accuracy, "name": om.name} self.assertEqual(h1osmatches[j], tdict) - j+=1 + j += 1 def test_fpv6(self): fpval = "OS:SCAN(V=6.40-2%E=4%D=5/9%OT=88%CT=%CU=%PV=Y%DS=0%DC=L%G=N%TM=536BFF2F%P=x\nOS:86_64-apple-darwin10.8.0)SEQ(SP=F9%GCD=1%ISR=103%TI=RD%TS=A)OPS(O1=M3FD8\nOS:NW4NNT11SLL%O2=M3FD8NW4NNT11SLL%O3=M3FD8NW4NNT11%O4=M3FD8NW4NNT11SLL%O5=\nOS:M3FD8NW4NNT11SLL%O6=M3FD8NNT11SLL)WIN(W1=FFFF%W2=FFFF%W3=FFFF%W4=FFFF%W5\nOS:=FFFF%W6=FFFF)ECN(R=Y%DF=Y%TG=40%W=FFFF%O=M3FD8NW4SLL%CC=N%Q=)T1(R=Y%DF=\nOS:Y%TG=40%S=O%A=S+%F=AS%RD=0%Q=)T2(R=N)T3(R=N)T4(R=Y%DF=Y%TG=40%W=0%S=A%A=\nOS:Z%F=R%O=%RD=0%Q=)U1(R=N)IE(R=N)\n" - fparray = ['OS:SCAN(V=6.40-2%E=4%D=5/9%OT=88%CT=%CU=%PV=Y%DS=0%DC=L%G=N%TM=536BFF2F%P=x\nOS:86_64-apple-darwin10.8.0)SEQ(SP=F9%GCD=1%ISR=103%TI=RD%TS=A)OPS(O1=M3FD8\nOS:NW4NNT11SLL%O2=M3FD8NW4NNT11SLL%O3=M3FD8NW4NNT11%O4=M3FD8NW4NNT11SLL%O5=\nOS:M3FD8NW4NNT11SLL%O6=M3FD8NNT11SLL)WIN(W1=FFFF%W2=FFFF%W3=FFFF%W4=FFFF%W5\nOS:=FFFF%W6=FFFF)ECN(R=Y%DF=Y%TG=40%W=FFFF%O=M3FD8NW4SLL%CC=N%Q=)T1(R=Y%DF=\nOS:Y%TG=40%S=O%A=S+%F=AS%RD=0%Q=)T2(R=N)T3(R=N)T4(R=Y%DF=Y%TG=40%W=0%S=A%A=\nOS:Z%F=R%O=%RD=0%Q=)U1(R=N)IE(R=N)\n'] - rep = NmapParser.parse_fromfile(self.flist_os['nv6']['file']) + fparray = [ + "OS:SCAN(V=6.40-2%E=4%D=5/9%OT=88%CT=%CU=%PV=Y%DS=0%DC=L%G=N%TM=536BFF2F%P=x\nOS:86_64-apple-darwin10.8.0)SEQ(SP=F9%GCD=1%ISR=103%TI=RD%TS=A)OPS(O1=M3FD8\nOS:NW4NNT11SLL%O2=M3FD8NW4NNT11SLL%O3=M3FD8NW4NNT11%O4=M3FD8NW4NNT11SLL%O5=\nOS:M3FD8NW4NNT11SLL%O6=M3FD8NNT11SLL)WIN(W1=FFFF%W2=FFFF%W3=FFFF%W4=FFFF%W5\nOS:=FFFF%W6=FFFF)ECN(R=Y%DF=Y%TG=40%W=FFFF%O=M3FD8NW4SLL%CC=N%Q=)T1(R=Y%DF=\nOS:Y%TG=40%S=O%A=S+%F=AS%RD=0%Q=)T2(R=N)T3(R=N)T4(R=Y%DF=Y%TG=40%W=0%S=A%A=\nOS:Z%F=R%O=%RD=0%Q=)U1(R=N)IE(R=N)\n" + ] + rep = NmapParser.parse_fromfile(self.flist_os["nv6"]["file"]) h1 = rep.hosts.pop() self.assertEqual(h1.os.fingerprint, fpval) self.assertEqual(h1.os.fingerprints, fparray) def test_fpv5(self): - fpval = 'OS:SCAN(V=5.21%D=5/8%OT=22%CT=1%CU=37884%PV=Y%DS=0%DC=L%G=Y%TM=536BFE32%P=x\nOS:86_64-unknown-linux-gnu)SEQ(SP=100%GCD=1%ISR=106%TI=Z%CI=Z%II=I%TS=8)SEQ\nOS:(SP=101%GCD=1%ISR=107%TI=Z%CI=Z%II=I%TS=8)OPS(O1=M400CST11NW3%O2=M400CST\nOS:11NW3%O3=M400CNNT11NW3%O4=M400CST11NW3%O5=M400CST11NW3%O6=M400CST11)WIN(\nOS:W1=8000%W2=8000%W3=8000%W4=8000%W5=8000%W6=8000)ECN(R=Y%DF=Y%T=40%W=8018\nOS:%O=M400CNNSNW3%CC=Y%Q=)T1(R=Y%DF=Y%T=40%S=O%A=S+%F=AS%RD=0%Q=)T2(R=N)T3(\nOS:R=Y%DF=Y%T=40%W=8000%S=O%A=S+%F=AS%O=M400CST11NW3%RD=0%Q=)T4(R=Y%DF=Y%T=\nOS:40%W=0%S=A%A=Z%F=R%O=%RD=0%Q=)T5(R=Y%DF=Y%T=40%W=0%S=Z%A=S+%F=AR%O=%RD=0\nOS:%Q=)T6(R=Y%DF=Y%T=40%W=0%S=A%A=Z%F=R%O=%RD=0%Q=)T7(R=Y%DF=Y%T=40%W=0%S=Z\nOS:%A=S+%F=AR%O=%RD=0%Q=)U1(R=Y%DF=N%T=40%IPL=164%UN=0%RIPL=G%RID=G%RIPCK=G\nOS:%RUCK=G%RUD=G)IE(R=Y%DFI=N%T=40%CD=S)\n' - fparray = ['OS:SCAN(V=5.21%D=5/8%OT=22%CT=1%CU=37884%PV=Y%DS=0%DC=L%G=Y%TM=536BFE32%P=x\nOS:86_64-unknown-linux-gnu)SEQ(SP=100%GCD=1%ISR=106%TI=Z%CI=Z%II=I%TS=8)SEQ\nOS:(SP=101%GCD=1%ISR=107%TI=Z%CI=Z%II=I%TS=8)OPS(O1=M400CST11NW3%O2=M400CST\nOS:11NW3%O3=M400CNNT11NW3%O4=M400CST11NW3%O5=M400CST11NW3%O6=M400CST11)WIN(\nOS:W1=8000%W2=8000%W3=8000%W4=8000%W5=8000%W6=8000)ECN(R=Y%DF=Y%T=40%W=8018\nOS:%O=M400CNNSNW3%CC=Y%Q=)T1(R=Y%DF=Y%T=40%S=O%A=S+%F=AS%RD=0%Q=)T2(R=N)T3(\nOS:R=Y%DF=Y%T=40%W=8000%S=O%A=S+%F=AS%O=M400CST11NW3%RD=0%Q=)T4(R=Y%DF=Y%T=\nOS:40%W=0%S=A%A=Z%F=R%O=%RD=0%Q=)T5(R=Y%DF=Y%T=40%W=0%S=Z%A=S+%F=AR%O=%RD=0\nOS:%Q=)T6(R=Y%DF=Y%T=40%W=0%S=A%A=Z%F=R%O=%RD=0%Q=)T7(R=Y%DF=Y%T=40%W=0%S=Z\nOS:%A=S+%F=AR%O=%RD=0%Q=)U1(R=Y%DF=N%T=40%IPL=164%UN=0%RIPL=G%RID=G%RIPCK=G\nOS:%RUCK=G%RUD=G)IE(R=Y%DFI=N%T=40%CD=S)\n'] - rep = NmapParser.parse_fromfile(self.flist_os['nv5']['file']) + fpval = "OS:SCAN(V=5.21%D=5/8%OT=22%CT=1%CU=37884%PV=Y%DS=0%DC=L%G=Y%TM=536BFE32%P=x\nOS:86_64-unknown-linux-gnu)SEQ(SP=100%GCD=1%ISR=106%TI=Z%CI=Z%II=I%TS=8)SEQ\nOS:(SP=101%GCD=1%ISR=107%TI=Z%CI=Z%II=I%TS=8)OPS(O1=M400CST11NW3%O2=M400CST\nOS:11NW3%O3=M400CNNT11NW3%O4=M400CST11NW3%O5=M400CST11NW3%O6=M400CST11)WIN(\nOS:W1=8000%W2=8000%W3=8000%W4=8000%W5=8000%W6=8000)ECN(R=Y%DF=Y%T=40%W=8018\nOS:%O=M400CNNSNW3%CC=Y%Q=)T1(R=Y%DF=Y%T=40%S=O%A=S+%F=AS%RD=0%Q=)T2(R=N)T3(\nOS:R=Y%DF=Y%T=40%W=8000%S=O%A=S+%F=AS%O=M400CST11NW3%RD=0%Q=)T4(R=Y%DF=Y%T=\nOS:40%W=0%S=A%A=Z%F=R%O=%RD=0%Q=)T5(R=Y%DF=Y%T=40%W=0%S=Z%A=S+%F=AR%O=%RD=0\nOS:%Q=)T6(R=Y%DF=Y%T=40%W=0%S=A%A=Z%F=R%O=%RD=0%Q=)T7(R=Y%DF=Y%T=40%W=0%S=Z\nOS:%A=S+%F=AR%O=%RD=0%Q=)U1(R=Y%DF=N%T=40%IPL=164%UN=0%RIPL=G%RID=G%RIPCK=G\nOS:%RUCK=G%RUD=G)IE(R=Y%DFI=N%T=40%CD=S)\n" + fparray = [ + "OS:SCAN(V=5.21%D=5/8%OT=22%CT=1%CU=37884%PV=Y%DS=0%DC=L%G=Y%TM=536BFE32%P=x\nOS:86_64-unknown-linux-gnu)SEQ(SP=100%GCD=1%ISR=106%TI=Z%CI=Z%II=I%TS=8)SEQ\nOS:(SP=101%GCD=1%ISR=107%TI=Z%CI=Z%II=I%TS=8)OPS(O1=M400CST11NW3%O2=M400CST\nOS:11NW3%O3=M400CNNT11NW3%O4=M400CST11NW3%O5=M400CST11NW3%O6=M400CST11)WIN(\nOS:W1=8000%W2=8000%W3=8000%W4=8000%W5=8000%W6=8000)ECN(R=Y%DF=Y%T=40%W=8018\nOS:%O=M400CNNSNW3%CC=Y%Q=)T1(R=Y%DF=Y%T=40%S=O%A=S+%F=AS%RD=0%Q=)T2(R=N)T3(\nOS:R=Y%DF=Y%T=40%W=8000%S=O%A=S+%F=AS%O=M400CST11NW3%RD=0%Q=)T4(R=Y%DF=Y%T=\nOS:40%W=0%S=A%A=Z%F=R%O=%RD=0%Q=)T5(R=Y%DF=Y%T=40%W=0%S=Z%A=S+%F=AR%O=%RD=0\nOS:%Q=)T6(R=Y%DF=Y%T=40%W=0%S=A%A=Z%F=R%O=%RD=0%Q=)T7(R=Y%DF=Y%T=40%W=0%S=Z\nOS:%A=S+%F=AR%O=%RD=0%Q=)U1(R=Y%DF=N%T=40%IPL=164%UN=0%RIPL=G%RID=G%RIPCK=G\nOS:%RUCK=G%RUD=G)IE(R=Y%DFI=N%T=40%CD=S)\n" + ] + rep = NmapParser.parse_fromfile(self.flist_os["nv5"]["file"]) h1 = rep.hosts[4] self.assertEqual(h1.os.fingerprint, fpval) self.assertEqual(h1.os.fingerprints, fparray) def test_cpeservice(self): - cpelist = ['cpe:/a:openbsd:openssh:5.9p1','cpe:/o:linux:linux_kernel'] - rep = NmapParser.parse_fromfile(self.flist_os['fullscan']['file']) + cpelist = ["cpe:/a:openbsd:openssh:5.9p1", "cpe:/o:linux:linux_kernel"] + rep = NmapParser.parse_fromfile(self.flist_os["fullscan"]["file"]) h1 = rep.hosts.pop() s = h1.services[0] self.assertEqual(s.cpelist[0].cpestring, cpelist[0]) @@ -136,11 +267,19 @@ def test_os_class_probabilities(self): self.assertEqual(osc.osgen, "3.X") self.assertEqual(osc.accuracy, 100) - #cpe:/o:linux:linux_kernel:3 - + # cpe:/o:linux:linux_kernel:3 + -if __name__ == '__main__': - test_suite = ['test_fp', 'test_fpv6', 'test_osmatches_new', 'test_osclasses_new', - 'test_fpv5', 'test_osmatches_old', 'test_cpeservice', 'test_os_class_probabilities'] +if __name__ == "__main__": + test_suite = [ + "test_fp", + "test_fpv6", + "test_osmatches_new", + "test_osclasses_new", + "test_fpv5", + "test_osmatches_old", + "test_cpeservice", + "test_os_class_probabilities", + ] suite = unittest.TestSuite(map(TestNmapFP, test_suite)) test_result = unittest.TextTestRunner(verbosity=2).run(suite) diff --git a/libnmap/test/test_host.py b/libnmap/test/test_host.py index 7e90535..2697b56 100644 --- a/libnmap/test/test_host.py +++ b/libnmap/test/test_host.py @@ -164,9 +164,9 @@ def test_host_api(self): h = NmapParser.parse(host2) self.assertEqual(h.starttime, "1361738318") self.assertEqual(h.endtime, "13617386177") - self.assertEqual(h.address, '127.0.0.1') + self.assertEqual(h.address, "127.0.0.1") self.assertEqual(h.status, "up") - self.assertEqual(h.hostnames, ['localhost', 'localhost', 'localhost2']) + self.assertEqual(h.hostnames, ["localhost", "localhost", "localhost2"]) h2 = NmapParser.parse(host3) self.assertEqual(len(h2.services), 5) @@ -178,11 +178,19 @@ 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( + 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'}]) + 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) @@ -193,39 +201,60 @@ def test_diff_host(self): c2 = h1.diff(h3) c3 = h2.diff(h3) - self.assertEqual(c1.changed(), set(['hostnames'])) + self.assertEqual(c1.changed(), set(["hostnames"])) self.assertEqual(c1.added(), set([])) self.assertEqual(c1.removed(), set([])) - self.assertEqual(c1.unchanged(), set(['status', - "NmapService::tcp.22", - "NmapService::tcp.111", - "NmapService::tcp.631", - "NmapService::tcp.3306", - 'address', - "NmapService::tcp.25"])) + self.assertEqual( + c1.unchanged(), + set( + [ + "status", + "NmapService::tcp.22", + "NmapService::tcp.111", + "NmapService::tcp.631", + "NmapService::tcp.3306", + "address", + "NmapService::tcp.25", + ] + ), + ) - self.assertEqual(c2.changed(), set(['status', - "NmapService::tcp.3306"])) + self.assertEqual(c2.changed(), set(["status", "NmapService::tcp.3306"])) self.assertEqual(c2.added(), set(["NmapService::tcp.25"])) self.assertEqual(c2.removed(), set(["NmapService::tcp.3307"])) - self.assertEqual(c2.unchanged(), set(["NmapService::tcp.631", - 'hostnames', - "NmapService::tcp.22", - "NmapService::tcp.111", - 'address'])) - - self.assertEqual(c3.changed(), set(['status', 'hostnames', - "NmapService::tcp.3306"])) + self.assertEqual( + c2.unchanged(), + set( + [ + "NmapService::tcp.631", + "hostnames", + "NmapService::tcp.22", + "NmapService::tcp.111", + "address", + ] + ), + ) + + self.assertEqual( + c3.changed(), set(["status", "hostnames", "NmapService::tcp.3306"]) + ) self.assertEqual(c3.added(), set(["NmapService::tcp.25"])) self.assertEqual(c3.removed(), set(["NmapService::tcp.3307"])) - self.assertEqual(c3.unchanged(), set(["NmapService::tcp.631", - "NmapService::tcp.22", - "NmapService::tcp.111", - 'address'])) + self.assertEqual( + c3.unchanged(), + set( + [ + "NmapService::tcp.631", + "NmapService::tcp.22", + "NmapService::tcp.111", + "address", + ] + ), + ) -if __name__ == '__main__': - test_suite = ['test_eq_host', 'test_host_api', 'test_diff_host'] +if __name__ == "__main__": + test_suite = ["test_eq_host", "test_host_api", "test_diff_host"] suite = unittest.TestSuite(map(TestNmapHost, test_suite)) test_result = unittest.TextTestRunner(verbosity=2).run(suite) diff --git a/libnmap/test/test_new_parser.py b/libnmap/test/test_new_parser.py index a863c5b..0370ccc 100644 --- a/libnmap/test/test_new_parser.py +++ b/libnmap/test/test_new_parser.py @@ -4,22 +4,28 @@ import unittest from libnmap.parser import NmapParser, NmapParserException -baddatalist = ["aaa", None, '', 123, "ports/>>>", "", - "", ""] +baddatalist = [ + "aaa", + None, + "", + 123, + "ports/>>>", + "", + "", + "", +] class TestNmapParser(unittest.TestCase): def test_parse(self): for baddata in baddatalist: - self.assertRaises(NmapParserException, NmapParser.parse, - baddata, "zz") - self.assertRaises(NmapParserException, NmapParser.parse, - baddata, "XML") - self.assertRaises(NmapParserException, NmapParser.parse, - baddata, "YAML") + self.assertRaises(NmapParserException, NmapParser.parse, baddata, "zz") + self.assertRaises(NmapParserException, NmapParser.parse, baddata, "XML") + self.assertRaises(NmapParserException, NmapParser.parse, baddata, "YAML") -if __name__ == '__main__': - test_suite = ['test_parse'] + +if __name__ == "__main__": + test_suite = ["test_parse"] suite = unittest.TestSuite(map(TestNmapParser, test_suite)) test_result = unittest.TextTestRunner(verbosity=2).run(suite) diff --git a/libnmap/test/test_parser.py b/libnmap/test/test_parser.py index d940969..599af95 100644 --- a/libnmap/test/test_parser.py +++ b/libnmap/test/test_parser.py @@ -10,28 +10,22 @@ class TestNmapParser(unittest.TestCase): def setUp(self): fdir = os.path.dirname(os.path.realpath(__file__)) self.flist_full = [ - {'file': "%s/%s" % (fdir, - 'files/2_hosts.xml'), 'hosts': 2}, - {'file': "%s/%s" % (fdir, - 'files/1_hosts.xml'), 'hosts': 1}, - {'file': "%s/%s" % (fdir, - 'files/1_hosts_banner_ports_notsyn.xml'), - 'hosts': 1}, + {"file": "%s/%s" % (fdir, "files/2_hosts.xml"), "hosts": 2}, + {"file": "%s/%s" % (fdir, "files/1_hosts.xml"), "hosts": 1}, + { + "file": "%s/%s" % (fdir, "files/1_hosts_banner_ports_notsyn.xml"), + "hosts": 1, + }, # {'file': "%s/%s" % (fdir, # 'files/1_hosts_banner_ports_xmas.xml'), # 'hosts': 1}, - {'file': "%s/%s" % (fdir, - 'files/1_hosts_banner_ports.xml'), 'hosts': 1}, - {'file': "%s/%s" % (fdir, - 'files/1_hosts_banner.xml'), 'hosts': 1}, - {'file': "%s/%s" % (fdir, - 'files/2_hosts_version.xml'), 'hosts': 2}, + {"file": "%s/%s" % (fdir, "files/1_hosts_banner_ports.xml"), "hosts": 1}, + {"file": "%s/%s" % (fdir, "files/1_hosts_banner.xml"), "hosts": 1}, + {"file": "%s/%s" % (fdir, "files/2_hosts_version.xml"), "hosts": 2}, # {'file': "%s/%s" % (fdir, # 'files/2_null_hosts.xml'), 'hosts': 2}, - {'file': "%s/%s" % (fdir, - 'files/2_tcp_hosts.xml'), 'hosts': 2}, - {'file': "%s/%s" % (fdir, - 'files/1_hosts_nohostname.xml'), 'hosts': 1}, + {"file": "%s/%s" % (fdir, "files/2_tcp_hosts.xml"), "hosts": 2}, + {"file": "%s/%s" % (fdir, "files/1_hosts_nohostname.xml"), "hosts": 1}, ] self.flist = self.flist_full @@ -92,9 +86,9 @@ def setUp(self): """ - self.port_string3 = '' - self.port_string4 = '' - self.port_string5 = 'GINGERBREADMAN' + self.port_string3 = "" + self.port_string4 = "" + self.port_string5 = "GINGERBREADMAN" self.port_string6 = """ """ + class TestNmapService(unittest.TestCase): def setUp(self): self.s1 = NmapParser.parse(service1) @@ -182,22 +183,23 @@ def test_port_state_changed(self): nservice3 = NmapParser.parse(port_string_other3) nservice4 = NmapParser.parse(port_string_other4) - self.assertEqual(nservice1.diff(nservice2).changed(), set(['state'])) + self.assertEqual(nservice1.diff(nservice2).changed(), set(["state"])) self.assertRaises(NmapDiffException, nservice1.diff, nservice3) self.assertRaises(NmapDiffException, nservice1.diff, nservice4) -# + # self.assertRaises(NmapDiffException, nservice2.diff, nservice3) - self.assertEqual(nservice3.diff(nservice4).changed(), - set(['state', 'service'])) + self.assertEqual(nservice3.diff(nservice4).changed(), set(["state", "service"])) def test_port_state_unchanged(self): nservice1 = NmapParser.parse(port_string) nservice2 = NmapParser.parse(port_string_other2) - #nservice3 = NmapParser.parse(port_string_other3) - #nservice4 = NmapParser.parse(port_string_other4) + # nservice3 = NmapParser.parse(port_string_other3) + # nservice4 = NmapParser.parse(port_string_other4) - self.assertEqual(nservice1.diff(nservice2).unchanged(), - set(['banner', 'protocol', 'port', 'service', 'id', 'reason'])) + self.assertEqual( + nservice1.diff(nservice2).unchanged(), + set(["banner", "protocol", "port", "service", "id", "reason"]), + ) def test_port_service_changed(self): nservice1 = NmapParser.parse(port_string) @@ -207,13 +209,10 @@ def test_port_service_changed(self): nservice8 = NmapParser.parse(port_string_other8) nservice9 = NmapParser.parse(port_string_other9) - self.assertEqual(nservice1.diff(nservice2).changed(), - set(['state'])) - self.assertEqual(nservice5.diff(nservice4).changed(), - set(['service'])) + self.assertEqual(nservice1.diff(nservice2).changed(), set(["state"])) + self.assertEqual(nservice5.diff(nservice4).changed(), set(["service"])) # banner changed - self.assertEqual(nservice8.diff(nservice9).changed(), - set(['banner'])) + self.assertEqual(nservice8.diff(nservice9).changed(), set(["banner"])) def test_eq_service(self): self.assertNotEqual(NmapDiffException, self.s1, self.s2) @@ -226,19 +225,20 @@ def test_eq_service(self): def test_diff_service(self): self.assertRaises(NmapDiffException, self.s1.diff, self.s2) 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', 'reason'])) + self.assertEqual(self.s1.diff(self.s4).changed(), set(["state"])) + self.assertEqual( + self.s1.diff(self.s4).unchanged(), + set(["banner", "protocol", "port", "service", "id", "reason"]), + ) - self.assertEqual(self.s5.diff(self.s6).changed(), set(['banner'])) + 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'])) + self.assertEqual(ddict.changed(), set(["reason"])) def test_noservice(self): noservice = NmapParser.parse(port_noservice) @@ -253,9 +253,13 @@ def test_tunnel(self): self.assertEqual(servicetunnel.tunnel, "ssl") -if __name__ == '__main__': - test_suite = ['test_port_state_changed', 'test_port_state_unchanged', - 'test_port_service_changed', 'test_eq_service', - 'test_diff_service'] +if __name__ == "__main__": + test_suite = [ + "test_port_state_changed", + "test_port_state_unchanged", + "test_port_service_changed", + "test_eq_service", + "test_diff_service", + ] suite = unittest.TestSuite(map(TestNmapService, test_suite)) test_result = unittest.TextTestRunner(verbosity=2).run(suite) diff --git a/setup.py b/setup.py index fcbe068..37f4c55 100644 --- a/setup.py +++ b/setup.py @@ -5,25 +5,29 @@ 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/', + name="python-libnmap", + version="0.7.1", + 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'), + 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", - "Programming Language :: Python :: 3.5", - "Programming Language :: Python :: 3.6", - "Topic :: System :: Networking"] + 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", + "Programming Language :: Python :: 3.5", + "Programming Language :: Python :: 3.6", + "Topic :: System :: Networking", + ], ) diff --git a/tox.ini b/tox.ini index 8ea1651..6d69312 100644 --- a/tox.ini +++ b/tox.ini @@ -1,8 +1,15 @@ [tox] -envlist = py27,py32 +envlist = py27, py32, py37, linters + [testenv] -deps=nose +deps=pytest pymongo sqlalchemy pymysql -commands=nosetests +commands=pytest + +[linters:env] +deps = + flake8 +commands = + flake8 --exclude test,docs,examples . \ No newline at end of file From cf1aee77980b295e2d632dbedea1cb3abf661c9a Mon Sep 17 00:00:00 2001 From: Ronald Bister Date: Wed, 18 Nov 2020 20:55:48 +0100 Subject: [PATCH 08/88] fix formatting and tox/travis tests --- .travis.yml | 12 +++++------- libnmap/process.py | 24 +++++++++++++----------- tox.ini | 22 +++++++++++++++++++--- 3 files changed, 37 insertions(+), 21 deletions(-) diff --git a/.travis.yml b/.travis.yml index 1a06184..271d3ed 100644 --- a/.travis.yml +++ b/.travis.yml @@ -6,25 +6,23 @@ python: - "3.6" - "3.7" # command to install dependencies -env: - - MONGO_VERSION=2.4.3 +# env: +# - MONGO_VERSION=2.4.3 -services: mongodb +# services: mongodb before_install: - "sudo apt-get update -qq" - "sudo apt-get install nmap -qq" install: - - "pip install pep8" - - "pip install pyflakes" + - "pip install flake8" # - "pip install boto" # disabled: since boto not supporting py3 # - "pip install pymongo sqlalchemy MySQL-python" # disabled MySQL-python (not py3 compatible) # - "pip install pymongo sqlalchemy pymysql" - "pip install coveralls" - "pip install ." before_script: - - "pep8 . --exclude test,docs,examples" - - "pyflakes ." + - "flake8 . --exclude test,docs,examples" # - mysql -e 'create database poulet;' script: nosetests --with-coverage --cover-package=libnmap after_success: diff --git a/libnmap/process.py b/libnmap/process.py index 184dfee..4359848 100644 --- a/libnmap/process.py +++ b/libnmap/process.py @@ -354,9 +354,9 @@ def has_terminated(self): :return: True if nmap process is not running anymore. """ return ( - self.state == self.DONE - or self.state == self.FAILED - or self.state == self.CANCELLED + self.state == self.DONE or + self.state == self.FAILED or + self.state == self.CANCELLED ) def has_failed(self): @@ -675,15 +675,17 @@ def mycallback(nmapscan=None): ) 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) + 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) + 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__": diff --git a/tox.ini b/tox.ini index 6d69312..96d68b7 100644 --- a/tox.ini +++ b/tox.ini @@ -1,5 +1,5 @@ [tox] -envlist = py27, py32, py37, linters +envlist = py27, py32, py38, flake8, pycodestyle, formatting [testenv] deps=pytest @@ -8,8 +8,24 @@ deps=pytest pymysql commands=pytest -[linters:env] +[testenv:flake8] deps = flake8 commands = - flake8 --exclude test,docs,examples . \ No newline at end of file + flake8 --exclude test,docs,examples,.tox . + +[testenv:pycodestyle] +deps = + pycodestyle +commands = + pycodestyle --exclude test,docs,examples,.tox . + +[testenv:formatting] +basepython = python3 +deps = + black==20.8b1 + isort==5.5.2 +commands = + black --check . --exclude="tests/|venv|.tox" + isort . --check-only +changedir = {toxinidir} \ No newline at end of file From 71b707758851e4b622f87d9a73266e06f60aeab4 Mon Sep 17 00:00:00 2001 From: Ronald Date: Mon, 23 Nov 2020 21:28:34 +0100 Subject: [PATCH 09/88] fixed issue #87 --- .travis.yml | 1 + CHANGES.txt | 1 + TODO | 5 + examples/check_cpe.py | 25 +- examples/diff_sample2.py | 4 +- examples/elastikibana.py | 7 +- examples/nmap_task.py | 5 +- examples/nmap_task_bg.py | 5 +- libnmap/parser.py | 12 +- libnmap/test/files/defused_et_included.xml | 6 + .../test/files/defused_et_local_includer.xml | 5 + libnmap/test/test_backend_plugin_factory.py | 344 +++++++++--------- libnmap/test/test_defusedxml.py | 42 +++ tox.ini | 13 +- 14 files changed, 275 insertions(+), 200 deletions(-) create mode 100644 libnmap/test/files/defused_et_included.xml create mode 100644 libnmap/test/files/defused_et_local_includer.xml create mode 100644 libnmap/test/test_defusedxml.py diff --git a/.travis.yml b/.travis.yml index 271d3ed..f3c3196 100644 --- a/.travis.yml +++ b/.travis.yml @@ -16,6 +16,7 @@ before_install: - "sudo apt-get install nmap -qq" install: - "pip install flake8" + - "pip install defusedxml" # - "pip install boto" # disabled: since boto not supporting py3 # - "pip install pymongo sqlalchemy MySQL-python" # disabled MySQL-python (not py3 compatible) # - "pip install pymongo sqlalchemy pymysql" diff --git a/CHANGES.txt b/CHANGES.txt index 0c850b3..55d6af7 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -1,3 +1,4 @@ +v0.7.1, 22/11/2020 -- code clean-up + fix for CVE-2019-1010017 v0.7.0, 28/02/2016 -- A few bugfixes - fixe of endless loop in Nmap.Process. Fix provided by @rcarrillo, many thanks! v0.6.3, 18/08/2015 -- Merged pull requests for automatic pypi upload, thanks @bmx0r diff --git a/TODO b/TODO index f9a82b7..7185fd4 100644 --- a/TODO +++ b/TODO @@ -1,3 +1,8 @@ +0.7.1: - clean-up blacked code and pylint it +0.7.1: - add unittest for defusedxml + - billionlaugh and external entities +0.7.1: - add CSV backend support +0.7.1: - Change License - improve API for NSE scripts - add support for post,pre and host scripts - complete unit tests with coverall support diff --git a/examples/check_cpe.py b/examples/check_cpe.py index e2656e6..7c1ad8f 100644 --- a/examples/check_cpe.py +++ b/examples/check_cpe.py @@ -5,18 +5,24 @@ rep = NmapParser.parse_fromfile("libnmap/test/files/full_sudo6.xml") -print("Nmap scan discovered {0}/{1} hosts up".format(rep.hosts_up, - rep.hosts_total)) +print( + "Nmap scan discovered {0}/{1} hosts up".format( + rep.hosts_up, rep.hosts_total + ) +) for _host in rep.hosts: if _host.is_up(): - print("+ Host: {0} {1}".format(_host.address, - " ".join(_host.hostnames))) + print( + "+ Host: {0} {1}".format(_host.address, " ".join(_host.hostnames)) + ) # get CPE from service if available for s in _host.services: - print(" Service: {0}/{1} ({2})".format(s.port, - s.protocol, - s.state)) + print( + " Service: {0}/{1} ({2})".format( + s.port, s.protocol, s.state + ) + ) # NmapService.cpelist returns an array of CPE objects for _serv_cpe in s.cpelist: print(" CPE: {0}".format(_serv_cpe.cpestring)) @@ -24,8 +30,9 @@ if _host.os_fingerprinted: print(" OS Fingerprints") for osm in _host.os.osmatches: - print(" Found Match:{0} ({1}%)".format(osm.name, - osm.accuracy)) + print( + " Found Match:{0} ({1}%)".format(osm.name, osm.accuracy) + ) # NmapOSMatch.get_cpe() method return an array of string # unlike NmapOSClass.cpelist which returns an array of CPE obj for cpe in osm.get_cpe(): diff --git a/examples/diff_sample2.py b/examples/diff_sample2.py index 6f2f105..0cb711a 100644 --- a/examples/diff_sample2.py +++ b/examples/diff_sample2.py @@ -66,7 +66,9 @@ def print_diff(obj1, obj2): def main(): - newrep = NmapParser.parse_fromfile("libnmap/test/files/2_hosts_achange.xml") + newrep = NmapParser.parse_fromfile( + "libnmap/test/files/2_hosts_achange.xml" + ) oldrep = NmapParser.parse_fromfile("libnmap/test/files/1_hosts.xml") print_diff(newrep, oldrep) diff --git a/examples/elastikibana.py b/examples/elastikibana.py index 43c8640..f91cadc 100644 --- a/examples/elastikibana.py +++ b/examples/elastikibana.py @@ -27,10 +27,9 @@ def get_os(nmap_host): cpelist = nmap_host.os.os_cpelist() if len(cpelist): mcpe = cpelist.pop() - rval.update({ - "vendor": mcpe.get_vendor(), - "product": mcpe.get_product() - }) + rval.update( + {"vendor": mcpe.get_vendor(), "product": mcpe.get_product()} + ) return rval diff --git a/examples/nmap_task.py b/examples/nmap_task.py index cbea96e..c0988be 100644 --- a/examples/nmap_task.py +++ b/examples/nmap_task.py @@ -9,10 +9,7 @@ def mycallback(nmaptask): if nmaptask: print( "Task {0} ({1}): ETC: {2} DONE: {3}%".format( - nmaptask.name, - nmaptask.status, - nmaptask.etc, - nmaptask.progress + nmaptask.name, nmaptask.status, nmaptask.etc, nmaptask.progress ) ) diff --git a/examples/nmap_task_bg.py b/examples/nmap_task_bg.py index 5b640b2..30d5c31 100644 --- a/examples/nmap_task_bg.py +++ b/examples/nmap_task_bg.py @@ -10,10 +10,7 @@ if nmaptask: print( "Task {0} ({1}): ETC: {2} DONE: {3}%".format( - nmaptask.name, - nmaptask.status, - nmaptask.etc, - nmaptask.progress + nmaptask.name, nmaptask.status, nmaptask.etc, nmaptask.progress ) ) print("rc: {0} output: {1}".format(nmap_proc.rc, nmap_proc.summary)) diff --git a/libnmap/parser.py b/libnmap/parser.py index 427eb52..ed1fabc 100644 --- a/libnmap/parser.py +++ b/libnmap/parser.py @@ -2,9 +2,13 @@ try: - import xml.etree.cElementTree as ET + import defusedxml.ElementTree as ET except ImportError: - import xml.etree.ElementTree as ET + try: + import xml.etree.cElementTree as ET + except ImportError: + import xml.etree.ElementTree as ET +from xml.etree.ElementTree import iselement as et_iselement from libnmap.objects import NmapHost, NmapService, NmapReport @@ -701,7 +705,7 @@ def __format_element(elt_data): "to instanciate XML Element from " "string {0} - {1}".format(elt_data, e) ) - elif ET.iselement(elt_data): + elif et_iselement(elt_data): xelement = elt_data else: raise NmapParserException( @@ -724,7 +728,7 @@ def __format_attributes(elt_data): """ rval = {} - if not ET.iselement(elt_data): + if not et_iselement(elt_data): raise NmapParserException( "Error while trying to parse supplied " "data attributes: format is not XML or " diff --git a/libnmap/test/files/defused_et_included.xml b/libnmap/test/files/defused_et_included.xml new file mode 100644 index 0000000..82e04b6 --- /dev/null +++ b/libnmap/test/files/defused_et_included.xml @@ -0,0 +1,6 @@ + + + text + texttail + + \ No newline at end of file diff --git a/libnmap/test/files/defused_et_local_includer.xml b/libnmap/test/files/defused_et_local_includer.xml new file mode 100644 index 0000000..d063ae6 --- /dev/null +++ b/libnmap/test/files/defused_et_local_includer.xml @@ -0,0 +1,5 @@ + + +]> + \ No newline at end of file diff --git a/libnmap/test/test_backend_plugin_factory.py b/libnmap/test/test_backend_plugin_factory.py index 8e956ab..f396848 100644 --- a/libnmap/test/test_backend_plugin_factory.py +++ b/libnmap/test/test_backend_plugin_factory.py @@ -1,175 +1,175 @@ #!/usr/bin/env python # -*- coding: utf-8 -*- -# import unittest -# import os -# from libnmap.parser import NmapParser -# from libnmap.plugins.backendplugin import NmapBackendPlugin -# from libnmap.plugins.backendpluginFactory import BackendPluginFactory -# -# -# class TestNmapBackendPlugin(unittest.TestCase): -# """ -# This testing class will tests each plugins -# The following test need to be done : -# - test the factory -# - test all the method of the class NmapBackendPlugin: -# - Verify implmented/notImplemented -# - Verify the behaviour (ie insert must insert) -# To support a new plugin or a new way to instanciate a plugin, add a dict -# with the necessary parameter in the urls table define in setUp -# All testcase must loop thru theses urls to validate a plugins -# """ -# def setUp(self): -# fdir = os.path.dirname(os.path.realpath(__file__)) -# self.flist_full = [ -# { -# 'file': "{0}/{1}".format(fdir, "files/2_hosts.xml"), -# 'hosts': 2 -# }, -# { -# 'file': "{0}/{1}".format(fdir, "files/1_hosts.xml"), -# 'hosts': 1 -# }, -# { -# 'file': "{0}/{1}".format( -# fdir, -# "files/1_hosts_banner_ports_notsyn.xml" -# ), -# 'hosts': 1 -# }, -# { -# 'file': "{0}/{1}".format( -# fdir, -# 'files/1_hosts_banner_ports.xml' -# ), -# 'hosts': 1 -# }, -# { -# 'file': "{0}/{1}".format( -# fdir, -# 'files/1_hosts_banner.xml' -# ), -# 'hosts': 1 -# }, -# { -# 'file': "{0}/{1}".format( -# fdir, -# 'files/2_hosts_version.xml' -# ), -# 'hosts': 2 -# }, -# { -# 'file': "{0}/{1}".format( -# fdir, -# 'files/2_tcp_hosts.xml' -# ), -# 'hosts': 2 -# }, -# { -# 'file': "{0}/{1}".format( -# fdir, -# 'files/1_hosts_nohostname.xml' -# ), -# 'hosts': 1 -# } -# ] -# self.flist = self.flist_full -# # build a list of NmapReport -# self.reportList = [] -# for testfile in self.flist: -# fd = open(testfile['file'], 'r') -# s = fd.read() -# fd.close() -# nrp = NmapParser.parse(s) -# self.reportList.append(nrp) -# -# self.urls = [ -# { -# 'plugin_name': "mongodb" -# }, -# { -# 'plugin_name': 'sql', -# 'url': 'sqlite:////tmp/reportdb.sql', -# 'echo': False -# }, -# { -# 'plugin_name': 'sql', -# 'url': 'mysql+pymysql://root@localhost/poulet', -# 'echo': False -# } -# ] -# -# def test_backend_factory(self): -# """ test_factory BackendPluginFactory.create(**url) -# Invoke factory and test that the object is of the right classes -# """ -# for url in self.urls: -# backend = BackendPluginFactory.create(**url) -# self.assertEqual(isinstance(backend, NmapBackendPlugin), True) -# className = "Nmap%sPlugin" % url['plugin_name'].title() -# self.assertEqual(backend.__class__.__name__, className, True) -# -# def test_backend_insert(self): -# """ test_insert -# best way to insert is to call save() of nmapreport :P -# """ -# for nrp in self.reportList: -# for url in self.urls: -# # create the backend factory object -# backend = BackendPluginFactory.create(**url) -# # save the report -# returncode = nrp.save(backend) -# # test return code -# self.assertNotEqual(returncode, None) -# -# def test_backend_get(self): -# """ test_backend_get -# inset all report and save the returned id in a list -# then get each id and create a new list of report -# compare each report (assume eq) -# """ -# id_list = [] -# result_list = [] -# for url in self.urls: -# backend = BackendPluginFactory.create(**url) -# for nrp in self.reportList: -# id_list.append(nrp.save(backend)) -# for rep_id in id_list: -# result_list.append(backend.get(rep_id)) -# self.assertEqual(len(result_list), len(self.reportList)) -# self.assertEqual((result_list), (self.reportList)) -# id_list = [] -# result_list = [] -# -# def test_backend_getall(self): -# pass -# -# def test_backend_delete(self): -# """ test_backend_delete -# inset all report and save the returned id in a list -# for each id remove the item and test if not present -# """ -# id_list = [] -# result_list = [] -# for url in self.urls: -# backend = BackendPluginFactory.create(**url) -# for nrp in self.reportList: -# id_list.append(nrp.save(backend)) -# for rep_id in id_list: -# result_list.append(backend.delete(rep_id)) -# self.assertEqual(backend.get(rep_id), None) -# id_list = [] -# result_list = [] -# -# -# if __name__ == '__main__': -# test_suite = [ -# 'test_backend_factory', -# 'test_backend_insert', -# 'test_backend_get', -# 'test_backend_getall', -# 'test_backend_delete' -# ] -# suite = unittest.TestSuite(map(TestNmapBackendPlugin, test_suite)) -# test_result = unittest.TextTestRunner(verbosity=5).run(suite) +import unittest +import os +from libnmap.parser import NmapParser +from libnmap.plugins.backendplugin import NmapBackendPlugin +from libnmap.plugins.backendpluginFactory import BackendPluginFactory + + +class TestNmapBackendPlugin(unittest.TestCase): + """ + This testing class will tests each plugins + The following test need to be done : + - test the factory + - test all the method of the class NmapBackendPlugin: + - Verify implmented/notImplemented + - Verify the behaviour (ie insert must insert) + To support a new plugin or a new way to instanciate a plugin, add a dict + with the necessary parameter in the urls table define in setUp + All testcase must loop thru theses urls to validate a plugins + """ + def setUp(self): + fdir = os.path.dirname(os.path.realpath(__file__)) + self.flist_full = [ + { + 'file': "{0}/{1}".format(fdir, "files/2_hosts.xml"), + 'hosts': 2 + }, + { + 'file': "{0}/{1}".format(fdir, "files/1_hosts.xml"), + 'hosts': 1 + }, + { + 'file': "{0}/{1}".format( + fdir, + "files/1_hosts_banner_ports_notsyn.xml" + ), + 'hosts': 1 + }, + { + 'file': "{0}/{1}".format( + fdir, + 'files/1_hosts_banner_ports.xml' + ), + 'hosts': 1 + }, + { + 'file': "{0}/{1}".format( + fdir, + 'files/1_hosts_banner.xml' + ), + 'hosts': 1 + }, + { + 'file': "{0}/{1}".format( + fdir, + 'files/2_hosts_version.xml' + ), + 'hosts': 2 + }, + { + 'file': "{0}/{1}".format( + fdir, + 'files/2_tcp_hosts.xml' + ), + 'hosts': 2 + }, + { + 'file': "{0}/{1}".format( + fdir, + 'files/1_hosts_nohostname.xml' + ), + 'hosts': 1 + } + ] + self.flist = self.flist_full + # build a list of NmapReport + self.reportList = [] + for testfile in self.flist: + fd = open(testfile['file'], 'r') + s = fd.read() + fd.close() + nrp = NmapParser.parse(s) + self.reportList.append(nrp) + + self.urls = [ + { + 'plugin_name': "mongodb" + }, + { + 'plugin_name': 'sql', + 'url': 'sqlite:////tmp/reportdb.sql', + 'echo': False + }, + { + 'plugin_name': 'sql', + 'url': 'mysql+pymysql://root@localhost/poulet', + 'echo': False + } + ] + + def test_backend_factory(self): + """ test_factory BackendPluginFactory.create(**url) + Invoke factory and test that the object is of the right classes + """ + for url in self.urls: + backend = BackendPluginFactory.create(**url) + self.assertEqual(isinstance(backend, NmapBackendPlugin), True) + className = "Nmap%sPlugin" % url['plugin_name'].title() + self.assertEqual(backend.__class__.__name__, className, True) + + def test_backend_insert(self): + """ test_insert + best way to insert is to call save() of nmapreport :P + """ + for nrp in self.reportList: + for url in self.urls: + # create the backend factory object + backend = BackendPluginFactory.create(**url) + # save the report + returncode = nrp.save(backend) + # test return code + self.assertNotEqual(returncode, None) + + def test_backend_get(self): + """ test_backend_get + inset all report and save the returned id in a list + then get each id and create a new list of report + compare each report (assume eq) + """ + id_list = [] + result_list = [] + for url in self.urls: + backend = BackendPluginFactory.create(**url) + for nrp in self.reportList: + id_list.append(nrp.save(backend)) + for rep_id in id_list: + result_list.append(backend.get(rep_id)) + self.assertEqual(len(result_list), len(self.reportList)) + self.assertEqual((result_list), (self.reportList)) + id_list = [] + result_list = [] + + def test_backend_getall(self): + pass + + def test_backend_delete(self): + """ test_backend_delete + inset all report and save the returned id in a list + for each id remove the item and test if not present + """ + id_list = [] + result_list = [] + for url in self.urls: + backend = BackendPluginFactory.create(**url) + for nrp in self.reportList: + id_list.append(nrp.save(backend)) + for rep_id in id_list: + result_list.append(backend.delete(rep_id)) + self.assertEqual(backend.get(rep_id), None) + id_list = [] + result_list = [] + + +if __name__ == '__main__': + test_suite = [ + 'test_backend_factory', + 'test_backend_insert', + 'test_backend_get', + 'test_backend_getall', + 'test_backend_delete' + ] + suite = unittest.TestSuite(map(TestNmapBackendPlugin, test_suite)) + test_result = unittest.TextTestRunner(verbosity=5).run(suite) diff --git a/libnmap/test/test_defusedxml.py b/libnmap/test/test_defusedxml.py new file mode 100644 index 0000000..99be84d --- /dev/null +++ b/libnmap/test/test_defusedxml.py @@ -0,0 +1,42 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +from libnmap.parser import NmapParser, NmapParserException +import unittest +import os + + +class TestDefusedXML(unittest.TestCase): + def setUp(self): + self.billionlaugh = """ + + + + + + + + + + + +]> +&lol9; + """ + self.fdir = os.path.dirname(os.path.realpath(__file__)) + self.billionlaugh_file = "{0}/files/{1}".format(self.fdir, "billion_laugh.xml") + self.external_entities_file = "{0}/files/{1}".format(self.fdir, "defused_et_local_includer.xml") + + def test_billion_laugh(self): + self.assertRaisesRegex(NmapParserException, ".*EntitiesForbidden", NmapParser.parse_fromstring, self.billionlaugh) + + def test_external_entities(self): + self.assertRaisesRegex(NmapParserException, ".*EntitiesForbidden", NmapParser.parse_fromfile, self.external_entities_file) + + +if __name__ == "__main__": + #test_suite = ["test_external_entities"] + test_suite = ["test_billion_laugh", "test_external_entities" ] + suite = unittest.TestSuite(map(TestDefusedXML, test_suite)) + test_result = unittest.TextTestRunner(verbosity=2).run(suite) diff --git a/tox.ini b/tox.ini index 96d68b7..a3edd8f 100644 --- a/tox.ini +++ b/tox.ini @@ -1,12 +1,21 @@ [tox] -envlist = py27, py32, py38, flake8, pycodestyle, formatting +envlist = py27, py32, py38, flake8, pycodestyle, formatting, defusedxml [testenv] +deps=pytest +commands=pytest --ignore=libnmap/test/test_defusedxml.py --ignore=libnmap/test/test_backend_plugin_factory.py + +[testenv:defusedxml] +deps=pytest + defusedxml +commands=pytest --ignore=libnmap/test/test_backend_plugin_factory.py + +[testenv:dbbackend] deps=pytest pymongo sqlalchemy pymysql -commands=pytest +commands=pytest --ignore=libnmap/test/test_defusedxml.py [testenv:flake8] deps = From 01f0e35a7b14afbfd933a8158d58d7853680f44f Mon Sep 17 00:00:00 2001 From: Ronald Bister Date: Mon, 23 Nov 2020 23:37:01 +0100 Subject: [PATCH 10/88] reformated the code using psf/black and some manual modifications --- libnmap/diff.py | 24 +++--- libnmap/objects/host.py | 9 ++- libnmap/objects/os.py | 2 +- libnmap/objects/report.py | 17 ++-- libnmap/objects/service.py | 2 +- libnmap/parser.py | 29 +++---- libnmap/plugins/es.py | 13 ++- libnmap/plugins/sql.py | 2 +- libnmap/process.py | 75 ++++++++--------- libnmap/test/test_backend_plugin_factory.py | 89 ++++++++------------- libnmap/test/test_defusedxml.py | 26 ++++-- libnmap/test/test_fp.py | 41 ++++++++-- libnmap/test/test_host.py | 7 +- libnmap/test/test_new_parser.py | 12 ++- libnmap/test/test_parser.py | 42 +++++++--- libnmap/test/test_report.py | 45 ++++++++--- libnmap/test/test_report_diff.py | 17 +++- libnmap/test/test_service.py | 4 +- tox.ini | 6 +- 19 files changed, 271 insertions(+), 191 deletions(-) diff --git a/libnmap/diff.py b/libnmap/diff.py index 152a9a5..d2e8e34 100644 --- a/libnmap/diff.py +++ b/libnmap/diff.py @@ -24,19 +24,17 @@ 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] - ) + 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] - ) + return set( + o + for o in self.intersect + if self.past_dict[o] == self.current_dict[o] ) @@ -72,8 +70,10 @@ def __init__(self, nmap_obj1, nmap_obj2): - 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): + 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() diff --git a/libnmap/objects/host.py b/libnmap/objects/host.py index de6a8ea..980682f 100644 --- a/libnmap/objects/host.py +++ b/libnmap/objects/host.py @@ -274,9 +274,9 @@ def get_open_ports(self): :return: list: of tuples (port,'proto') ie:[(22,'tcp'),(25, 'tcp')] """ - return ([ + return [ (p.port, p.protocol) for p in self._services if p.state == "open" - ]) + ] def get_service(self, portno, protocol="tcp"): """ @@ -286,8 +286,9 @@ def get_service(self, portno, protocol="tcp"): :return: NmapService or None """ plist = [ - p for p in self._services if(p.port == portno and - p.protocol == protocol) + 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") diff --git a/libnmap/objects/os.py b/libnmap/objects/os.py index 32abf70..5c64d86 100644 --- a/libnmap/objects/os.py +++ b/libnmap/objects/os.py @@ -331,7 +331,7 @@ def _add_dummy_osmatch(self, osclass_obj): "osmatch": { "name": _dname, "accuracy": osclass_obj.accuracy, - "line": -1 + "line": -1, }, "osclasses": [], } diff --git a/libnmap/objects/report.py b/libnmap/objects/report.py index fb9644c..0fbc05c 100644 --- a/libnmap/objects/report.py +++ b/libnmap/objects/report.py @@ -231,7 +231,7 @@ def summary(self): self.endtimestr, self.hosts_total, self.hosts_up, - self.elapsed + self.elapsed, ) ) return rval @@ -303,7 +303,7 @@ def get_raw_data(self): Returns a dict representing the NmapReport object. :return: dict - :todo: deprecate. get rid of this uglyness. + :todo: deprecate. get rid of this ugliness. """ raw_data = { "_nmaprun": self._nmaprun, @@ -327,14 +327,13 @@ def is_consistent(self): :return: boolean """ - rval = False + rval = True 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 + if set(_consistent_keys) != set(rdata): + rval = False + if None in rdata.values(): + rval = False return rval def get_dict(self): @@ -416,5 +415,5 @@ def __repr__(self): self.__class__.__name__, self.started, self.hosts_up, - self.hosts_total + self.hosts_total, ) diff --git a/libnmap/objects/service.py b/libnmap/objects/service.py index 6a691de..70fc5d0 100644 --- a/libnmap/objects/service.py +++ b/libnmap/objects/service.py @@ -237,7 +237,7 @@ def banner(self): "conf", "cpelist", "servicefp", - "tunnel" + "tunnel", ] relevant = ["product", "version", "extrainfo"] b = "" diff --git a/libnmap/parser.py b/libnmap/parser.py index ed1fabc..928a62b 100644 --- a/libnmap/parser.py +++ b/libnmap/parser.py @@ -89,8 +89,7 @@ def _parse_xml(cls, nmap_data=None, incomplete=False): ) elif not isinstance(nmap_data, str): raise NmapParserException( - "wrong nmap_data type given as " - "argument: cannot parse data" + "wrong nmap_data type given as argument: cannot parse data" ) if incomplete is True: @@ -112,8 +111,9 @@ def _parse_xml(cls, nmap_data=None, incomplete=False): elif root.tag == "port": nmapobj = cls._parse_xml_port(root) else: - raise NmapParserException("Unpexpected data structure for XML " - "root node") + raise NmapParserException( + "Unpexpected data structure for XML " "root node" + ) return nmapobj @classmethod @@ -132,12 +132,13 @@ def _parse_xml_report(cls, root=None): "_nmaprun": {}, "_scaninfo": {}, "_hosts": [], - "_runstats": {} + "_runstats": {}, } if root is None: - raise NmapParserException("No root node provided to parse XML " - "report") + raise NmapParserException( + "No root node provided to parse XML " "report" + ) nmap_scan["_nmaprun"] = cls.__format_attributes(root) for el in root: @@ -183,9 +184,7 @@ def parse_fromstring(cls, nmap_data, data_type="XML", incomplete=False): @classmethod def parse_fromfile( - cls, nmap_report_path, - data_type="XML", - incomplete=False + cls, nmap_report_path, data_type="XML", incomplete=False ): """ Call generic cls.parse() method and ensure that a correct file \ @@ -330,12 +329,8 @@ def _parse_xml_host(cls, scanhost_data): # 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"] + _stime = _host_header.get("starttime", "") + _etime = _host_header.get("endtime", "") nhost = NmapHost( _stime, _etime, @@ -343,7 +338,7 @@ def _parse_xml_host(cls, scanhost_data): _status, _hostnames, _services, - _host_extras + _host_extras, ) return nhost diff --git a/libnmap/plugins/es.py b/libnmap/plugins/es.py index c0c9d7e..311fb75 100644 --- a/libnmap/plugins/es.py +++ b/libnmap/plugins/es.py @@ -31,9 +31,9 @@ def insert(self, report, doc_type=None): if doc_type is None: doc_type = "NmapReport" j = json.dumps(report, cls=ReportEncoder) - res = self._esapi.index(index=self.index, - doc_type=doc_type, - body=json.loads(j)) + res = self._esapi.index( + index=self.index, doc_type=doc_type, body=json.loads(j) + ) rc = res["_id"] return rc @@ -50,10 +50,9 @@ def get(self, id): :param id: str :return: NmapReport """ - res = self._esapi.get(index=self.index, - doc_type="NmapReport", - id=id)["_source"] - return res + res = self._esapi.get(index=self.index, doc_type="NmapReport", id=id) + rc = res["_source"] + return rc def getall(self, filter=None): """ diff --git a/libnmap/plugins/sql.py b/libnmap/plugins/sql.py index afab24a..b754306 100644 --- a/libnmap/plugins/sql.py +++ b/libnmap/plugins/sql.py @@ -100,7 +100,7 @@ def __init__(self, **kwargs): Base.metadata.create_all(bind=self.engine, checkfirst=True) self.Session.configure(bind=self.engine) except Exception as e: - raise(e) + raise (e) def insert(self, nmap_report): """ diff --git a/libnmap/process.py b/libnmap/process.py index 4359848..83a5892 100644 --- a/libnmap/process.py +++ b/libnmap/process.py @@ -131,8 +131,9 @@ def __init__( 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") + 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() @@ -141,11 +142,13 @@ def __init__( 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.DONE, + self.READY, + self.RUNNING, + self.CANCELLED, + self.FAILED, + ) = range(5) self._run_init() def _run_init(self): @@ -179,10 +182,8 @@ def _whereis(self, program): 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)) - ): + _file_path = os.path.join(path, program) + if os.path.exists(_file_path) and not os.path.isdir(_file_path): return os.path.join(path, program) return None @@ -229,7 +230,7 @@ def sudo_run(self, run_as="root"): 2, "sudo is not installed or " "could not be found in system path: " - "cannot run nmap with sudo" + "cannot run nmap with sudo", ) self.__sudo_run = "{0} -u {1}".format(sudo_path, sudo_user) @@ -267,7 +268,7 @@ def sudo_run_background(self, run_as="root"): 2, "sudo is not installed or " "could not be found in system path: " - "cannot run nmap with sudo" + "cannot run nmap with sudo", ) self.__sudo_run = "{0} -u {1}".format(sudo_path, sudo_user) @@ -304,8 +305,9 @@ def run(self): except OSError: self.__state = self.FAILED raise EnvironmentError( - 1, "nmap is not installed or could " - "not be found in system path" + 1, + "nmap is not installed or could " + "not be found in system path", ) while self.__nmap_proc.poll() is None: @@ -354,9 +356,9 @@ def has_terminated(self): :return: True if nmap process is not running anymore. """ return ( - self.state == self.DONE or - self.state == self.FAILED or - self.state == self.CANCELLED + self.state == self.DONE + or self.state == self.FAILED + or self.state == self.CANCELLED ) def has_failed(self): @@ -406,9 +408,9 @@ def __process_event(self, eventdata): 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() + if ( + xmlnode.nodeName == "taskbegin" + and xmlnode.attributes.keys() ): xt = xmlnode.attributes taskname = xt["task"].value @@ -420,9 +422,9 @@ def __process_event(self, eventdata): self.__nmap_tasks[newtask.name] = newtask self.__current_task = newtask.name rval = True - elif( - xmlnode.nodeName == "taskend" and - xmlnode.attributes.keys() + elif ( + xmlnode.nodeName == "taskend" + and xmlnode.attributes.keys() ): xt = xmlnode.attributes tname = xt["task"].value @@ -434,8 +436,8 @@ def __process_event(self, eventdata): self.__nmap_tasks[tname].status = "ended" rval = True elif ( - xmlnode.nodeName == "taskprogress" and - xmlnode.attributes.keys() + xmlnode.nodeName == "taskprogress" + and xmlnode.attributes.keys() ): xt = xmlnode.attributes tname = xt["task"].value @@ -449,16 +451,16 @@ def __process_event(self, eventdata): self.__nmap_tasks[tname].remaining = remaining self.__nmap_tasks[tname].updated = updated rval = True - elif( - xmlnode.nodeName == "nmaprun" and - xmlnode.attributes.keys() + 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() + elif ( + xmlnode.nodeName == "finished" + and xmlnode.attributes.keys() ): self.__endtime = xmlnode.attributes["time"].value self.__elapsed = xmlnode.attributes["elapsed"].value @@ -669,14 +671,13 @@ def mycallback(nmapscan=None): ) nm = NmapProcess( - "scanme.nmap.org", - options="-A", - event_callback=mycallback + "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( + "Scan started at {0} nmap version: {1}".format( + nm.starttime, nm.version ) ) print("state: {0} (rc: {1})".format(nm.state, nm.rc)) diff --git a/libnmap/test/test_backend_plugin_factory.py b/libnmap/test/test_backend_plugin_factory.py index f396848..f5733ca 100644 --- a/libnmap/test/test_backend_plugin_factory.py +++ b/libnmap/test/test_backend_plugin_factory.py @@ -20,84 +20,63 @@ class TestNmapBackendPlugin(unittest.TestCase): with the necessary parameter in the urls table define in setUp All testcase must loop thru theses urls to validate a plugins """ + def setUp(self): fdir = os.path.dirname(os.path.realpath(__file__)) self.flist_full = [ + {"file": "{0}/{1}".format(fdir, "files/2_hosts.xml"), "hosts": 2}, + {"file": "{0}/{1}".format(fdir, "files/1_hosts.xml"), "hosts": 1}, { - 'file': "{0}/{1}".format(fdir, "files/2_hosts.xml"), - 'hosts': 2 - }, - { - 'file': "{0}/{1}".format(fdir, "files/1_hosts.xml"), - 'hosts': 1 + "file": "{0}/{1}".format( + fdir, "files/1_hosts_banner_ports_notsyn.xml" + ), + "hosts": 1, }, { - 'file': "{0}/{1}".format( - fdir, - "files/1_hosts_banner_ports_notsyn.xml" - ), - 'hosts': 1 + "file": "{0}/{1}".format( + fdir, "files/1_hosts_banner_ports.xml" + ), + "hosts": 1, }, { - 'file': "{0}/{1}".format( - fdir, - 'files/1_hosts_banner_ports.xml' - ), - 'hosts': 1 + "file": "{0}/{1}".format(fdir, "files/1_hosts_banner.xml"), + "hosts": 1, }, { - 'file': "{0}/{1}".format( - fdir, - 'files/1_hosts_banner.xml' - ), - 'hosts': 1 + "file": "{0}/{1}".format(fdir, "files/2_hosts_version.xml"), + "hosts": 2, }, { - 'file': "{0}/{1}".format( - fdir, - 'files/2_hosts_version.xml' - ), - 'hosts': 2 + "file": "{0}/{1}".format(fdir, "files/2_tcp_hosts.xml"), + "hosts": 2, }, { - 'file': "{0}/{1}".format( - fdir, - 'files/2_tcp_hosts.xml' - ), - 'hosts': 2 + "file": "{0}/{1}".format(fdir, "files/1_hosts_nohostname.xml"), + "hosts": 1, }, - { - 'file': "{0}/{1}".format( - fdir, - 'files/1_hosts_nohostname.xml' - ), - 'hosts': 1 - } ] self.flist = self.flist_full # build a list of NmapReport self.reportList = [] for testfile in self.flist: - fd = open(testfile['file'], 'r') + fd = open(testfile["file"], "r") s = fd.read() fd.close() nrp = NmapParser.parse(s) self.reportList.append(nrp) self.urls = [ + {"plugin_name": "mongodb"}, { - 'plugin_name': "mongodb" + "plugin_name": "sql", + "url": "sqlite:////tmp/reportdb.sql", + "echo": False, }, { - 'plugin_name': 'sql', - 'url': 'sqlite:////tmp/reportdb.sql', - 'echo': False + "plugin_name": "sql", + "url": "mysql+pymysql://root@localhost/poulet", + "echo": False, }, - { - 'plugin_name': 'sql', - 'url': 'mysql+pymysql://root@localhost/poulet', - 'echo': False - } ] def test_backend_factory(self): @@ -107,7 +86,7 @@ def test_backend_factory(self): for url in self.urls: backend = BackendPluginFactory.create(**url) self.assertEqual(isinstance(backend, NmapBackendPlugin), True) - className = "Nmap%sPlugin" % url['plugin_name'].title() + className = "Nmap%sPlugin" % url["plugin_name"].title() self.assertEqual(backend.__class__.__name__, className, True) def test_backend_insert(self): @@ -163,13 +142,13 @@ def test_backend_delete(self): result_list = [] -if __name__ == '__main__': +if __name__ == "__main__": test_suite = [ - 'test_backend_factory', - 'test_backend_insert', - 'test_backend_get', - 'test_backend_getall', - 'test_backend_delete' + "test_backend_factory", + "test_backend_insert", + "test_backend_get", + "test_backend_getall", + "test_backend_delete", ] suite = unittest.TestSuite(map(TestNmapBackendPlugin, test_suite)) test_result = unittest.TextTestRunner(verbosity=5).run(suite) diff --git a/libnmap/test/test_defusedxml.py b/libnmap/test/test_defusedxml.py index 99be84d..17c7116 100644 --- a/libnmap/test/test_defusedxml.py +++ b/libnmap/test/test_defusedxml.py @@ -25,18 +25,32 @@ def setUp(self): &lol9; """ self.fdir = os.path.dirname(os.path.realpath(__file__)) - self.billionlaugh_file = "{0}/files/{1}".format(self.fdir, "billion_laugh.xml") - self.external_entities_file = "{0}/files/{1}".format(self.fdir, "defused_et_local_includer.xml") + self.billionlaugh_file = "{0}/files/{1}".format( + self.fdir, "billion_laugh.xml" + ) + self.external_entities_file = "{0}/files/{1}".format( + self.fdir, "defused_et_local_includer.xml" + ) def test_billion_laugh(self): - self.assertRaisesRegex(NmapParserException, ".*EntitiesForbidden", NmapParser.parse_fromstring, self.billionlaugh) + self.assertRaisesRegex( + NmapParserException, + ".*EntitiesForbidden", + NmapParser.parse_fromstring, + self.billionlaugh, + ) def test_external_entities(self): - self.assertRaisesRegex(NmapParserException, ".*EntitiesForbidden", NmapParser.parse_fromfile, self.external_entities_file) + self.assertRaisesRegex( + NmapParserException, + ".*EntitiesForbidden", + NmapParser.parse_fromfile, + self.external_entities_file, + ) if __name__ == "__main__": - #test_suite = ["test_external_entities"] - test_suite = ["test_billion_laugh", "test_external_entities" ] + # test_suite = ["test_external_entities"] + test_suite = ["test_billion_laugh", "test_external_entities"] suite = unittest.TestSuite(map(TestDefusedXML, test_suite)) test_result = unittest.TextTestRunner(verbosity=2).run(suite) diff --git a/libnmap/test/test_fp.py b/libnmap/test/test_fp.py index 7f5b069..d3deb2a 100644 --- a/libnmap/test/test_fp.py +++ b/libnmap/test/test_fp.py @@ -10,10 +10,14 @@ class TestNmapFP(unittest.TestCase): def setUp(self): fdir = os.path.dirname(os.path.realpath(__file__)) self.flist_full = [ - {"file": "%s/%s" % (fdir, "files/1_os_banner_scripts.xml"), "os": 1}, + { + "file": "%s/%s" % (fdir, "files/1_os_banner_scripts.xml"), + "os": 1, + }, {"file": "%s/%s" % (fdir, "files/2_hosts_version.xml"), "os": 1}, { - "file": "%s/%s" % (fdir, "files/1_hosts_banner_ports_notsyn.xml"), + "file": "%s/%s" + % (fdir, "files/1_hosts_banner_ports_notsyn.xml"), "os": 0, }, {"file": "%s/%s" % (fdir, "files/1_hosts_banner.xml"), "os": 0}, @@ -22,10 +26,15 @@ def setUp(self): self.flist = self.flist_full self.flist_os = { "nv6": {"file": "%s/%s" % (fdir, "files/full_sudo6.xml"), "os": 0}, - "fullscan": {"file": "%s/%s" % (fdir, "files/fullscan.xml"), "os": 0}, + "fullscan": { + "file": "%s/%s" % (fdir, "files/fullscan.xml"), + "os": 0, + }, "nv5": {"file": "%s/%s" % (fdir, "files/os_scan5.xml"), "os": 0}, } - self.fos_class_probabilities = "{0}/{1}".format(fdir, "files/test_osclass.xml") + self.fos_class_probabilities = "{0}/{1}".format( + fdir, "files/test_osclass.xml" + ) def test_fp(self): for file_e in self.flist_full: @@ -190,7 +199,11 @@ def test_osmatches_new(self): "accuracy": 100, "name": "Microsoft Windows 7 Professional", }, - {"line": 54362, "accuracy": 100, "name": "Microsoft Windows Phone 7.5"}, + { + "line": 54362, + "accuracy": 100, + "name": "Microsoft Windows Phone 7.5", + }, { "line": 54897, "accuracy": 100, @@ -207,7 +220,11 @@ def test_osmatches_new(self): j = 0 for h in hlist: for om in h.os.osmatches: - tdict = {"line": om.line, "accuracy": om.accuracy, "name": om.name} + tdict = { + "line": om.line, + "accuracy": om.accuracy, + "name": om.name, + } self.assertEqual(baseline[i][j], tdict) j += 1 j = 0 @@ -217,9 +234,17 @@ def test_osmatches_old(self): rep = NmapParser.parse_fromfile(self.flist_os["nv5"]["file"]) h1 = rep.hosts[4] h1osmatches = [ - {"line": -1, "accuracy": 95, "name": "general purpose:Linux:Linux"}, + { + "line": -1, + "accuracy": 95, + "name": "general purpose:Linux:Linux", + }, {"line": -1, "accuracy": 90, "name": "WAP:Gemtek:embedded"}, - {"line": -1, "accuracy": 89, "name": "general purpose:Nokia:Linux"}, + { + "line": -1, + "accuracy": 89, + "name": "general purpose:Nokia:Linux", + }, {"line": -1, "accuracy": 88, "name": "webcam:AXIS:Linux"}, ] diff --git a/libnmap/test/test_host.py b/libnmap/test/test_host.py index 2697b56..df361d1 100644 --- a/libnmap/test/test_host.py +++ b/libnmap/test/test_host.py @@ -179,7 +179,8 @@ def test_extra_ports(self): h2 = NmapParser.parse(host2) self.assertEqual( - h1.extraports_state["state"], {"count": "995", "state": "WILLY_WONCKA"} + h1.extraports_state["state"], + {"count": "995", "state": "WILLY_WONCKA"}, ) self.assertEqual( h1.extraports_reasons, [{"reason": "conn-refused", "count": "995"}] @@ -220,7 +221,9 @@ def test_diff_host(self): ), ) - self.assertEqual(c2.changed(), set(["status", "NmapService::tcp.3306"])) + self.assertEqual( + c2.changed(), set(["status", "NmapService::tcp.3306"]) + ) self.assertEqual(c2.added(), set(["NmapService::tcp.25"])) self.assertEqual(c2.removed(), set(["NmapService::tcp.3307"])) self.assertEqual( diff --git a/libnmap/test/test_new_parser.py b/libnmap/test/test_new_parser.py index 0370ccc..2a094ab 100644 --- a/libnmap/test/test_new_parser.py +++ b/libnmap/test/test_new_parser.py @@ -19,9 +19,15 @@ class TestNmapParser(unittest.TestCase): def test_parse(self): for baddata in baddatalist: - self.assertRaises(NmapParserException, NmapParser.parse, baddata, "zz") - self.assertRaises(NmapParserException, NmapParser.parse, baddata, "XML") - self.assertRaises(NmapParserException, NmapParser.parse, baddata, "YAML") + self.assertRaises( + NmapParserException, NmapParser.parse, baddata, "zz" + ) + self.assertRaises( + NmapParserException, NmapParser.parse, baddata, "XML" + ) + self.assertRaises( + NmapParserException, NmapParser.parse, baddata, "YAML" + ) if __name__ == "__main__": diff --git a/libnmap/test/test_parser.py b/libnmap/test/test_parser.py index 599af95..e1eae50 100644 --- a/libnmap/test/test_parser.py +++ b/libnmap/test/test_parser.py @@ -13,19 +13,29 @@ def setUp(self): {"file": "%s/%s" % (fdir, "files/2_hosts.xml"), "hosts": 2}, {"file": "%s/%s" % (fdir, "files/1_hosts.xml"), "hosts": 1}, { - "file": "%s/%s" % (fdir, "files/1_hosts_banner_ports_notsyn.xml"), + "file": "%s/%s" + % (fdir, "files/1_hosts_banner_ports_notsyn.xml"), "hosts": 1, }, # {'file': "%s/%s" % (fdir, # 'files/1_hosts_banner_ports_xmas.xml'), # 'hosts': 1}, - {"file": "%s/%s" % (fdir, "files/1_hosts_banner_ports.xml"), "hosts": 1}, + { + "file": "%s/%s" % (fdir, "files/1_hosts_banner_ports.xml"), + "hosts": 1, + }, {"file": "%s/%s" % (fdir, "files/1_hosts_banner.xml"), "hosts": 1}, - {"file": "%s/%s" % (fdir, "files/2_hosts_version.xml"), "hosts": 2}, + { + "file": "%s/%s" % (fdir, "files/2_hosts_version.xml"), + "hosts": 2, + }, # {'file': "%s/%s" % (fdir, # 'files/2_null_hosts.xml'), 'hosts': 2}, {"file": "%s/%s" % (fdir, "files/2_tcp_hosts.xml"), "hosts": 2}, - {"file": "%s/%s" % (fdir, "files/1_hosts_nohostname.xml"), "hosts": 1}, + { + "file": "%s/%s" % (fdir, "files/1_hosts_nohostname.xml"), + "hosts": 1, + }, ] self.flist = self.flist_full @@ -123,7 +133,9 @@ def test_class_ports_parser(self): 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])) + self.assertEqual( + sorted([p.port for p in plist]), sorted([22, 25, 9929, 80]) + ) self.assertRaises(ValueError, NmapParser.parse, self.ports_string2) def test_class_port_parser(self): @@ -138,12 +150,22 @@ def test_class_port_parser(self): def test_port_except(self): self.assertRaises(ValueError, NmapParser.parse, self.port_string2) - self.assertRaises(NmapParserException, NmapParser.parse, self.port_string3) - self.assertRaises(NmapParserException, NmapParser.parse, self.port_string4) - self.assertRaises(NmapParserException, NmapParser.parse, self.port_string5) + self.assertRaises( + NmapParserException, NmapParser.parse, self.port_string3 + ) + self.assertRaises( + NmapParserException, NmapParser.parse, self.port_string4 + ) + self.assertRaises( + NmapParserException, NmapParser.parse, self.port_string5 + ) self.assertRaises(ValueError, NmapParser.parse, self.port_string6) - self.assertRaises(NmapParserException, NmapParser.parse, self.port_string7) - self.assertRaises(NmapParserException, NmapParser.parse, self.port_string8) + self.assertRaises( + NmapParserException, NmapParser.parse, self.port_string7 + ) + self.assertRaises( + NmapParserException, NmapParser.parse, self.port_string8 + ) serv = NmapParser.parse(self.port_string9) self.assertEqual(serv.state, None) diff --git a/libnmap/test/test_report.py b/libnmap/test/test_report.py index 6d0cbc0..d9f2858 100644 --- a/libnmap/test/test_report.py +++ b/libnmap/test/test_report.py @@ -16,18 +16,31 @@ def setUp(self): {"file": "%s/%s" % (fdir, "files/2_hosts.xml"), "hosts": 2}, {"file": "%s/%s" % (fdir, "files/1_hosts.xml"), "hosts": 1}, { - "file": "%s/%s" % (fdir, "files/1_hosts_banner_ports_notsyn.xml"), + "file": "%s/%s" + % (fdir, "files/1_hosts_banner_ports_notsyn.xml"), + "hosts": 1, + }, + { + "file": "%s/%s" % (fdir, "files/1_hosts_banner_ports.xml"), "hosts": 1, }, - {"file": "%s/%s" % (fdir, "files/1_hosts_banner_ports.xml"), "hosts": 1}, {"file": "%s/%s" % (fdir, "files/1_hosts_banner.xml"), "hosts": 1}, - {"file": "%s/%s" % (fdir, "files/2_hosts_version.xml"), "hosts": 2}, + { + "file": "%s/%s" % (fdir, "files/2_hosts_version.xml"), + "hosts": 2, + }, {"file": "%s/%s" % (fdir, "files/2_tcp_hosts.xml"), "hosts": 2}, - {"file": "%s/%s" % (fdir, "files/1_hosts_nohostname.xml"), "hosts": 1}, + { + "file": "%s/%s" % (fdir, "files/1_hosts_nohostname.xml"), + "hosts": 1, + }, ] self.flist_one = [ - {"file": "%s/%s" % (fdir, "files/1_hosts_nohostname.xml"), "hosts": 1} + { + "file": "%s/%s" % (fdir, "files/1_hosts_nohostname.xml"), + "hosts": 1, + } ] self.flist_two = [ { @@ -55,8 +68,14 @@ def setUp(self): "banner": { "631": "product: CUPS version: 1.4", "3306": "product: MySQL version: 5.1.61", - "22": ("product: OpenSSH version: 5.3" " extrainfo: protocol 2.0"), - "25": ("product: Postfix smtpd" " hostname: jambon.localdomain"), + "22": ( + "product: OpenSSH version: 5.3" + " extrainfo: protocol 2.0" + ), + "25": ( + "product: Postfix smtpd" + " hostname: jambon.localdomain" + ), "111": "", }, } @@ -75,7 +94,9 @@ def test_report_constructor(self): self.assertEqual(len(nr.hosts), testfile["hosts"]) self.assertEqual(len(nr2.hosts), testfile["hosts"]) - self.assertEqual(sorted(nr2.get_raw_data()), sorted(nr.get_raw_data())) + self.assertEqual( + sorted(nr2.get_raw_data()), sorted(nr.get_raw_data()) + ) def test_get_ports(self): for testfile in self.flist: @@ -104,7 +125,9 @@ def test_runstats(self): nr = NmapParser.parse(s) self.assertEqual(getattr(nr, "endtime"), int(testfile["endtime"])) self.assertEqual(getattr(nr, "summary"), testfile["summary"]) - self.assertEqual(getattr(nr, "elapsed"), float(testfile["elapsed"])) + self.assertEqual( + getattr(nr, "elapsed"), float(testfile["elapsed"]) + ) def test_banner(self): for testfile in self.flist_banner: @@ -131,7 +154,9 @@ def test_service_equal(self): """All the service of the host must be compared and the hash should be also the same""" for i in range(len(host1.services)): - self.assertEqual(hash(host1.services[i]), hash(host2.services[i])) + self.assertEqual( + hash(host1.services[i]), hash(host2.services[i]) + ) self.assertEqual(host1.services[i], host2.services[i]) # print host1.serviceChanged(host2) diff --git a/libnmap/test/test_report_diff.py b/libnmap/test/test_report_diff.py index 4dc225c..69430cc 100644 --- a/libnmap/test/test_report_diff.py +++ b/libnmap/test/test_report_diff.py @@ -20,15 +20,26 @@ def test_diff_host_list(self): r1 = NmapParser.parse_fromfile("%s/%s" % (fdir, "files/1_hosts.xml")) r2 = NmapParser.parse_fromfile("%s/%s" % (fdir, "files/2_hosts.xml")) r3 = NmapParser.parse_fromfile("%s/%s" % (fdir, "files/1_hosts.xml")) - r4 = NmapParser.parse_fromfile("%s/%s" % (fdir, "files/2_hosts_achange.xml")) + r4 = NmapParser.parse_fromfile( + "%s/%s" % (fdir, "files/2_hosts_achange.xml") + ) d1 = r1.diff(r2) self.assertEqual( d1.changed(), - set(["hosts_total", "commandline", "hosts_up", "scan_type", "elapsed"]), + set( + [ + "hosts_total", + "commandline", + "hosts_up", + "scan_type", + "elapsed", + ] + ), ) self.assertEqual( - d1.unchanged(), set(["hosts_down", "version", "NmapHost::127.0.0.1"]) + d1.unchanged(), + set(["hosts_down", "version", "NmapHost::127.0.0.1"]), ) self.assertEqual(d1.removed(), set(["NmapHost::74.207.244.221"])) diff --git a/libnmap/test/test_service.py b/libnmap/test/test_service.py index b08f902..bab5ef3 100644 --- a/libnmap/test/test_service.py +++ b/libnmap/test/test_service.py @@ -188,7 +188,9 @@ def test_port_state_changed(self): self.assertRaises(NmapDiffException, nservice1.diff, nservice4) # self.assertRaises(NmapDiffException, nservice2.diff, nservice3) - self.assertEqual(nservice3.diff(nservice4).changed(), set(["state", "service"])) + self.assertEqual( + nservice3.diff(nservice4).changed(), set(["state", "service"]) + ) def test_port_state_unchanged(self): nservice1 = NmapParser.parse(port_string) diff --git a/tox.ini b/tox.ini index a3edd8f..cf593ab 100644 --- a/tox.ini +++ b/tox.ini @@ -30,11 +30,9 @@ commands = pycodestyle --exclude test,docs,examples,.tox . [testenv:formatting] -basepython = python3 deps = black==20.8b1 isort==5.5.2 commands = - black --check . --exclude="tests/|venv|.tox" - isort . --check-only -changedir = {toxinidir} \ No newline at end of file + black -l 79 --check . --exclude="tests/|venv|.tox" + isort . --check-only \ No newline at end of file From ed32cac0d1ba895345ba98e20755ba53e0c6c5f2 Mon Sep 17 00:00:00 2001 From: Ronald Bister Date: Tue, 24 Nov 2020 00:02:57 +0100 Subject: [PATCH 11/88] changed to Apache 2.0 licence --- LICENCE | 13 +++++++++++++ LICENSE.txt | 19 ------------------- TODO | 10 +++++++--- docs/conf.py | 6 +++--- libnmap/__init__.py | 4 ++-- setup.py | 9 +++++++-- 6 files changed, 32 insertions(+), 29 deletions(-) create mode 100644 LICENCE delete mode 100644 LICENSE.txt diff --git a/LICENCE b/LICENCE new file mode 100644 index 0000000..92138ca --- /dev/null +++ b/LICENCE @@ -0,0 +1,13 @@ + Copyright 2020 Ronald Bister + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. \ No newline at end of file diff --git a/LICENSE.txt b/LICENSE.txt deleted file mode 100644 index d0e33ff..0000000 --- a/LICENSE.txt +++ /dev/null @@ -1,19 +0,0 @@ -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 diff --git a/TODO b/TODO index 7185fd4..8283536 100644 --- a/TODO +++ b/TODO @@ -2,9 +2,13 @@ 0.7.1: - add unittest for defusedxml - billionlaugh and external entities 0.7.1: - add CSV backend support -0.7.1: - Change License -- improve API for NSE scripts -- add support for post,pre and host scripts +0.7.1: - Change License to MIT, BSD or Apache +0.7.1: - Update readme file to reflect the xml fixed issue + how to install it +0.7.1: - add extra_requires for plugins deps and defusedxml +0.7.2: improve API for NSE scripts +0.7.2: add support for post,pre and host scripts +0.7.2: add a Contribution guideline page +0.7.2: automate in github actions the git workflow + doc update + pypi update - complete unit tests with coverall support - Add new plugins to support import/export from mysql, couchdb, csv - add unittest for udp scans, ping sweeping diff --git a/docs/conf.py b/docs/conf.py index 1933a3a..712cc25 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -41,16 +41,16 @@ # General information about the project. project = u"libnmap" -copyright = u"CC-BY 2013, Ronald Bister" +copyright = u"Apache 2.0 2020, Ronald Bister" # The version info for the project you're documenting, acts as replacement for # |version| and |release|, also used in various other places throughout the # built documents. # # The short X.Y version. -version = "0.2" +version = "0.7" # The full version, including alpha/beta/rc tags. -release = "0.2" +release = "0.7.1" # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. diff --git a/libnmap/__init__.py b/libnmap/__init__.py index 2ecebe8..b3b5f89 100644 --- a/libnmap/__init__.py +++ b/libnmap/__init__.py @@ -4,5 +4,5 @@ __credits__ = ["Ronald Bister", "Mike Boutillier"] __maintainer__ = "Ronald Bister" __email__ = "mini.pelle@gmail.com" -__license__ = "CC-BY" -__version__ = "0.6.1" +__license__ = "Apache 2.0" +__version__ = "0.7.1" diff --git a/setup.py b/setup.py index 37f4c55..a0d3824 100644 --- a/setup.py +++ b/setup.py @@ -11,23 +11,28 @@ 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', + extras_require={ + 'defusedxml': ['defusedxml>=0.6.0'], + }, + license="Apache 2.0", description=( "Python NMAP library enabling you to start async nmap tasks, " "parse and compare/diff scan results" ), long_description=long_description, classifiers=[ + "License :: OSI Approved :: Apache Software License", "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", "Programming Language :: Python :: 3.5", "Programming Language :: Python :: 3.6", + "Programming Language :: Python :: 3.7", + "Programming Language :: Python :: 3.8", "Topic :: System :: Networking", ], ) From fbded5da0297609f8acc65dd3c0abf6112930c29 Mon Sep 17 00:00:00 2001 From: Ronald Bister Date: Tue, 24 Nov 2020 21:58:47 +0100 Subject: [PATCH 12/88] fix for travis issue in testing --- .travis.yml | 3 ++- MANIFEST | 1 - libnmap/test/test_defusedxml.py | 13 +++++++++---- tox.ini | 3 ++- 4 files changed, 13 insertions(+), 7 deletions(-) diff --git a/.travis.yml b/.travis.yml index f3c3196..7030496 100644 --- a/.travis.yml +++ b/.travis.yml @@ -17,6 +17,7 @@ before_install: install: - "pip install flake8" - "pip install defusedxml" + - "pip install pytest" # - "pip install boto" # disabled: since boto not supporting py3 # - "pip install pymongo sqlalchemy MySQL-python" # disabled MySQL-python (not py3 compatible) # - "pip install pymongo sqlalchemy pymysql" @@ -25,7 +26,7 @@ install: before_script: - "flake8 . --exclude test,docs,examples" # - mysql -e 'create database poulet;' -script: nosetests --with-coverage --cover-package=libnmap +script: pytest --ignore=libnmap/test/test_backend_plugin_factory.py . after_success: coveralls deploy: diff --git a/MANIFEST b/MANIFEST index 35cde81..7dcc9c7 100644 --- a/MANIFEST +++ b/MANIFEST @@ -1,6 +1,5 @@ # file GENERATED by distutils, do NOT edit CHANGES.txt -LICENSE.txt README.rst TODO setup.py diff --git a/libnmap/test/test_defusedxml.py b/libnmap/test/test_defusedxml.py index 17c7116..db2bf26 100644 --- a/libnmap/test/test_defusedxml.py +++ b/libnmap/test/test_defusedxml.py @@ -4,10 +4,15 @@ from libnmap.parser import NmapParser, NmapParserException import unittest import os - +import sys class TestDefusedXML(unittest.TestCase): def setUp(self): + if int(sys.version[0]) == 3: + self._assertRaisesRegex = self.assertRaisesRegex + else: + self._assertRaisesRegex = self.assertRaisesRegexp + self.billionlaugh = """ @@ -32,8 +37,8 @@ def setUp(self): self.fdir, "defused_et_local_includer.xml" ) - def test_billion_laugh(self): - self.assertRaisesRegex( + def test_billion_laugh(self): + self._assertRaisesRegex( NmapParserException, ".*EntitiesForbidden", NmapParser.parse_fromstring, @@ -41,7 +46,7 @@ def test_billion_laugh(self): ) def test_external_entities(self): - self.assertRaisesRegex( + self._assertRaisesRegex( NmapParserException, ".*EntitiesForbidden", NmapParser.parse_fromfile, diff --git a/tox.ini b/tox.ini index cf593ab..283b18e 100644 --- a/tox.ini +++ b/tox.ini @@ -3,7 +3,8 @@ envlist = py27, py32, py38, flake8, pycodestyle, formatting, defusedxml [testenv] deps=pytest -commands=pytest --ignore=libnmap/test/test_defusedxml.py --ignore=libnmap/test/test_backend_plugin_factory.py + defusedxml +commands=pytest --ignore=libnmap/test/test_backend_plugin_factory.py --ignore=libnmap/test/test_defusedxml.py [testenv:defusedxml] deps=pytest From 286f00e708f104c678667d2cd342f4a94d06ad5b Mon Sep 17 00:00:00 2001 From: Ronald Bister Date: Wed, 25 Nov 2020 09:22:18 +0100 Subject: [PATCH 13/88] another run of py-black with updated version + isort + fix travis --- .travis.yml | 3 +- docs/conf.py | 15 +- examples/elastikibana.py | 6 +- examples/es_plugin.py | 7 +- examples/json_serialize.py | 3 +- examples/proc_async.py | 2 +- examples/proc_nmap_like.py | 2 +- libnmap/diff.py | 58 ++--- libnmap/objects/__init__.py | 2 +- libnmap/objects/cpe.py | 32 +-- libnmap/objects/host.py | 228 +++++++++--------- libnmap/objects/os.py | 157 ++++++------ libnmap/objects/report.py | 174 ++++++------- libnmap/objects/service.py | 144 +++++------ libnmap/parser.py | 194 +++++++-------- libnmap/plugins/backendplugin.py | 28 +-- libnmap/plugins/backendpluginFactory.py | 18 +- libnmap/plugins/es.py | 32 +-- libnmap/plugins/mongodb.py | 37 +-- libnmap/plugins/s3.py | 94 ++++---- libnmap/plugins/sql.py | 118 ++++----- libnmap/process.py | 4 +- libnmap/reportjson.py | 12 +- .../test/process-stressbox/check_fqp_nmap.py | 2 +- .../multi_nmap_process_background.py | 3 +- libnmap/test/process-stressbox/proc_async.py | 2 +- .../test/process-stressbox/proc_nmap_like.py | 2 +- libnmap/test/process-stressbox/stop_scan.py | 2 +- libnmap/test/process-stressbox/stressback.py | 3 +- libnmap/test/process-stressbox/stresstest.py | 2 +- libnmap/test/test_backend_plugin_factory.py | 25 +- libnmap/test/test_cpe.py | 3 +- libnmap/test/test_defusedxml.py | 8 +- libnmap/test/test_fp.py | 3 +- libnmap/test/test_host.py | 1 + libnmap/test/test_new_parser.py | 1 + libnmap/test/test_parser.py | 3 +- libnmap/test/test_report.py | 5 +- libnmap/test/test_report_diff.py | 3 +- libnmap/test/test_service.py | 3 +- setup.py | 2 +- tox.ini | 4 +- 42 files changed, 744 insertions(+), 703 deletions(-) diff --git a/.travis.yml b/.travis.yml index 7030496..9e7dd94 100644 --- a/.travis.yml +++ b/.travis.yml @@ -18,6 +18,7 @@ install: - "pip install flake8" - "pip install defusedxml" - "pip install pytest" + - "pip install pytest-cov" # - "pip install boto" # disabled: since boto not supporting py3 # - "pip install pymongo sqlalchemy MySQL-python" # disabled MySQL-python (not py3 compatible) # - "pip install pymongo sqlalchemy pymysql" @@ -26,7 +27,7 @@ install: before_script: - "flake8 . --exclude test,docs,examples" # - mysql -e 'create database poulet;' -script: pytest --ignore=libnmap/test/test_backend_plugin_factory.py . +script: pytest --cov=libnmap/ --ignore=libnmap/test/test_backend_plugin_factory.py . after_success: coveralls deploy: diff --git a/docs/conf.py b/docs/conf.py index 712cc25..07e9dc8 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -11,7 +11,8 @@ # All configuration values have a default; values that are commented out # serve to show the default. -import sys, os +import os +import sys # If extensions (or modules to document with autodoc) are in another directory, # add these directories to sys.path here. If the directory is relative to the @@ -184,7 +185,13 @@ # Grouping the document tree into LaTeX files. List of tuples # (source start file, target name, title, author, documentclass [howto/manual]). latex_documents = [ - ("index", "libnmap.tex", u"libnmap Documentation", u"Ronald Bister", "manual") + ( + "index", + "libnmap.tex", + u"libnmap Documentation", + u"Ronald Bister", + "manual", + ) ] # The name of an image file (relative to this directory) to place at the top of @@ -212,7 +219,9 @@ # One entry per manual page. List of tuples # (source start file, name, description, authors, manual section). -man_pages = [("index", "libnmap", u"libnmap Documentation", [u"Ronald Bister"], 1)] +man_pages = [ + ("index", "libnmap", u"libnmap Documentation", [u"Ronald Bister"], 1) +] # If true, show URL addresses after external links. # man_show_urls = False diff --git a/examples/elastikibana.py b/examples/elastikibana.py index f91cadc..3be6487 100644 --- a/examples/elastikibana.py +++ b/examples/elastikibana.py @@ -1,10 +1,12 @@ #!/usr/bin/env python # -*- coding: utf-8 -*- -from libnmap.parser import NmapParser -from elasticsearch import Elasticsearch from datetime import datetime + import pygeoip +from elasticsearch import Elasticsearch + +from libnmap.parser import NmapParser def store_report(nmap_report, database, index): diff --git a/examples/es_plugin.py b/examples/es_plugin.py index 4bf2cbe..6474399 100644 --- a/examples/es_plugin.py +++ b/examples/es_plugin.py @@ -1,10 +1,11 @@ #!/usr/bin/env python +import json +from datetime import datetime + from libnmap.parser import NmapParser -from libnmap.reportjson import ReportDecoder from libnmap.plugins.es import NmapElasticsearchPlugin -from datetime import datetime -import json +from libnmap.reportjson import ReportDecoder nmap_report = NmapParser.parse_fromfile("libnmap/test/files/1_hosts.xml") mindex = datetime.fromtimestamp(nmap_report.started).strftime("%Y-%m-%d") diff --git a/examples/json_serialize.py b/examples/json_serialize.py index 4e70b16..b7d5148 100644 --- a/examples/json_serialize.py +++ b/examples/json_serialize.py @@ -1,9 +1,10 @@ #!/usr/bin/env python # -*- coding: utf-8 -*- +import json + from libnmap.parser import NmapParser from libnmap.reportjson import ReportDecoder, ReportEncoder -import json nmap_report_obj = NmapParser.parse_fromfile("libnmap/test/files/1_hosts.xml") diff --git a/examples/proc_async.py b/examples/proc_async.py index 868f808..01c167c 100644 --- a/examples/proc_async.py +++ b/examples/proc_async.py @@ -1,9 +1,9 @@ #!/usr/bin/env python # -*- coding: utf-8 -*- -from libnmap.process import NmapProcess from time import sleep +from libnmap.process import NmapProcess nmap_proc = NmapProcess(targets="scanme.nmap.org", options="-sT") nmap_proc.run_background() diff --git a/examples/proc_nmap_like.py b/examples/proc_nmap_like.py index bd85cfd..bed93fe 100644 --- a/examples/proc_nmap_like.py +++ b/examples/proc_nmap_like.py @@ -1,8 +1,8 @@ #!/usr/bin/env python # -*- coding: utf-8 -*- -from libnmap.process import NmapProcess from libnmap.parser import NmapParser, NmapParserException +from libnmap.process import NmapProcess # start a new nmap scan on localhost with some specific options diff --git a/libnmap/diff.py b/libnmap/diff.py index d2e8e34..432aa2f 100644 --- a/libnmap/diff.py +++ b/libnmap/diff.py @@ -3,11 +3,11 @@ 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 + 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): @@ -40,35 +40,35 @@ def unchanged(self): 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,...). + 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: + 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 + - 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__ diff --git a/libnmap/objects/__init__.py b/libnmap/objects/__init__.py index b7f24e4..ddf3699 100644 --- a/libnmap/objects/__init__.py +++ b/libnmap/objects/__init__.py @@ -1,7 +1,7 @@ # -*- coding: utf-8 -*- -from libnmap.objects.report import NmapReport from libnmap.objects.host import NmapHost +from libnmap.objects.report import NmapReport from libnmap.objects.service import NmapService __all__ = ["NmapReport", "NmapHost", "NmapService"] diff --git a/libnmap/objects/cpe.py b/libnmap/objects/cpe.py index 8b6e497..9476444 100644 --- a/libnmap/objects/cpe.py +++ b/libnmap/objects/cpe.py @@ -3,11 +3,11 @@ class CPE(object): """ - CPE class offers an API for basic CPE objects. - These objects could be found in NmapService or in tag - within NmapHost. + CPE class offers an API for basic CPE objects. + These objects could be found in NmapService or in tag + within NmapHost. - :todo: interpret CPE string and provide appropriate API + :todo: interpret CPE string and provide appropriate API """ def __init__(self, cpestring): @@ -29,14 +29,14 @@ def __init__(self, cpestring): @property def cpestring(self): """ - Accessor for the full CPE string. + Accessor for the full CPE string. """ return self._cpestring @property def cpedict(self): """ - Accessor for _cpedict + Accessor for _cpedict """ return self._cpedict @@ -45,60 +45,60 @@ def __repr__(self): def get_part(self): """ - Returns the cpe part (/o, /h, /a) + Returns the cpe part (/o, /h, /a) """ return self._cpedict["part"] def get_vendor(self): """ - Returns the vendor name + Returns the vendor name """ return self._cpedict["vendor"] def get_product(self): """ - Returns the product name + Returns the product name """ return self._cpedict["product"] def get_version(self): """ - Returns the version of the cpe + Returns the version of the cpe """ return self._cpedict["version"] def get_update(self): """ - Returns the update version + Returns the update version """ return self._cpedict["update"] def get_edition(self): """ - Returns the cpe edition + Returns the cpe edition """ return self._cpedict["edition"] def get_language(self): """ - Returns the cpe language + Returns the cpe language """ return self._cpedict["language"] def is_application(self): """ - Returns True if cpe describes an application + Returns True if cpe describes an application """ return self.get_part() == "/a" def is_hardware(self): """ - Returns True if cpe describes a hardware + Returns True if cpe describes a hardware """ return self.get_part() == "/h" def is_operating_system(self): """ - Returns True if cpe describes an operating system + Returns True if cpe describes an operating system """ return self.get_part() == "/o" diff --git a/libnmap/objects/host.py b/libnmap/objects/host.py index 980682f..a8e1ba6 100644 --- a/libnmap/objects/host.py +++ b/libnmap/objects/host.py @@ -6,7 +6,7 @@ class NmapHost(object): """ - NmapHost is a class representing a host object of NmapReport + NmapHost is a class representing a host object of NmapReport """ def __init__( @@ -20,17 +20,17 @@ def __init__( 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: + 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 @@ -65,13 +65,13 @@ def __init__( def __eq__(self, other): """ - Compare eq NmapHost based on : + Compare eq NmapHost based on : - - hostnames - - address - - if an associated services has changed + - hostnames + - address + - if an associated services has changed - :return: boolean + :return: boolean """ rval = False if self.__class__ == other.__class__ and self.id == other.id: @@ -80,13 +80,13 @@ def __eq__(self, other): def __ne__(self, other): """ - Compare ne NmapHost based on: + Compare ne NmapHost based on: - - hostnames - - address - - if an associated services has changed + - hostnames + - address + - if an associated services has changed - :return: boolean + :return: boolean """ rval = True if self.__class__ == other.__class__ and self.id == other.id: @@ -95,8 +95,8 @@ def __ne__(self, other): def __repr__(self): """ - String representing the object - :return: string + String representing the object + :return: string """ return "{0}: [{1} ({2}) - {3}]".format( self.__class__.__name__, @@ -107,8 +107,8 @@ def __repr__(self): def __hash__(self): """ - Hash is needed to be able to use our object in sets - :return: hash + Hash is needed to be able to use our object in sets + :return: hash """ return ( hash(self.status) @@ -119,46 +119,46 @@ def __hash__(self): def changed(self, other): """ - return the number of attribute who have changed - :param other: NmapHost object to compare - :return int + 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 + Accessor for the unix timestamp of when the scan was started - :return: string + :return: string """ return self._starttime @property def endtime(self): """ - Accessor for the unix timestamp of when the scan ended + Accessor for the unix timestamp of when the scan ended - :return: string + :return: string """ return self._endtime @property def address(self): """ - Accessor for the IP address of the scanned host + Accessor for the IP address of the scanned host - :return: IP address as a string + :return: IP address as a string """ return self._main_address @address.setter def address(self, addrdict): """ - Setter for the address dictionnary. + Setter for the address dictionnary. - :param addrdict: valid dict is {'addr': '1.1.1.1', - 'addrtype': 'ipv4'} + :param addrdict: valid dict is {'addr': '1.1.1.1', + 'addrtype': 'ipv4'} """ if addrdict["addrtype"] == "ipv4": self._ipv4_addr = addrdict["addr"] @@ -175,65 +175,65 @@ def address(self, addrdict): @property def ipv4(self): """ - Accessor for the IPv4 address of the scanned host + Accessor for the IPv4 address of the scanned host - :return: IPv4 address as a string + :return: IPv4 address as a string """ return self._ipv4_addr or "" @property def mac(self): """ - Accessor for the MAC address of the scanned host + Accessor for the MAC address of the scanned host - :return: MAC address as a string + :return: MAC address as a string """ return self._mac_addr or "" @property def vendor(self): """ - Accessor for the vendor attribute of the scanned host + Accessor for the vendor attribute of the scanned host - :return: string (vendor) of empty string if no vendor defined + :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 + Accessor for the IPv6 address of the scanned host - :return: IPv6 address as a string + :return: IPv6 address as a string """ return self._ipv6_addr or "" @property def status(self): """ - Accessor for the host's status (up, down, unknown...) + Accessor for the host's status (up, down, unknown...) - :return: string + :return: string """ return self._status["state"] @status.setter def status(self, statusdict): """ - Setter for the status dictionnary. + Setter for the status dictionnary. - :param statusdict: valid dict is {"state": "open", - "reason": "syn-ack", - "reason_ttl": "0"} - 'state' is the only mandatory key. + :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 + method to determine if host is up or not - :return: bool + :return: bool """ rval = False if self.status == "up": @@ -243,36 +243,36 @@ def is_up(self): @property def hostnames(self): """ - Accessor returning the list of hostnames (array of strings). + Accessor returning the list of hostnames (array of strings). - :return: array of string + :return: array of string """ return self._hostnames @property def services(self): """ - Accessor for the array of scanned services for that host. + Accessor for the array of scanned services for that host. - An array of NmapService objects is returned. + An array of NmapService objects is returned. - :return: array of NmapService + :return: array of NmapService """ return self._services def get_ports(self): """ - Retrieve a list of the port used by each service of the NmapHost + 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: 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 + Same as get_ports() but only for open ports - :return: list: of tuples (port,'proto') ie:[(22,'tcp'),(25, 'tcp')] + :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" @@ -280,10 +280,10 @@ def get_open_ports(self): def get_service(self, portno, protocol="tcp"): """ - :param portno: int the portnumber - :param protocol='tcp': string ('tcp','udp') + :param portno: int the portnumber + :param protocol='tcp': string ('tcp','udp') - :return: NmapService or None + :return: NmapService or None """ plist = [ p @@ -296,9 +296,9 @@ def get_service(self, portno, protocol="tcp"): def get_service_byid(self, service_id): """ - Returns a NmapService by providing its id. + Returns a NmapService by providing its id. - The id of a nmap service is a python tupl made of (protocol, port) + The id of a nmap service is a python tupl made of (protocol, port) """ rval = None for _tmpservice in self._services: @@ -308,10 +308,10 @@ def get_service_byid(self, service_id): def os_class_probabilities(self): """ - Returns an array of possible OS class detected during - the OS fingerprinting. + Returns an array of possible OS class detected during + the OS fingerprinting. - :return: Array of NmapOSClass objects + :return: Array of NmapOSClass objects """ rval = [] if self.os is not None: @@ -320,10 +320,10 @@ def os_class_probabilities(self): def os_match_probabilities(self): """ - Returns an array of possible OS match detected during - the OS fingerprinting + Returns an array of possible OS match detected during + the OS fingerprinting - :return: array of NmapOSMatches objects + :return: array of NmapOSMatches objects """ rval = [] if self.os is not None: @@ -333,18 +333,18 @@ def os_match_probabilities(self): @property def os_fingerprinted(self): """ - Specify if the host has OS fingerprint data available + Specify if the host has OS fingerprint data available - :return: Boolean + :return: Boolean """ return self._osfingerprinted @property def os_fingerprint(self): """ - Returns the fingerprint of the scanned system. + Returns the fingerprint of the scanned system. - :return: string + :return: string """ rval = "" if self.os is not None: @@ -353,11 +353,11 @@ def os_fingerprint(self): def os_ports_used(self): """ - Returns an array of the ports used for OS fingerprinting + Returns an array of the ports used for OS fingerprinting - :return: array of ports used: [{'portid': '22', - 'proto': 'tcp', - 'state': 'open'},] + :return: array of ports used: [{'portid': '22', + 'proto': 'tcp', + 'state': 'open'},] """ rval = [] try: @@ -369,10 +369,10 @@ def os_ports_used(self): @property def tcpsequence(self): """ - Returns the difficulty to determine remotely predict - the tcp sequencing. + Returns the difficulty to determine remotely predict + the tcp sequencing. - return: string + return: string """ rval = "" try: @@ -384,9 +384,9 @@ def tcpsequence(self): @property def ipsequence(self): """ - Return the class of ip sequence of the remote hosts. + Return the class of ip sequence of the remote hosts. - :return: string + :return: string """ rval = "" try: @@ -398,9 +398,9 @@ def ipsequence(self): @property def uptime(self): """ - uptime of the remote host (if nmap was able to determine it) + uptime of the remote host (if nmap was able to determine it) - :return: string (in seconds) + :return: string (in seconds) """ rval = 0 try: @@ -412,9 +412,9 @@ def uptime(self): @property def lastboot(self): """ - Since when the host was booted. + Since when the host was booted. - :return: string + :return: string """ rval = "" try: @@ -426,9 +426,9 @@ def lastboot(self): @property def distance(self): """ - Number of hops to host + Number of hops to host - :return: int + :return: int """ rval = 0 try: @@ -440,9 +440,9 @@ def distance(self): @property def scripts_results(self): """ - Scripts results specific to the scanned host + Scripts results specific to the scanned host - :return: array of + + +cpe:/a:mysql:mysql:5.7.16 + + +cpe:/a:memcached:memcached:1.4.25 + + + + + + + + \ No newline at end of file diff --git a/libnmap/test/test_service.py b/libnmap/test/test_service.py index 0e4c29d..567b239 100644 --- a/libnmap/test/test_service.py +++ b/libnmap/test/test_service.py @@ -1,6 +1,7 @@ #!/usr/bin/env python # -*- coding: utf-8 -*- +import os import unittest from libnmap.diff import NmapDiffException @@ -170,6 +171,7 @@ class TestNmapService(unittest.TestCase): def setUp(self): + self.fdir = os.path.dirname(os.path.realpath(__file__)) self.s1 = NmapParser.parse(service1) self.s2 = NmapParser.parse(service2) self.s3 = NmapParser.parse(service3) @@ -255,6 +257,21 @@ def test_tunnel(self): servicetunnel = NmapParser.parse(port_tunnel) self.assertEqual(servicetunnel.tunnel, "ssl") + def test_bannerdict(self): + nmapreport = NmapParser.parse_fromfile( + "{0}/files/dionaea_scan.xml".format(self.fdir) + ) + dhttp = nmapreport.hosts[0].get_service(80) + dftp = nmapreport.hosts[0].get_service(21) + self.assertEqual(dhttp.banner_dict, {"product": "nginx"}) + self.assertEqual( + dftp.banner_dict, + { + "product": "Synology DiskStation NAS ftpd", + "devicetype": "storage-misc", + }, + ) + if __name__ == "__main__": test_suite = [ From 58b0d0a77afd2d1bca66477386cecb017fbcb99a Mon Sep 17 00:00:00 2001 From: Debra Jules Date: Fri, 17 Apr 2015 10:09:45 +0200 Subject: [PATCH 44/88] Adding the MAC adress to method host.get_dict() When host is down, mac_addr = None --- libnmap/objects/host.py | 2 ++ libnmap/test/test_host.py | 7 +++++++ 2 files changed, 9 insertions(+) diff --git a/libnmap/objects/host.py b/libnmap/objects/host.py index a8e1ba6..fc957fd 100644 --- a/libnmap/objects/host.py +++ b/libnmap/objects/host.py @@ -510,8 +510,10 @@ def get_dict(self): "address": self.address, "status": self.status, "hostnames": " ".join(self._hostnames), + "mac_addr": self._mac_addr, } ) + return d def diff(self, other): diff --git a/libnmap/test/test_host.py b/libnmap/test/test_host.py index 67830ff..0d6394c 100644 --- a/libnmap/test/test_host.py +++ b/libnmap/test/test_host.py @@ -149,6 +149,10 @@ class TestNmapHost(unittest.TestCase): + def setUp(self): + self.fdir = os.path.dirname(os.path.realpath(__file__)) + self.dionaea_path = "{0}/files/dionaea_scan.xml" + def test_eq_host(self): h1 = NmapParser.parse(host1) h2 = NmapParser.parse(host2) @@ -218,6 +222,7 @@ def test_diff_host(self): "NmapService::tcp.3306", "address", "NmapService::tcp.25", + "mac_addr", ] ), ) @@ -236,6 +241,7 @@ def test_diff_host(self): "NmapService::tcp.22", "NmapService::tcp.111", "address", + "mac_addr", ] ), ) @@ -253,6 +259,7 @@ def test_diff_host(self): "NmapService::tcp.22", "NmapService::tcp.111", "address", + "mac_addr", ] ), ) From 204c8e3918966f11bcb12f2f7f16086ac8a0e3ea Mon Sep 17 00:00:00 2001 From: Ronald Date: Sun, 6 Dec 2020 21:38:00 +0100 Subject: [PATCH 45/88] feat: added support for mac address in NmapHost and cascaded attribute support in NmapDiff --- .gitignore | 1 + TODO | 7 ++++--- libnmap/objects/host.py | 1 + libnmap/test/files/1_host_ping.xml | 19 +++++++++++++++++++ .../files/diff_1_host_ping_mac_changed.xml | 19 +++++++++++++++++++ libnmap/test/test_host.py | 4 +++- libnmap/test/test_report.py | 18 ++++++++++++++++++ 7 files changed, 65 insertions(+), 4 deletions(-) create mode 100644 libnmap/test/files/1_host_ping.xml create mode 100644 libnmap/test/files/diff_1_host_ping_mac_changed.xml diff --git a/.gitignore b/.gitignore index 1be8b40..3706542 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,6 @@ *.py[cod] *.swp +.pylintrc *~ *.lock *.DS_Store diff --git a/TODO b/TODO index 386ee14..aacde4a 100644 --- a/TODO +++ b/TODO @@ -1,11 +1,12 @@ 0.7.1: - clean-up blacked code and pylint it 0.7.1: - add unittest for defusedxml to fix billionlaugh and external entities security issues -0.7.1: - add CSV backend support 0.7.1: - Change License from CC-BY to Apache 2.0 -0.7.1: - Update readme file to reflect the xml fixed issue + how to install it +0.7.1: - Enabled defusedxml support as preferred option for parsing () 0.7.1: - add extra_requires for plugins deps and defusedxml 0.7.1: - Remove code duplication in sudo_run and sudo_run_background from process.py -0.7.1: - Merge PR79 from Shouren to fix empty nmap outputs +0.7.1: - Fix empty nmap outputs due to subprocess race condition (Merge PR79 from @Shouren) +0.7.1: - Added banner_dict support + unittest (Merge edited PR from @cfoulds) +0.7.2: - add CSV backend support 0.7.2: improve API for NSE scripts 0.7.2: add support for post,pre and host scripts 0.7.2: add a Contribution guideline page diff --git a/libnmap/objects/host.py b/libnmap/objects/host.py index fc957fd..9425e2e 100644 --- a/libnmap/objects/host.py +++ b/libnmap/objects/host.py @@ -113,6 +113,7 @@ def __hash__(self): return ( hash(self.status) ^ hash(self.address) + ^ hash(self._mac_addr) ^ hash(frozenset(self._services)) ^ hash(frozenset(" ".join(self._hostnames))) ) diff --git a/libnmap/test/files/1_host_ping.xml b/libnmap/test/files/1_host_ping.xml new file mode 100644 index 0000000..92fde18 --- /dev/null +++ b/libnmap/test/files/1_host_ping.xml @@ -0,0 +1,19 @@ + + + + + + + + +
+
+ + + + + + + + + \ No newline at end of file diff --git a/libnmap/test/files/diff_1_host_ping_mac_changed.xml b/libnmap/test/files/diff_1_host_ping_mac_changed.xml new file mode 100644 index 0000000..91d0429 --- /dev/null +++ b/libnmap/test/files/diff_1_host_ping_mac_changed.xml @@ -0,0 +1,19 @@ + + + + + + + + +
+
+ + + + + + + + + \ No newline at end of file diff --git a/libnmap/test/test_host.py b/libnmap/test/test_host.py index 0d6394c..d192f32 100644 --- a/libnmap/test/test_host.py +++ b/libnmap/test/test_host.py @@ -1,6 +1,7 @@ #!/usr/bin/env python # -*- coding: utf-8 -*- +import os import unittest from libnmap.parser import NmapParser @@ -111,6 +112,7 @@ """ + host4 = """ @@ -151,7 +153,7 @@ class TestNmapHost(unittest.TestCase): def setUp(self): self.fdir = os.path.dirname(os.path.realpath(__file__)) - self.dionaea_path = "{0}/files/dionaea_scan.xml" + self.dionaea_path = "{0}/files/dionaea_scan.xml".format(self.fdir) def test_eq_host(self): h1 = NmapParser.parse(host1) diff --git a/libnmap/test/test_report.py b/libnmap/test/test_report.py index b777a5d..df231d6 100644 --- a/libnmap/test/test_report.py +++ b/libnmap/test/test_report.py @@ -249,10 +249,28 @@ def test_host_address_unchanged(self): "NmapService::tcp.3306", "address", "NmapService::tcp.25", + "mac_addr", ] ), ) + def test_diff_mac(self): + fdir = os.path.dirname(os.path.realpath(__file__)) + host_ping = "{0}/files/1_host_ping.xml".format(fdir) + host_ping_mac_changed = ( + "{0}/files/diff_1_host_ping_mac_changed.xml".format(fdir) + ) + + report_mac_original = NmapParser.parse_fromfile(host_ping) + report_mac_changed = NmapParser.parse_fromfile(host_ping_mac_changed) + + report_diff = report_mac_original.diff(report_mac_changed) + self.assertEqual(report_diff.changed(), set(["NmapHost::172.28.1.3"])) + host_original = report_mac_original.hosts[0] + host_mac_changed = report_mac_changed.hosts[0] + host_diff = host_original.diff(host_mac_changed) + self.assertEqual(host_diff.changed(), set(["mac_addr"])) + if __name__ == "__main__": test_suite = [ From 93c6f6a817624b482f658f284cd70a88e0f0888c Mon Sep 17 00:00:00 2001 From: Ronald Date: Thu, 10 Dec 2020 23:25:02 +0100 Subject: [PATCH 46/88] fix: fixed change log and added pre-commit hooks --- CHANGES.txt | 148 ------------------------ TODO | 28 ++--- libnmap/plugins/backendpluginFactory.py | 6 +- 3 files changed, 19 insertions(+), 163 deletions(-) delete mode 100644 CHANGES.txt diff --git a/CHANGES.txt b/CHANGES.txt deleted file mode 100644 index 55d6af7..0000000 --- a/CHANGES.txt +++ /dev/null @@ -1,148 +0,0 @@ -v0.7.1, 22/11/2020 -- code clean-up + fix for CVE-2019-1010017 -v0.7.0, 28/02/2016 -- A few bugfixes - - fixe of endless loop in Nmap.Process. Fix provided by @rcarrillo, many thanks! -v0.6.3, 18/08/2015 -- Merged pull requests for automatic pypi upload, thanks @bmx0r -v0.6.2, 03/01/2015 -- Fixed a few bugs - - bulk fix for issues 37, 41, 42, 43, 44, 46 - - added cpe_list method - - added elasticsearch example code -v0.6.1, 29/06/2014 -- Added full support for python 3.X - - python now supports python 2.6, 2.7, 3.3, 3.4 -v0.5.1, 26/05/2014 -- Added basic API for class CPE - - interface similar to python-cpe for more - advanced usage of CPE, I strongly recommend you - to use python-cpe. Nice code, good doc. -v0.5.0, 17/05/2014 -- Rewrite of NmapProcess - - removed Threads to read stdout/stderr - - replaced by Process - - use of semaphore to not consume CPU while looping - - added NmapTask class - - added NmapProcess.current_task - - added NmapProcess.tasks list - - fixed bug in NmapProcess.state - - note NmapProcess.is_alive() is not recommended -v0.4.9, 14/05/2014 -- Fix for issue 28 and added code samples -v0.4.8, 05/05/2014 -- Changes in OS fingerprint data API - - NmapHost now holds its OS fingerprint data in NmapHost.os - (NmapOSFingerpring object) - - fingerprint is now a property which means you have to call - it without () NmapHost.os.fingerprint and should be called - directly from NmapHost.os - - NmapHost.os.fingerprints return an array of os - fingerprints (strings) - - NmapHost.os.fingerprint return a concatenated string of - all fingerprints - - Fingerprints data are now accessible via - NmapHost.os.osmatches which returns a list of NmapOSMatch - objects - - NmapOSMatch objects might contain a list of NmapOSClass - objects matching with it - - NmapOSClass objects might contain a list of CPE object - related to the os class (CPE class will be improved and - API enriched) - - TODO: finalize API doc and create all related unit tests -v0.4.7, 03/05/2014 -- minor fix for issue25 - - fixed exception when optional service tag is not present - in tag - - added support for if present in : - accessible via NmapService.owner -v0.4.6, 06/04/2014 -- minor fix - - corrected missing incomplete parameter on parse_fromfile - and parse_fromstring - - added support to run scan in background with sudo support - added NmapProcess.sudo_run_background() - - fixed issue with run() blocking when an error triggered - during the scan -v0.4.5, 06/04/2014 -- minor fixes and botox injections - - Added "incomplete" argument in NmapReport.parse() - in order to enable parsing of incomplete or interrupted - nmap scans. Could be useful to use with a background - scan to parse incomplete data blocks from callback - function (thanks @Sibwara for the idea). - - Fixed bug when NmapReport.summary is empty - - added NmapReport.endtimestr - - remplaced ElementTree by cElementTree (performance) -v0.4.4, 04/04/2014 -- Bugs and features requests - - added support for tunnel attribute from tag - - added support for servicefp (service fingerprint) in - attributes from tag - - added support for reasons attributes from 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: - NmapService.scripts_results is now a property - NmapService.scripts_results return an array of scripts results - - - fix issue#9 address field not correcly parsed: - MAC Address would erase an ipv4 address type. - - added new properties in hosts object API: - NmapHost.ipv4, NmapHost.ipv6, NmapHost.mac - - NmapHost.address property returns the IPv4 or IPv6 or MAC - in that preference order. Use specific calls for determinists - results - - fix API issue#10: os_ports_used -v0.4.2, 26/12/2013 -- Bug corrected: issue 8 - There is no guarantee that "finished" or "runstats" will be - received by event parser of process.py. - Summary functions are now flagged as deprecated. To use data - from scan summary of numbers of hosts up, the user of the - lib will have to use NmapParser.parse() and the appropriate - accessors. -v0.4.1, 26/12/2013 -- Bug corrected: issue6 - Infinite loop while launching several nmap scans in background -v0.4.0, 28/10/2013 -- Bug corrected in missing data from nmap scan output - Added stop() to terminate nmap scan running in background -v0.3.1, 17/06/2013 -- Refactory of objects to isolate each nmap object in a - separate file -v0.3.0, 17/06/2013 -- Added fingerprint class - - added NmapOSFingerprint class to provide better API to - fingerprint data - - added unit tests for basic NmapHost API check - - added unit test for basic NmapOSFingerprint class -v0.2.9, 17/06/2013 -- Add S3 plugin, allow to store nmapreport object to aws - - S3. and compatible (via boto) -v0.2.8, 11/06/2013 -- Prepare packaging for pypi -v0.2.1, 17/05/2013 -- Code Docstring and added support for additional data - - added support for scripts in NmapService - - added support for hosts extra data in NmapHost (uptime, distance,...) - - added support for OS fingerprint data in NmapHost - - added python docstrings for all modules - - added sphinx documentation - - reviewed API for libnmap objects - - fixed errors with hash() in diff - - fixed errors/exceptions in NmapParser -v0.2.0, 18/04/2013 -- Added Serialization and Plugin support - - added serialization encoders and decoders for NmapReport - - added basic plugin capability to NmapReport - - added basic mongodb plugin to validate plugin setup -v0.1.5, 08/04/2013 -- Refactory of NmapDiff system - - rework of NmapHost and NmapService API - - added __hash__, id and get_dict() for common Nmap - Objects - - added NmapDiff class - - full rework of unittests - - NmapParser now supports parsing from file - - NmapParser is able to handle nmap XML portions - - added import in reports -v0.1.4, 05/04/2013 -- Bug Fixes and improvements - - unittest for diff on NmapHost - - unittest for diff on NmapService - - fixed: __eq__ in NmapService: protocol not honoured - - fixed: sudo_run hardened and added exception handling -v0.1.3, 04/04/2013 -- Full refactory of NmapParser with static method - - added support for diffing NmapHost and NmapService - - corrected en hardened code for NmapParser - - added NmapParserException class - - added NmapReport class - - added unittest for report api - - added unittest for parser -v0.1.2, 13/03/2013 -- Bug Fixes and improvement after refactory - - added scaninfo parsing - - corrected unused variables and wrong unittests - - parse() method reviewed to call "independent" XML bloc - parsers -v0.1.1, 12/03/2013 -- Complete refactory of code to isolate NMAP objects. -v0.1.0, 11/03/2013 -- First developement release packaged for Project Ninaval diff --git a/TODO b/TODO index aacde4a..927e686 100644 --- a/TODO +++ b/TODO @@ -1,20 +1,20 @@ -0.7.1: - clean-up blacked code and pylint it -0.7.1: - add unittest for defusedxml to fix billionlaugh and external entities security issues -0.7.1: - Change License from CC-BY to Apache 2.0 -0.7.1: - Enabled defusedxml support as preferred option for parsing () -0.7.1: - add extra_requires for plugins deps and defusedxml -0.7.1: - Remove code duplication in sudo_run and sudo_run_background from process.py -0.7.1: - Fix empty nmap outputs due to subprocess race condition (Merge PR79 from @Shouren) -0.7.1: - Added banner_dict support + unittest (Merge edited PR from @cfoulds) -0.7.2: - add CSV backend support +0.7.1: clean-up blacked code and pylint it +0.7.1: add unittest for defusedxml to fix billionlaugh and external entities security issues +0.7.1: Change License from CC-BY to Apache 2.0 +0.7.1: Enabled defusedxml support as preferred option for parsing () +0.7.1: add extra_requires for plugins deps and defusedxml +0.7.1: Remove code duplication in sudo_run and sudo_run_background from process.py +0.7.1: Fix empty nmap outputs due to subprocess race condition (Merge PR79 from @Shouren) +0.7.1: Added banner_dict support + unittest (Merge edited PR from @cfoulds) + +0.7.2: add CSV backend support 0.7.2: improve API for NSE scripts 0.7.2: add support for post,pre and host scripts 0.7.2: add a Contribution guideline page 0.7.2: add development environment config and setup 0.7.2: add pre-commit hooks to enforce black and isort 0.7.2: automate in github actions the git workflow + doc update + pypi update -0.7.3: create complete python testing environment based on docker-compose and some examples -- complete unit tests with coverall support -- Add new plugins to support import/export from mysql, couchdb, csv -- add unittest for udp scans, ping sweeping -- add support for 'resume' capability (see nmap --resume) + +0.7.3: Add support and tests for traceroute in nmap + +0.7.4: create complete python testing environment based on docker-compose and some examples diff --git a/libnmap/plugins/backendpluginFactory.py b/libnmap/plugins/backendpluginFactory.py index 3b06bfd..1690e17 100644 --- a/libnmap/plugins/backendpluginFactory.py +++ b/libnmap/plugins/backendpluginFactory.py @@ -29,5 +29,9 @@ def create(cls, plugin_name="mongodb", **kwargs): try: backendplugin = classobj(**kwargs) except Exception as error: - raise Exception("Cannot create Backend: {0}".format(error)) + raise Exception( + "Cannot create Backend {0}: {1}".format( + classname, error + ) + ) return backendplugin From e7ed29507d109b953a05da90dce0257f8794db85 Mon Sep 17 00:00:00 2001 From: Ronald Date: Wed, 16 Dec 2020 21:30:18 +0100 Subject: [PATCH 47/88] fix: changelog to keep-a-change-log specs and updated documentation --- .github/workflows/pypi_publish.yaml | 26 +++ .gitignore | 1 + .pre-commit-config.yaml | 23 ++ CHANGELOG.md | 319 ++++++++++++++++++++++++++++ CODEOWNERS | 1 + MANIFEST | 2 +- NOTICE | 4 + README.md | 22 +- TODO | 5 + docs/parser.rst | 46 +++- requirements-dev.txt | 7 + 11 files changed, 447 insertions(+), 9 deletions(-) create mode 100644 .github/workflows/pypi_publish.yaml create mode 100644 .pre-commit-config.yaml create mode 100644 CHANGELOG.md create mode 100644 CODEOWNERS create mode 100644 NOTICE create mode 100644 requirements-dev.txt diff --git a/.github/workflows/pypi_publish.yaml b/.github/workflows/pypi_publish.yaml new file mode 100644 index 0000000..2d88f00 --- /dev/null +++ b/.github/workflows/pypi_publish.yaml @@ -0,0 +1,26 @@ +name: Upload Python Package + +on: + release: + types: [created] + +jobs: + deploy: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - name: Set up Python + uses: actions/setup-python@v2 + with: + python-version: '3.x' + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install setuptools wheel twine + - name: Build and publish + env: + TWINE_USERNAME: ${{ secrets.PYPI_USERNAME }} + TWINE_PASSWORD: ${{ secrets.PYPI_PASSWORD }} + run: | + python setup.py sdist bdist_wheel + twine upload dist/* \ No newline at end of file diff --git a/.gitignore b/.gitignore index 3706542..02d3b53 100644 --- a/.gitignore +++ b/.gitignore @@ -44,3 +44,4 @@ nosetests.xml __pycache__ .vscode/settings.json .noseids +_build diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 0000000..bf80633 --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,23 @@ +exclude: ^(test/|.tox/|docs) +repos: +- repo: https://github.com/pre-commit/pre-commit-hooks + rev: v2.3.0 + hooks: + - id: check-yaml + - id: end-of-file-fixer + - id: trailing-whitespace +- repo: https://github.com/psf/black + rev: 20.8b1 + hooks: + - id: black + args: [--line-length=79] + files: ^libnmap +- repo: https://github.com/pre-commit/mirrors-isort + rev: v5.6.4 + hooks: + - id: isort + args: [--multi-line=3, --line-length=79, --profile=black] +- repo: https://gitlab.com/pycqa/flake8 + rev: 3.8.4 + hooks: + - id: flake8 diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..7fed80b --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,319 @@ +# Changelog + +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). (or tries to...) + +## [v0.7.1] + +### Added + +- Added pre-commit hook support to enforce code style (black, isort) +- Added unittest for defusedxml to fix billionlaugh and external entities security issues +- Added extra_requires for plugins deps and defusedxml +- Added banner_dict support + unittest (Merge edited PR from @cfoulds) +- Added black, isort in tox environment +- Added more unit tests in several modules to improve code collaboration and automated tested +- Added GitHub action pipeline to run pytests, black and isort checks +- Added GitHub action pipeline to publish pypi package + +### Changed + +- Code linted and styled with black and isort +- Changed Licence from CC-BY to Apache 2.0, considering that CC is [not appropriate for code licensing](https://creativecommons.org/faq/#can-i-apply-a-creative-commons-license-to-software) +- Changelog now using [Keep-a-changelog](https://keepachangelog.com/en/1.0.0/) specs + +### Removed + +- Removed travis build in favor of GitHub Actions pipelines + +### Fixed + +- Fix empty nmap outputs due to subprocess race condition (Merge PR#79 from @Shouren) +- Add extra_requires for plugins deps and defusedxml +- Removed code duplication in sudo_run and sudo_run_background from process.py + +### Security + +- Fix for security issue on XXE (XML External Entities) - CVE-2019-1010017 + +## [v0.7.0] - 28/02/2016 + +### Fixed + +- Fix of endless loop in Nmap.Process. Fix provided by @rcarrillo, many thanks! + +## [v0.6.3] - 18/08/2015 + +### Added + +- Merged pull requests for automatic pypi upload, thanks @bmx0r + +## [v0.6.2] - 03/01/2015 + +### Added + +- Added cpe_list method +- Added elasticsearch example code + +### Fixed + +- Fixed issues: 37, 41, 42, 43, 44, 46 + +## [v0.6.1] - 29/06/2014 + +### Added + +- Added full support for python 3.X: python now supports python 2.6, 2.7, 3.3, 3.4 + +## [v0.5.1] - 26/05/2014 + +### Added + +- Basic API for class CPE interface similar to python-cpe for more advanced usage of CPE, I strongly recommend you to use python-cpe. + +## [v0.5.0] - 17/05/2014 + +### Added + +- Added NmapTask class +- Added NmapProcess.current_task +- Added NmapProcess.tasks list +- Use of semaphore to not consume CPU while looping + +### Fixed + +- Removed Threads to read stdout/stderr +- Fixed bug in NmapProcess.state + +## [v0.4.9] - 14/05/2014 + +### Added + +- Added [code samples](examples/) + +### Fixed + +- Fix for issue 28 + +## [v0.4.8] - 05/05/2014 + +### Changed + +- Changes in OS fingerprint data API +- NmapHost now holds its OS fingerprint data in NmapHost.os (NmapOSFingerpring object) +- fingerprint is now a property which means you have to call it without (); e.g.: NmapHost.os.fingerprint +- NmapHost.os.fingerprints return an array of os fingerprints (strings) +- NmapHost.os.fingerprint return a concatenated string of all fingerprints +- Fingerprints data are now accessible via NmapHost.os.osmatches which returns a list of NmapOSMatch objects +- NmapOSMatch objects might contain a list of NmapOSClass objects matching with it +- NmapOSClass objects might contain a list of CPE object related to the os class (CPE class will be improved and API enriched) + +## [v0.4.7] - 03/05/2014 + +### Added + +- added support for if present in : accessible via NmapService.owner + +### Fixed + +- Minor fix for issue25 +- Fixed exception when optional service tag is not present in tag + + +## [v0.4.6] - 06/04/2014 + +### Added + +- Added support to run scan in background with sudo support +- Added NmapProcess.sudo_run_background() + +### Fixed + +- Corrected missing incomplete parameter on parse_fromfile and parse_fromstring +- Fixed issue with run() blocking when an error triggered during the scan + +## [v0.4.5] - 06/04/2014 + +### Added + +- Added "incomplete" argument in NmapReport.parse() in order to enable parsing of incomplete or interrupted nmap scans. Could be useful to use with a background scan to parse incomplete data blocks from callback function (thanks @Sibwara for the idea). +- Added NmapReport.endtimestr +- Added and tested cElementTree support (performance) + +### Fixed + +- Fixed bug when NmapReport.summary is empty + +## [v0.4.4] - 04/04/2014 + +### Added +- Added support for tunnel attribute from tag +- Added support for servicefp (service fingerprint) in attributes from tag +- Added support for reasons attributes from tag +- Added support for extraports/extrareasons tags + +### Fixed + +- corrected bug in serialization: missing extra data (pull request from @DougRoyal) + +## [v0.4.3] - 14/03/2014 + +### Changed + +- API change for NmapService.scripts_results: + - NmapHost.address property returns the IPv4 or IPv6 or MAC in that preference order. Use specific calls for determinists results + - NmapService.scripts_results is now a property + - NmapService.scripts_results return an array of scripts results + +### Added + +- Added new properties in hosts object API: + - NmapHost.ipv4 + - NmapHost.ipv6 + - NmapHost.mac + +### Fixed + +- Fix issue#14: better scripts parsing +- Fix issue#9 address field not correcly parsed: MAC Address would erase an ipv4 address type. +- Fix API issue#10: os_ports_used + +## [v0.4.2] - 26/12/2013 + +### Fixed + +- Fixed #issue8: There is no guarantee that "finished" or "runstats" will be received by event parser of process.py. +- Summary functions are now flagged as deprecated. To use data from scan summary of numbers of hosts up, the user of the lib will have to use NmapParser.parse() and the appropriate accessors. + +## [v0.4.1] - 26/12/2013 + +### Fixed + +- Fixed issue#6: Infinite loop while launching several nmap scans in background + +## [v0.4.0] - 28/10/2013 + +### Added + +- Added stop() to terminate nmap scan running in background + +### Fixed + +- Bug corrected in missing data from nmap scan output + +## [v0.3.1] - 17/06/2013 + +### Changed + +- Refactory of objects to isolate each nmap object in a separate file + +## [v0.3.0] - 17/06/2013 + +### Added + +- Added fingerprint class +- Added NmapOSFingerprint class to provide better API to fingerprint data +- Added unit tests for basic NmapHost API check +- Added unit test for basic NmapOSFingerprint class + +## [v0.2.9] - 17/06/2013 + +### Added + +- Add S3 plugin, allow to store nmapreport object to aws S3 compatible object storage backend (via boto) + +## [v0.2.8] - 11/06/2013 + +### Added + +- Prepare packaging for pypi + +## [v0.2.1] - 17/05/2013 + +### Added +- Code Docstring and added support for additional data +- Added support for scripts in NmapService +- Added support for hosts extra data in NmapHost (uptime, distance,...) +- Added support for OS fingerprint data in NmapHost +- Added python docstrings for all modules +- Added sphinx documentation + +### Fixed + +- Reviewed API for libnmap objects +- Fixed errors with hash() in diff +- Fixed errors/exceptions in NmapParser + +## [v0.2.0] - 18/04/2013 + +### Added + +- Added Serialization and Plugin support +- Added serialization encoders and decoders for NmapReport +- Added basic plugin capability to NmapReport +- Added basic mongodb plugin to validate plugin setup + +## [v0.1.5] - 08/04/2013 + +### Changed + +Refactory of NmapDiff system +- Rework of NmapHost and NmapService API +- Added __hash__, id and get_dict() for common Nmap Objects +- Added NmapDiff class +- Full rework of unittests +- NmapParser now supports parsing from file +- NmapParser is able to handle nmap XML portions +- Added import in reports + +## [v0.1.4] - 05/04/2013 -- Bug Fixes and improvements + +### Added + +- Added unittest for diff on NmapHost +- Added unittest for diff on NmapService + +### Fixed + +- Fixed: __eq__ in NmapService: protocol not honoured +- Fixed: sudo_run hardened and added exception handling + +## [v0.1.3] - 04/04/2013 + +### Added + +- Full refactory of NmapParser with static method +- Added support for diffing NmapHost and NmapService +- Added NmapParserException class +- Added NmapReport class +- Added unittest for report api +- Added unittest for parser + +### Fixed + +- Corrected en hardened code for NmapParser + +## [v0.1.2] - 13/03/2013 + +### Added + +- Added scaninfo parsing + +### Fixed + +- Corrected unused variables and wrong unittests +- Parse() method reviewed to call "independent" XML bloc parsers + +## [v0.1.1] - 12/03/2013 + +### Added + +- Complete refactory of code to isolate NMAP objects. + +## [v0.1.0] - 11/03/2013 + +### Added + +- First developement release packaged for Project Ninaval diff --git a/CODEOWNERS b/CODEOWNERS new file mode 100644 index 0000000..7b644ee --- /dev/null +++ b/CODEOWNERS @@ -0,0 +1 @@ +* @savon-noir \ No newline at end of file diff --git a/MANIFEST b/MANIFEST index 3137d35..6637bb5 100644 --- a/MANIFEST +++ b/MANIFEST @@ -1,7 +1,7 @@ # file GENERATED by distutils, do NOT edit -CHANGES.txt README.md TODO +requirements-dev.txt setup.py docs/diff.rst docs/index.rst diff --git a/NOTICE b/NOTICE new file mode 100644 index 0000000..88e053c --- /dev/null +++ b/NOTICE @@ -0,0 +1,4 @@ +python-libnmap +Copyright 2020 Ronald Bister + +This product includes software developed by Ronald Bister \ No newline at end of file diff --git a/README.md b/README.md index 931cbee..1e6afe3 100644 --- a/README.md +++ b/README.md @@ -42,7 +42,7 @@ All the documentation is available on [read the docs](https://libnmap.readthedoc ## Dependencies -libnmap has by default no dependencies. +libnmap has by default no dependencies, except defusedxml if you need to import untrusted XML scans data. The only additional python modules you'll have to install depends if you wish to use libnmap to store reports on an exotic data store via libnmap's independents plugins. @@ -52,15 +52,27 @@ Below the list of optional dependencies: - [pymongo](https://github.com/mongodb/mongo-python-driver/) - [boto](https://github.com/boto/boto) +## Security + +If you are importing/parsing untrusted XML scan outputs with python-libnmap, install defusedxml library: + +```bash +ronald@brouette:~/dev$ pip install defusedxml +``` + +This will prevent you from being vulnerable to [XML External Entities attacks](https://owasp.org/www-community/vulnerabilities/XML_External_Entity_(XXE)_Processing). + +For more information, read the [official libnmap documentation](https://libnmap.readthedocs.io/en/latest/parser.html#security-note-for-libnmap-parser) + +This note relates to a cascaded CVE vulnerability from the python core library XML ElementTree. Nevertheless, python-libnmap has been assigned an [official CVE](https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2019-1010017) to track this issue. + +This CVE is addressed from v0.7.1. + ## Python Support The libnmap code is tested against the following python interpreters: -- Python 2.6 - Python 2.7 -- Python 3.3 -- Python 3.4 -- Python 3.5 - Python 3.6 - Python 3.7 - Python 3.8 diff --git a/TODO b/TODO index 927e686..4af529b 100644 --- a/TODO +++ b/TODO @@ -7,6 +7,11 @@ 0.7.1: Fix empty nmap outputs due to subprocess race condition (Merge PR79 from @Shouren) 0.7.1: Added banner_dict support + unittest (Merge edited PR from @cfoulds) +release: +- changelog date not respecting KACL specs +- check https://github.com/anton-yurchenko/git-release +- https://github.com/sean0x42/markdown-extract + 0.7.2: add CSV backend support 0.7.2: improve API for NSE scripts 0.7.2: add support for post,pre and host scripts diff --git a/docs/parser.rst b/docs/parser.rst index 99725eb..9673859 100644 --- a/docs/parser.rst +++ b/docs/parser.rst @@ -1,6 +1,42 @@ libnmap.parser ============== +Security note for libnmap.parser +-------------------------------- + +**TLDR:** if you are importing/parsing untrusted XML scan outputs with python-libnmap, install defusedxml library: + +.. code-block:: bash + + ronald@brouette:~/dev$ pip install defusedxml + +By default, python-libnmap's parser module does not enforces an extra XML parser module than the one provided in the python core distribution. + +In versions previous to 0.7.1, by default, the `ElementTree XML API was used `_. +This XML library is vulnerable to several `XML External Entities attacks `_ which may lead to: + +- Denial of Service attacks +- Remote and local files inclusions +- Remote code execution + +This implies, de facto, that parsing any untrusted XML file could result in any of the above. + +Fortunately, one of the python core developer is maintaining an alternative Python XML parsing library: `defusedxml `_ which addresses all the above vulnerabilities. + +Since the above vulnerabilities will only affect you if you are parsing untrusted XML scan outputs, by default, the defusedxml library is not enforced. +But if the defusedxml library is installed, it will be the preferred XML parser picked by python-libnmap. + +Consider the following lines from libnmap.parser module: + +.. literalinclude:: ../libnmap/parser.py + :linenos: + :lines: 3-10 + + +- Line 4 first tries to import defusedxml +- if it fails, it then tries to load cElementTree (known to be more performant) +- if it fails, it then defaults to XML ElementTree. + Purpose of libnmap.parser ------------------------- @@ -40,14 +76,18 @@ All of the above methods can receive as input: - a list of scanned services in XML (... tag) and will return a python array of NmapService objects - a scanned service in XML (... tag) and will return a NmapService object -Small example:: +Small example: + +.. code-block:: python from libnmap.parser import NmapParser nmap_report = NmapParser.parse_fromfile('libnmap/test/files/1_os_banner_scripts.xml') print "Nmap scan summary: {0}".format(nmap_report.summary) -Basic usage from a processed scan:: +Basic usage from a processed scan: + +.. code-block:: python from libnmap.process import NmapProcess from libnmap.parser import NmapParser @@ -67,4 +107,4 @@ NmapParser methods .. automodule:: libnmap.parser .. autoclass:: NmapParser - :members: + :members: \ No newline at end of file diff --git a/requirements-dev.txt b/requirements-dev.txt new file mode 100644 index 0000000..aaeebb6 --- /dev/null +++ b/requirements-dev.txt @@ -0,0 +1,7 @@ +black==20.8b1 +defusedxml==0.6.0 +isort==5.6.4 +pre-commit +pytest +pytest-cov +flake8 From 8438f4507969e28d1cea7ea472fe124ba101ef06 Mon Sep 17 00:00:00 2001 From: Ronald Date: Wed, 16 Dec 2020 22:01:42 +0100 Subject: [PATCH 48/88] fix: updated pypi publish workflow --- .github/workflows/pypi_publish.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/pypi_publish.yaml b/.github/workflows/pypi_publish.yaml index 2d88f00..a141097 100644 --- a/.github/workflows/pypi_publish.yaml +++ b/.github/workflows/pypi_publish.yaml @@ -2,7 +2,7 @@ name: Upload Python Package on: release: - types: [created] + types: [published] jobs: deploy: @@ -23,4 +23,4 @@ jobs: TWINE_PASSWORD: ${{ secrets.PYPI_PASSWORD }} run: | python setup.py sdist bdist_wheel - twine upload dist/* \ No newline at end of file + twine upload dist/* From 55588ba6b210e42a56de97235c2c04fc0d9bd99d Mon Sep 17 00:00:00 2001 From: Ronald Date: Wed, 16 Dec 2020 22:30:14 +0100 Subject: [PATCH 49/88] fix(github-acitons): fix pupi_publish workflow --- .github/workflows/pypi_publish.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/pypi_publish.yaml b/.github/workflows/pypi_publish.yaml index a141097..583652b 100644 --- a/.github/workflows/pypi_publish.yaml +++ b/.github/workflows/pypi_publish.yaml @@ -22,5 +22,5 @@ jobs: TWINE_USERNAME: ${{ secrets.PYPI_USERNAME }} TWINE_PASSWORD: ${{ secrets.PYPI_PASSWORD }} run: | - python setup.py sdist bdist_wheel + python setup.py sdist twine upload dist/* From 579b6ceacebb2cbdd6f9e4aff27394fb1a2bd2b0 Mon Sep 17 00:00:00 2001 From: Ronald Date: Wed, 16 Dec 2020 23:14:08 +0100 Subject: [PATCH 50/88] fix: moved back from README.md to README.rst --- MANIFEST | 2 +- MANIFEST.in | 2 +- README.md | 114 ------------------------------------- README.rst | 159 ++++++++++++++++++++++++++++++++++++++++++++++++++++ setup.py | 2 +- 5 files changed, 162 insertions(+), 117 deletions(-) delete mode 100644 README.md create mode 100644 README.rst diff --git a/MANIFEST b/MANIFEST index 6637bb5..c06a8aa 100644 --- a/MANIFEST +++ b/MANIFEST @@ -1,5 +1,5 @@ # file GENERATED by distutils, do NOT edit -README.md +README.rst TODO requirements-dev.txt setup.py diff --git a/MANIFEST.in b/MANIFEST.in index 9180c88..bb7a46c 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -1,3 +1,3 @@ include TODO -include *.rst *.txt README.md +include *.rst *.txt recursive-include docs *.rst diff --git a/README.md b/README.md deleted file mode 100644 index 1e6afe3..0000000 --- a/README.md +++ /dev/null @@ -1,114 +0,0 @@ -# python-libnmap - -## Code status - -![preflight-check](https://github.com/savon-noir/python-libnmap/workflows/Preflight%20Check/badge.svg) -[![Coverage Status](https://coveralls.io/repos/github/savon-noir/python-libnmap/badge.svg?branch=master)](https://coveralls.io/github/savon-noir/python-libnmap?branch=master) -[![License](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](https://opensource.org/licenses/Apache-2.0) - -## Use cases - -libnmap is a python library enabling python developers to manipulate nmap process and data. - -libnmap is what you were looking for if you need to implement the following: - -- automate or schedule nmap scans on a regular basis -- manipulate nmap scans results to do reporting -- compare and diff nmap scans to generate graphs -- batch process scan reports -- ... - -The above uses cases will be easy to implement with the help of the libnmap modules. - -## libnmap modules - -The lib currently offers the following modules: - -- **process**: enables you to launch nmap scans -- **parse**: enables you to parse nmap reports or scan results (only XML so far) from a file, a string,... -- **report**: enables you to manipulate a parsed scan result and de/serialize scan results in a json format -- **diff**: enables you to see what changed between two scans -- **common**: contains basic nmap objects like NmapHost and NmapService. It is to note that each object can be "diff()ed" with another similar object. -- **plugins**: enables you to support datastores for your scan results directly in the "NmapReport" object. from report module: - - mongodb: insert/get/getAll/delete - - sqlalchemy: insert/get/getAll/delete - - aws s3: insert/get/getAll/delete (not supported for python3 since boto is not supporting py3) - - csv: todo (easy to implement) - - elastic search: todo - -## Documentation - -All the documentation is available on [read the docs](https://libnmap.readthedocs.org). This documentation contains small code samples that you directly reuse. - -## Dependencies - -libnmap has by default no dependencies, except defusedxml if you need to import untrusted XML scans data. - -The only additional python modules you'll have to install depends if you wish to use libnmap to store reports on an exotic data store via libnmap's independents plugins. - -Below the list of optional dependencies: - -- [sqlalchemy](https://github.com/zzzeek/sqlalchemy) (+the driver ie:MySQL-python) -- [pymongo](https://github.com/mongodb/mongo-python-driver/) -- [boto](https://github.com/boto/boto) - -## Security - -If you are importing/parsing untrusted XML scan outputs with python-libnmap, install defusedxml library: - -```bash -ronald@brouette:~/dev$ pip install defusedxml -``` - -This will prevent you from being vulnerable to [XML External Entities attacks](https://owasp.org/www-community/vulnerabilities/XML_External_Entity_(XXE)_Processing). - -For more information, read the [official libnmap documentation](https://libnmap.readthedocs.io/en/latest/parser.html#security-note-for-libnmap-parser) - -This note relates to a cascaded CVE vulnerability from the python core library XML ElementTree. Nevertheless, python-libnmap has been assigned an [official CVE](https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2019-1010017) to track this issue. - -This CVE is addressed from v0.7.1. - -## Python Support - -The libnmap code is tested against the following python interpreters: - -- Python 2.7 -- Python 3.6 -- Python 3.7 -- Python 3.8 - -## Install - -You can install libnmap via pip: - -```bash -ronald@brouette:~$ pip install python-libnmap -``` - -or via git and dist utils (à l'ancienne): - -```bash -ronald@brouette:~$ git clone https://github.com/savon-noir/python-libnmap.git -ronald@brouette:~$ cd python-libnmap -ronald@brouette:~$ python setup.py install -``` - -or via git and pip: - -```bash -ronald@brouette:~$ git clone https://github.com/savon-noir/python-libnmap.git -ronald@brouette:~$ cd python-libnmap -ronald@brouette:~$ pip install . -``` - -## Examples - -Some codes samples are available in the examples directory or in the [documentation](https://libnmap.readthedocs.org). - -Among other example, you notice an sample code pushing nmap scan reports in an ElasticSearch instance and allowing you to create fancy dashboards in Kibana like the screenshot below: - -![Kibanane](https://github.com/savon-noir/python-libnmap/blob/es/examples/kibanalibnmap.png) - -## Contributors - -Mike @bmx0r Boutillier for S3 and SQL-Alechemy plugins and for the constructive critics. Thanks! diff --git a/README.rst b/README.rst new file mode 100644 index 0000000..f097b19 --- /dev/null +++ b/README.rst @@ -0,0 +1,159 @@ +python-libnmap +============== + +Code status +----------- + +|preflight-check| |Coverage Status| |License| + +Use cases +--------- + +libnmap is a python library enabling python developers to manipulate +nmap process and data. + +libnmap is what you were looking for if you need to implement the +following: + +- automate or schedule nmap scans on a regular basis +- manipulate nmap scans results to do reporting +- compare and diff nmap scans to generate graphs +- batch process scan reports +- … + +The above uses cases will be easy to implement with the help of the +libnmap modules. + +libnmap modules +--------------- + +The lib currently offers the following modules: + +- **process**: enables you to launch nmap scans +- **parse**: enables you to parse nmap reports or scan results (only + XML so far) from a file, a string,… +- **report**: enables you to manipulate a parsed scan result and + de/serialize scan results in a json format +- **diff**: enables you to see what changed between two scans +- **common**: contains basic nmap objects like NmapHost and + NmapService. It is to note that each object can be “diff()ed” with + another similar object. +- **plugins**: enables you to support datastores for your scan results + directly in the “NmapReport” object. from report module: + + - mongodb: insert/get/getAll/delete + - sqlalchemy: insert/get/getAll/delete + - aws s3: insert/get/getAll/delete (not supported for python3 since + boto is not supporting py3) + - csv: todo (easy to implement) + - elastic search: todo + +Documentation +------------- + +All the documentation is available on `read the +docs `__. This documentation contains +small code samples that you directly reuse. + +Dependencies +------------ + +libnmap has by default no dependencies, except defusedxml if you need to +import untrusted XML scans data. + +The only additional python modules you’ll have to install depends if you +wish to use libnmap to store reports on an exotic data store via +libnmap’s independents plugins. + +Below the list of optional dependencies: + +- `sqlalchemy `__ (+the driver + ie:MySQL-python) +- `pymongo `__ +- `boto `__ + +Security +-------- + +If you are importing/parsing untrusted XML scan outputs with +python-libnmap, install defusedxml library: + +.. code:: bash + + ronald@brouette:~/dev$ pip install defusedxml + +This will prevent you from being vulnerable to `XML External Entities +attacks `__. + +For more information, read the `official libnmap +documentation `__ + +This note relates to a cascaded CVE vulnerability from the python core +library XML ElementTree. Nevertheless, python-libnmap has been assigned +an `official +CVE `__ +to track this issue. + +This CVE is addressed from v0.7.1. + +Python Support +-------------- + +The libnmap code is tested against the following python interpreters: + +- Python 2.7 +- Python 3.6 +- Python 3.7 +- Python 3.8 + +Install +------- + +You can install libnmap via pip: + +.. code:: bash + + ronald@brouette:~$ pip install python-libnmap + +or via git and dist utils (à l’ancienne): + +.. code:: bash + + ronald@brouette:~$ git clone https://github.com/savon-noir/python-libnmap.git + ronald@brouette:~$ cd python-libnmap + ronald@brouette:~$ python setup.py install + +or via git and pip: + +.. code:: bash + + ronald@brouette:~$ git clone https://github.com/savon-noir/python-libnmap.git + ronald@brouette:~$ cd python-libnmap + ronald@brouette:~$ pip install . + +Examples +-------- + +Some codes samples are available in the examples directory or in the +`documentation `__. + +Among other example, you notice an sample code pushing nmap scan reports +in an ElasticSearch instance and allowing you to create fancy dashboards +in Kibana like the screenshot below: + +.. figure:: https://github.com/savon-noir/python-libnmap/blob/es/examples/kibanalibnmap.png + :alt: Kibanane + + Kibanane + +Contributors +------------ + +Mike @bmx0r Boutillier for S3 and SQL-Alechemy plugins and for the +constructive critics. Thanks! + +.. |preflight-check| image:: https://github.com/savon-noir/python-libnmap/workflows/Preflight%20Check/badge.svg +.. |Coverage Status| image:: https://coveralls.io/repos/github/savon-noir/python-libnmap/badge.svg?branch=master + :target: https://coveralls.io/github/savon-noir/python-libnmap?branch=master +.. |License| image:: https://img.shields.io/badge/License-Apache%202.0-blue.svg + :target: https://opensource.org/licenses/Apache-2.0 diff --git a/setup.py b/setup.py index b898211..d86f6e4 100644 --- a/setup.py +++ b/setup.py @@ -1,7 +1,7 @@ # -*- coding: utf-8 -*- from distutils.core import setup -with open("README.md") as rfile: +with open("README.rst") as rfile: long_description = rfile.read() setup( From d142c00b43fe4972daeb12f4aef639a0ef700ef3 Mon Sep 17 00:00:00 2001 From: Ronald Date: Wed, 16 Dec 2020 23:34:17 +0100 Subject: [PATCH 51/88] fix: bump up to v0.7.2 needed following pypi issue --- CHANGELOG.md | 2 +- README.rst | 4 +--- TODO | 41 ++++++++++++++++++++++++----------------- docs/conf.py | 2 +- docs/parser.rst | 4 ++-- libnmap/__init__.py | 2 +- setup.py | 2 +- 7 files changed, 31 insertions(+), 26 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7fed80b..824e7d2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,7 +5,7 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). (or tries to...) -## [v0.7.1] +## [v0.7.2] 2020-12-16 ### Added diff --git a/README.rst b/README.rst index f097b19..4951e71 100644 --- a/README.rst +++ b/README.rst @@ -94,7 +94,7 @@ an `official CVE `__ to track this issue. -This CVE is addressed from v0.7.1. +This CVE is addressed from v0.7.2. Python Support -------------- @@ -144,8 +144,6 @@ in Kibana like the screenshot below: .. figure:: https://github.com/savon-noir/python-libnmap/blob/es/examples/kibanalibnmap.png :alt: Kibanane - Kibanane - Contributors ------------ diff --git a/TODO b/TODO index 4af529b..6aa4c65 100644 --- a/TODO +++ b/TODO @@ -1,25 +1,32 @@ -0.7.1: clean-up blacked code and pylint it -0.7.1: add unittest for defusedxml to fix billionlaugh and external entities security issues -0.7.1: Change License from CC-BY to Apache 2.0 -0.7.1: Enabled defusedxml support as preferred option for parsing () -0.7.1: add extra_requires for plugins deps and defusedxml -0.7.1: Remove code duplication in sudo_run and sudo_run_background from process.py -0.7.1: Fix empty nmap outputs due to subprocess race condition (Merge PR79 from @Shouren) -0.7.1: Added banner_dict support + unittest (Merge edited PR from @cfoulds) +0.7.2: clean-up blacked code and pylint it +0.7.2: add unittest for defusedxml to fix billionlaugh and external entities security issues +0.7.2: Change License from CC-BY to Apache 2.0 +0.7.2: Enabled defusedxml support as preferred option for parsing () +0.7.2: add extra_requires for plugins deps and defusedxml +0.7.2: Remove code duplication in sudo_run and sudo_run_background from process.py +0.7.2: Fix empty nmap outputs due to subprocess race condition (Merge PR79 from @Shouren) +0.7.2: Added banner_dict support + unittest (Merge edited PR from @cfoulds) release: - changelog date not respecting KACL specs - check https://github.com/anton-yurchenko/git-release - https://github.com/sean0x42/markdown-extract -0.7.2: add CSV backend support -0.7.2: improve API for NSE scripts -0.7.2: add support for post,pre and host scripts -0.7.2: add a Contribution guideline page -0.7.2: add development environment config and setup -0.7.2: add pre-commit hooks to enforce black and isort -0.7.2: automate in github actions the git workflow + doc update + pypi update +Contribution file: +- specify where version needs to be set before adding tag to commit + - libnmap/__init__.py + - docs/conf.py + - setup.py + - CHANGELOG.md (set correct date) -0.7.3: Add support and tests for traceroute in nmap +0.7.3: add CSV backend support +0.7.3: improve API for NSE scripts +0.7.3: add support for post,pre and host scripts +0.7.3: add a Contribution guideline page +0.7.3: add development environment config and setup +0.7.3: add pre-commit hooks to enforce black and isort +0.7.3: automate in github actions the git workflow + doc update + pypi update -0.7.4: create complete python testing environment based on docker-compose and some examples +0.7.4: Add support and tests for traceroute in nmap + +0.7.5: create complete python testing environment based on docker-compose and some examples diff --git a/docs/conf.py b/docs/conf.py index 07e9dc8..6e0d4ee 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -51,7 +51,7 @@ # The short X.Y version. version = "0.7" # The full version, including alpha/beta/rc tags. -release = "0.7.1" +release = "0.7.2" # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. diff --git a/docs/parser.rst b/docs/parser.rst index 9673859..c0a1a70 100644 --- a/docs/parser.rst +++ b/docs/parser.rst @@ -12,7 +12,7 @@ Security note for libnmap.parser By default, python-libnmap's parser module does not enforces an extra XML parser module than the one provided in the python core distribution. -In versions previous to 0.7.1, by default, the `ElementTree XML API was used `_. +In versions previous to 0.7.2, by default, the `ElementTree XML API was used `_. This XML library is vulnerable to several `XML External Entities attacks `_ which may lead to: - Denial of Service attacks @@ -107,4 +107,4 @@ NmapParser methods .. automodule:: libnmap.parser .. autoclass:: NmapParser - :members: \ No newline at end of file + :members: diff --git a/libnmap/__init__.py b/libnmap/__init__.py index b3b5f89..411e762 100644 --- a/libnmap/__init__.py +++ b/libnmap/__init__.py @@ -5,4 +5,4 @@ __maintainer__ = "Ronald Bister" __email__ = "mini.pelle@gmail.com" __license__ = "Apache 2.0" -__version__ = "0.7.1" +__version__ = "0.7.2" diff --git a/setup.py b/setup.py index d86f6e4..4643b7f 100644 --- a/setup.py +++ b/setup.py @@ -6,7 +6,7 @@ setup( name="python-libnmap", - version="0.7.1", + version="0.7.2", author="Ronald Bister", author_email="mini.pelle@gmail.com", packages=["libnmap", "libnmap.plugins", "libnmap.objects"], From 4e969dd43ef92f4a4c4243e70629595b88da4f0b Mon Sep 17 00:00:00 2001 From: Konstantin Weddige Date: Mon, 4 Jan 2021 18:10:51 +0100 Subject: [PATCH 52/88] Set encoding for README As the default encoding is platform dependent, python-libnmap can not be installed if the preferred encoding is not UTF-8. --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 4643b7f..9668371 100644 --- a/setup.py +++ b/setup.py @@ -1,7 +1,7 @@ # -*- coding: utf-8 -*- from distutils.core import setup -with open("README.rst") as rfile: +with open("README.rst", encoding="utf-8") as rfile: long_description = rfile.read() setup( From 8f442747a7a16969309d6f7653ad1b13a3a99bae Mon Sep 17 00:00:00 2001 From: Freddie Chessell <4047565+FreddieDev@users.noreply.github.com> Date: Mon, 13 Sep 2021 14:09:31 +0100 Subject: [PATCH 53/88] Fix Windows charmap exception for quote characters When installing on Windows with `pip install python-libnmap`, these characters cause the error: ``` UnicodeDecodeError: 'charmap' codec can't decode byte 0x9d in position 1117: character maps to ``` --- README.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.rst b/README.rst index 4951e71..023726f 100644 --- a/README.rst +++ b/README.rst @@ -36,10 +36,10 @@ The lib currently offers the following modules: de/serialize scan results in a json format - **diff**: enables you to see what changed between two scans - **common**: contains basic nmap objects like NmapHost and - NmapService. It is to note that each object can be “diff()ed” with + NmapService. It is to note that each object can be "diff()ed" with another similar object. - **plugins**: enables you to support datastores for your scan results - directly in the “NmapReport” object. from report module: + directly in the "NmapReport" object. from report module: - mongodb: insert/get/getAll/delete - sqlalchemy: insert/get/getAll/delete From 28060b437e800d9bc9840cca8f171ce7a3fb34dd Mon Sep 17 00:00:00 2001 From: Tim Gates Date: Sun, 5 Sep 2021 06:54:47 +1000 Subject: [PATCH 54/88] docs: Fix a few typos There are small typos in: - docs/diff.rst - docs/index.rst - docs/parser.rst - libnmap/objects/os.py - libnmap/objects/report.py - libnmap/plugins/backendplugin.py - libnmap/plugins/es.py - libnmap/plugins/mongodb.py - libnmap/plugins/sql.py - libnmap/process.py Fixes: - Should read `retrieve` rather than `retreive`. - Should read `instantiated` rather than `instanciated`. - Should read `privileges` rather than `priviledges`. - Should read `number` rather than `numer`. - Should read `what` rather than `waht`. - Should read `support` rather than `suport`. - Should read `receive` rather than `reeive`. --- docs/diff.rst | 2 +- docs/index.rst | 2 +- docs/parser.rst | 2 +- libnmap/objects/os.py | 2 +- libnmap/objects/report.py | 4 ++-- libnmap/plugins/backendplugin.py | 2 +- libnmap/plugins/es.py | 2 +- libnmap/plugins/mongodb.py | 2 +- libnmap/plugins/sql.py | 4 ++-- libnmap/process.py | 6 +++--- 10 files changed, 14 insertions(+), 14 deletions(-) diff --git a/docs/diff.rst b/docs/diff.rst index 658c3f9..e9950d9 100644 --- a/docs/diff.rst +++ b/docs/diff.rst @@ -17,7 +17,7 @@ Those methods return a python set() of keys which have been changed/added/remove object to another. The keys of each objects could be found in the implementation of the get_dict() methods of the compared objects. -The example below is a heavy version of going through all nested objects to see waht has changed after a diff:: +The example below is a heavy version of going through all nested objects to see what has changed after a diff:: #!/usr/bin/env python diff --git a/docs/index.rst b/docs/index.rst index 7e89213..7226bbf 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -21,7 +21,7 @@ libnmap is a python toolkit for manipulating nmap. It currently offers the follo - plugins: enables you to support datastores for your scan results directly in the "NmapReport" object from report module - mongodb: only plugin implemented so far, ultra basic, for POC purpose only - - sqlalchemy: Allow to store/retreive NmapReport to sqlite/mysql/... all engine supported by sqlalchemy + - sqlalchemy: Allow to store/retrieve NmapReport to sqlite/mysql/... all engine supported by sqlalchemy - rabbitMQ : todo - couchdb: todo - elastic search: todo diff --git a/docs/parser.rst b/docs/parser.rst index c0a1a70..09bc5d2 100644 --- a/docs/parser.rst +++ b/docs/parser.rst @@ -63,7 +63,7 @@ Using libnmap.parser module NmapParser parse the whole data and returns nmap objects usable via their documented API. -The NmapParser should never be instanciated and only the following methods should be called: +The NmapParser should never be instantiated and only the following methods should be called: - NmapParser.parse(string) - NmapParser.parse_fromfile(file_path) diff --git a/libnmap/objects/os.py b/libnmap/objects/os.py index 912c32e..30d3f3b 100644 --- a/libnmap/objects/os.py +++ b/libnmap/objects/os.py @@ -269,7 +269,7 @@ def __repr__(self): class NmapOSFingerprint(object): """ NmapOSFingerprint is a easier API for using os fingerprinting. - Data for OS fingerprint ( tag) is instanciated from + Data for OS fingerprint ( tag) is instantiated from a NmapOSFingerprint which is accessible in NmapHost via NmapHost.os """ diff --git a/libnmap/objects/report.py b/libnmap/objects/report.py index cd9a556..8c69eb4 100644 --- a/libnmap/objects/report.py +++ b/libnmap/objects/report.py @@ -254,7 +254,7 @@ def elapsed(self): @property def hosts_up(self): """ - Accessor returning the numer of host detected + Accessor returning the number of host detected as 'up' during the scan. :return: integer (0 >= or -1) @@ -270,7 +270,7 @@ def hosts_up(self): @property def hosts_down(self): """ - Accessor returning the numer of host detected + Accessor returning the number of host detected as 'down' during the scan. :return: integer (0 >= or -1) diff --git a/libnmap/plugins/backendplugin.py b/libnmap/plugins/backendplugin.py index cfbf393..0a7c9a5 100644 --- a/libnmap/plugins/backendplugin.py +++ b/libnmap/plugins/backendplugin.py @@ -30,7 +30,7 @@ def delete(self, id): def get(self, id): """ - retreive a NmapReport from the backend + retrieve a NmapReport from the backend :param id: str :return: NmapReport """ diff --git a/libnmap/plugins/es.py b/libnmap/plugins/es.py index 688a3d2..8db8623 100644 --- a/libnmap/plugins/es.py +++ b/libnmap/plugins/es.py @@ -48,7 +48,7 @@ def delete(self, id): def get(self, id): """ - retreive a NmapReport from the backend + retrieve a NmapReport from the backend :param id: str :return: NmapReport """ diff --git a/libnmap/plugins/mongodb.py b/libnmap/plugins/mongodb.py index 1e707f3..80bfe1a 100644 --- a/libnmap/plugins/mongodb.py +++ b/libnmap/plugins/mongodb.py @@ -15,7 +15,7 @@ class NmapMongodbPlugin(NmapBackendPlugin): Implementation is made using pymongo Object of this class must be create via the BackendPluginFactory.create(**url) where url is a named dict like - {'plugin_name': "mongodb"} this dict may reeive all the param + {'plugin_name': "mongodb"} this dict may receive all the param MongoClient() support """ diff --git a/libnmap/plugins/sql.py b/libnmap/plugins/sql.py index dc0ede4..95eb36f 100644 --- a/libnmap/plugins/sql.py +++ b/libnmap/plugins/sql.py @@ -77,7 +77,7 @@ def __init__(self, **kwargs): - create all the necessary obj to discuss with the DB - create all the mapping(ORM) - todo : suport the : sqlalchemy.engine_from_config + todo : support the : sqlalchemy.engine_from_config :param **kwargs: :raises: ValueError if no url is given, @@ -121,7 +121,7 @@ def insert(self, nmap_report): def get(self, report_id=None): """ - retreive a NmapReport from the backend + retrieve a NmapReport from the backend :param id: str diff --git a/libnmap/process.py b/libnmap/process.py index 639a7f1..704c344 100644 --- a/libnmap/process.py +++ b/libnmap/process.py @@ -22,7 +22,7 @@ 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. + starts a new task during the scan, a new class will be instantiated. 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. @@ -215,7 +215,7 @@ def _ensure_user_exists(self, username=""): 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 + privileges 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() @@ -245,7 +245,7 @@ def sudo_run(self, run_as="root"): 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. + nmap scan with privileges 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 From c36fecde90017befeb4853396d0e2aac93c95b64 Mon Sep 17 00:00:00 2001 From: Facundo Tuesca Date: Mon, 29 Aug 2022 15:39:38 +0200 Subject: [PATCH 55/88] Fix CVE-2022-30284 by validating targets This change adds an extra validation to `NmapProcess` that does some basic checks on the `targets` parameter. It checks that no unexpected characters are present in each target, and that if dashes are present, they are not located at the start or end of the string (a valid FQDN can have dashes except in those positions). This last check fixes CVE-2022-30284, which allowed to inject extra command line options by using a target such as `127.0.0.1,--script,http-fetch....`. --- libnmap/process.py | 25 +++++++++++++++++++++++++ libnmap/test/test_process.py | 31 ++++++++++++++++++++++++++++--- 2 files changed, 53 insertions(+), 3 deletions(-) diff --git a/libnmap/process.py b/libnmap/process.py index 704c344..d1aba76 100644 --- a/libnmap/process.py +++ b/libnmap/process.py @@ -4,6 +4,7 @@ import os import platform import shlex +import string import subprocess import warnings from threading import Thread @@ -129,6 +130,9 @@ def __init__( "Supplied target list should be either a string or a list" ) + for target in self.__nmap_targets: + self.__validate_target(target) + self._nmap_options = set(options.split()) if safe_mode and not self._nmap_options.isdisjoint(unsafe_opts): raise Exception( @@ -480,6 +484,27 @@ def __build_windows_cmdline(self): cmdline += self.__nmap_targets # already a list return cmdline + @staticmethod + def __validate_target(target): + # See https://nmap.org/book/man-target-specification.html for all the + # ways targets can be specified + allowed_characters = frozenset( + string.ascii_letters + string.digits + "-.:/% " + ) + if not set(target).issubset(allowed_characters): + raise Exception( + "Target '{}' contains invalid characters".format(target) + ) + # FQDN can contain dashes anywhere except at the beginning or end + # This check also fixes/prevents CVE-2022-30284, which depends on being + # able to pass options such as --script as a target + elif target.startswith("-") or target.endswith("-"): + raise Exception( + "Target '{}' cannot begin or end with a dash ('-')".format( + target + ) + ) + @property def command(self): """ diff --git a/libnmap/test/test_process.py b/libnmap/test/test_process.py index 4345493..b2f3297 100644 --- a/libnmap/test/test_process.py +++ b/libnmap/test/test_process.py @@ -19,19 +19,26 @@ def setUp(self): self._assertRaisesRegex = self.assertRaisesRegexp self.fdir = os.path.dirname(os.path.realpath(__file__)) - def test_check_targets(self): - invalid_target_tests = [{"a": "bba"}, 5] + def test_check_valid_targets(self): valid_target_tests = [ {"value": "127.0.0.1, 1.1.1.1, 2.20.202", "size": 3}, {"value": ["127.0.0.1", "1.1.1.1", "2.20.202.2"], "size": 3}, {"value": [" 127.0.0.1", " 1.1.1.1"], "size": 2}, {"value": " 127.0.0.1, 1.1.1.1 , a", "size": 3}, + {"value": ["192.168.10.0/24", "192.168.0-255.1-254"], "size": 2}, + {"value": ["fe80::a8bb:ccff:fedd:eeff%eth0"], "size": 1}, + {"value": ["my-domain.com", "my-num3r1c-domain.com"], "size": 2}, ] for vtarget in valid_target_tests: nmapobj = NmapProcess(targets=vtarget["value"], options="-sP") self.assertEqual(vtarget["size"], len(nmapobj.targets)) - for vtarget in invalid_target_tests: + def test_check_invalid_targets(self): + invalid_target_type_tests = [{"a": "bba"}, 5] + invalid_target_character_tests = ["1.1.1.1$", "invalid_domain.com"] + invalid_target_dash_tests = ["-invalid-target", "--option"] + + for vtarget in invalid_target_type_tests: self._assertRaisesRegex( Exception, "Supplied target list should be either a string or a list", @@ -40,6 +47,24 @@ def test_check_targets(self): options="-sP", ) + for vtarget in invalid_target_character_tests: + self._assertRaisesRegex( + Exception, + "contains invalid characters", + NmapProcess, + targets=vtarget, + options="-sP", + ) + + for vtarget in invalid_target_dash_tests: + self._assertRaisesRegex( + Exception, + "cannot begin or end with a dash", + NmapProcess, + targets=vtarget, + options="-sP", + ) + def test_nmap_options(self): invalid_options = ["--iflist"] From 866f2aa4db5bf222a6d13ef722b417aae4489397 Mon Sep 17 00:00:00 2001 From: savon-noir Date: Thu, 1 Sep 2022 21:32:30 +0200 Subject: [PATCH 56/88] lint: ran linters on code --- .github/workflows/preflight_check.yaml | 4 ++-- .pre-commit-config.yaml | 2 +- CHANGELOG.md | 6 +++--- CODEOWNERS | 2 +- LICENCE | 2 +- NOTICE | 2 +- TODO | 4 ++-- config/database.yml | 3 +-- libnmap/test/files/1_host_ping.xml | 2 +- libnmap/test/files/2_hosts.json | 2 +- libnmap/test/files/defused_et_included.xml | 2 +- libnmap/test/files/defused_et_local_includer.xml | 2 +- libnmap/test/files/diff_1_host_ping_mac_changed.xml | 2 +- libnmap/test/files/dionaea_scan.xml | 2 +- libnmap/test/files/test_osclass.xml | 2 +- requirements-dev.txt | 6 +++--- tox.ini | 4 ++-- 17 files changed, 24 insertions(+), 25 deletions(-) diff --git a/.github/workflows/preflight_check.yaml b/.github/workflows/preflight_check.yaml index 3368218..d6c3729 100644 --- a/.github/workflows/preflight_check.yaml +++ b/.github/workflows/preflight_check.yaml @@ -6,7 +6,7 @@ on: - '**' pull_request: branches: - - main + - '**' jobs: lint: @@ -70,4 +70,4 @@ jobs: pip3 install --upgrade coveralls coveralls --finish env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} \ No newline at end of file + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index bf80633..b95e79d 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -7,7 +7,7 @@ repos: - id: end-of-file-fixer - id: trailing-whitespace - repo: https://github.com/psf/black - rev: 20.8b1 + rev: 22.1.0 hooks: - id: black args: [--line-length=79] diff --git a/CHANGELOG.md b/CHANGELOG.md index 824e7d2..8d20aae 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -142,7 +142,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Added NmapReport.endtimestr - Added and tested cElementTree support (performance) -### Fixed +### Fixed - Fixed bug when NmapReport.summary is empty @@ -199,10 +199,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Added stop() to terminate nmap scan running in background -### Fixed +### Fixed - Bug corrected in missing data from nmap scan output - + ## [v0.3.1] - 17/06/2013 ### Changed diff --git a/CODEOWNERS b/CODEOWNERS index 7b644ee..78c6fef 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -1 +1 @@ -* @savon-noir \ No newline at end of file +* @savon-noir diff --git a/LICENCE b/LICENCE index 92138ca..067f271 100644 --- a/LICENCE +++ b/LICENCE @@ -10,4 +10,4 @@ distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and - limitations under the License. \ No newline at end of file + limitations under the License. diff --git a/NOTICE b/NOTICE index 88e053c..615671d 100644 --- a/NOTICE +++ b/NOTICE @@ -1,4 +1,4 @@ python-libnmap Copyright 2020 Ronald Bister -This product includes software developed by Ronald Bister \ No newline at end of file +This product includes software developed by Ronald Bister diff --git a/TODO b/TODO index 6aa4c65..bf03075 100644 --- a/TODO +++ b/TODO @@ -1,4 +1,4 @@ -0.7.2: clean-up blacked code and pylint it +0.7.2: clean-up blacked code and pylint it 0.7.2: add unittest for defusedxml to fix billionlaugh and external entities security issues 0.7.2: Change License from CC-BY to Apache 2.0 0.7.2: Enabled defusedxml support as preferred option for parsing () @@ -10,7 +10,7 @@ release: - changelog date not respecting KACL specs - check https://github.com/anton-yurchenko/git-release -- https://github.com/sean0x42/markdown-extract +- https://github.com/sean0x42/markdown-extract Contribution file: - specify where version needs to be set before adding tag to commit diff --git a/config/database.yml b/config/database.yml index 7a28ee5..5c7a0f7 100644 --- a/config/database.yml +++ b/config/database.yml @@ -4,7 +4,6 @@ sqlite: timeout: 500 mysql: adapter: mysql2 - database: poulet + database: poulet username: encoding: utf8 - diff --git a/libnmap/test/files/1_host_ping.xml b/libnmap/test/files/1_host_ping.xml index 92fde18..0a0b797 100644 --- a/libnmap/test/files/1_host_ping.xml +++ b/libnmap/test/files/1_host_ping.xml @@ -16,4 +16,4 @@ - \ No newline at end of file + diff --git a/libnmap/test/files/2_hosts.json b/libnmap/test/files/2_hosts.json index f7099ea..d6e1727 100644 --- a/libnmap/test/files/2_hosts.json +++ b/libnmap/test/files/2_hosts.json @@ -1 +1 @@ -{"__NmapReport__": {"_nmaprun": {"scanner": "nmap", "args": "nmap -sS -vv -oX 2_hosts.xml localhost scanme.nmap.org", "start": "1361737906", "startstr": "Sun Feb 24 21:31:46 2013", "version": "5.51", "xmloutputversion": "1.03"}, "_scaninfo": {"type": "syn", "protocol": "tcp", "numservices": "1000", "services": "1,3-4,6-7,9,13,17,19-26,30,32-33,37,42-43,49,53,70,79-85,88-90,99-100,106,109-111,113,119,125,135,139,143-144,146,161,163,179,199,211-212,222,254-256,259,264,280,301,306,311,340,366,389,406-407,416-417,425,427,443-445,458,464-465,481,497,500,512-515,524,541,543-545,548,554-555,563,587,593,616-617,625,631,636,646,648,666-668,683,687,691,700,705,711,714,720,722,726,749,765,777,783,787,800-801,808,843,873,880,888,898,900-903,911-912,981,987,990,992-993,995,999-1002,1007,1009-1011,1021-1100,1102,1104-1108,1110-1114,1117,1119,1121-1124,1126,1130-1132,1137-1138,1141,1145,1147-1149,1151-1152,1154,1163-1166,1169,1174-1175,1183,1185-1187,1192,1198-1199,1201,1213,1216-1218,1233-1234,1236,1244,1247-1248,1259,1271-1272,1277,1287,1296,1300-1301,1309-1311,1322,1328,1334,1352,1417,1433-1434,1443,1455,1461,1494,1500-1501,1503,1521,1524,1533,1556,1580,1583,1594,1600,1641,1658,1666,1687-1688,1700,1717-1721,1723,1755,1761,1782-1783,1801,1805,1812,1839-1840,1862-1864,1875,1900,1914,1935,1947,1971-1972,1974,1984,1998-2010,2013,2020-2022,2030,2033-2035,2038,2040-2043,2045-2049,2065,2068,2099-2100,2103,2105-2107,2111,2119,2121,2126,2135,2144,2160-2161,2170,2179,2190-2191,2196,2200,2222,2251,2260,2288,2301,2323,2366,2381-2383,2393-2394,2399,2401,2492,2500,2522,2525,2557,2601-2602,2604-2605,2607-2608,2638,2701-2702,2710,2717-2718,2725,2800,2809,2811,2869,2875,2909-2910,2920,2967-2968,2998,3000-3001,3003,3005-3007,3011,3013,3017,3030-3031,3052,3071,3077,3128,3168,3211,3221,3260-3261,3268-3269,3283,3300-3301,3306,3322-3325,3333,3351,3367,3369-3372,3389-3390,3404,3476,3493,3517,3527,3546,3551,3580,3659,3689-3690,3703,3737,3766,3784,3800-3801,3809,3814,3826-3828,3851,3869,3871,3878,3880,3889,3905,3914,3918,3920,3945,3971,3986,3995,3998,4000-4006,4045,4111,4125-4126,4129,4224,4242,4279,4321,4343,4443-4446,4449,4550,4567,4662,4848,4899-4900,4998,5000-5004,5009,5030,5033,5050-5051,5054,5060-5061,5080,5087,5100-5102,5120,5190,5200,5214,5221-5222,5225-5226,5269,5280,5298,5357,5405,5414,5431-5432,5440,5500,5510,5544,5550,5555,5560,5566,5631,5633,5666,5678-5679,5718,5730,5800-5802,5810-5811,5815,5822,5825,5850,5859,5862,5877,5900-5904,5906-5907,5910-5911,5915,5922,5925,5950,5952,5959-5963,5987-5989,5998-6007,6009,6025,6059,6100-6101,6106,6112,6123,6129,6156,6346,6389,6502,6510,6543,6547,6565-6567,6580,6646,6666-6669,6689,6692,6699,6779,6788-6789,6792,6839,6881,6901,6969,7000-7002,7004,7007,7019,7025,7070,7100,7103,7106,7200-7201,7402,7435,7443,7496,7512,7625,7627,7676,7741,7777-7778,7800,7911,7920-7921,7937-7938,7999-8002,8007-8011,8021-8022,8031,8042,8045,8080-8090,8093,8099-8100,8180-8181,8192-8194,8200,8222,8254,8290-8292,8300,8333,8383,8400,8402,8443,8500,8600,8649,8651-8652,8654,8701,8800,8873,8888,8899,8994,9000-9003,9009-9011,9040,9050,9071,9080-9081,9090-9091,9099-9103,9110-9111,9200,9207,9220,9290,9415,9418,9485,9500,9502-9503,9535,9575,9593-9595,9618,9666,9876-9878,9898,9900,9917,9929,9943-9944,9968,9998-10004,10009-10010,10012,10024-10025,10082,10180,10215,10243,10566,10616-10617,10621,10626,10628-10629,10778,11110-11111,11967,12000,12174,12265,12345,13456,13722,13782-13783,14000,14238,14441-14442,15000,15002-15004,15660,15742,16000-16001,16012,16016,16018,16080,16113,16992-16993,17877,17988,18040,18101,18988,19101,19283,19315,19350,19780,19801,19842,20000,20005,20031,20221-20222,20828,21571,22939,23502,24444,24800,25734-25735,26214,27000,27352-27353,27355-27356,27715,28201,30000,30718,30951,31038,31337,32768-32785,33354,33899,34571-34573,35500,38292,40193,40911,41511,42510,44176,44442-44443,44501,45100,48080,49152-49161,49163,49165,49167,49175-49176,49400,49999-50003,50006,50300,50389,50500,50636,50800,51103,51493,52673,52822,52848,52869,54045,54328,55055-55056,55555,55600,56737-56738,57294,57797,58080,60020,60443,61532,61900,62078,63331,64623,64680,65000,65129,65389"}, "_hosts": [{"__NmapHost__": {"_starttime": "1361737906", "_endtime": "1361737906", "_hostnames": ["localhost", "localhost"], "_status": {"state": "up", "reason": "localhost-response"}, "_services": [{"__NmapService__": {"_portid": 22, "_protocol": "tcp", "_state": {"state": "open", "reason": "syn-ack", "reason_ttl": "64"}, "_service": {"name": "ssh", "method": "table", "conf": "3", "cpelist": []}, "_cpelist": [], "_owner": "", "_reason": "syn-ack", "_reason_ip": "", "_reason_ttl": "64", "_servicefp": "", "_tunnel": "", "_service_extras": {"scripts": []}}}, {"__NmapService__": {"_portid": 25, "_protocol": "tcp", "_state": {"state": "open", "reason": "syn-ack", "reason_ttl": "64"}, "_service": {"name": "smtp", "method": "table", "conf": "3", "cpelist": []}, "_cpelist": [], "_owner": "", "_reason": "syn-ack", "_reason_ip": "", "_reason_ttl": "64", "_servicefp": "", "_tunnel": "", "_service_extras": {"scripts": []}}}, {"__NmapService__": {"_portid": 111, "_protocol": "tcp", "_state": {"state": "open", "reason": "syn-ack", "reason_ttl": "64"}, "_service": {"name": "rpcbind", "method": "table", "conf": "3", "cpelist": []}, "_cpelist": [], "_owner": "", "_reason": "syn-ack", "_reason_ip": "", "_reason_ttl": "64", "_servicefp": "", "_tunnel": "", "_service_extras": {"scripts": []}}}, {"__NmapService__": {"_portid": 631, "_protocol": "tcp", "_state": {"state": "open", "reason": "syn-ack", "reason_ttl": "64"}, "_service": {"name": "ipp", "method": "table", "conf": "3", "cpelist": []}, "_cpelist": [], "_owner": "", "_reason": "syn-ack", "_reason_ip": "", "_reason_ttl": "64", "_servicefp": "", "_tunnel": "", "_service_extras": {"scripts": []}}}, {"__NmapService__": {"_portid": 3306, "_protocol": "tcp", "_state": {"state": "open", "reason": "syn-ack", "reason_ttl": "64"}, "_service": {"name": "mysql", "method": "table", "conf": "3", "cpelist": []}, "_cpelist": [], "_owner": "", "_reason": "syn-ack", "_reason_ip": "", "_reason_ttl": "64", "_servicefp": "", "_tunnel": "", "_service_extras": {"scripts": []}}}], "_extras": {"extraports": {"state": {"state": "closed", "count": "995"}, "count": {"state": "closed", "count": "995"}, "reasons": [{"reason": "resets", "count": "995"}]}, "times": {"srtt": "7", "rttvar": "0", "to": "100000"}}, "_osfingerprinted": false, "os": {"__NmapOSFingerprint__": {"_NmapOSFingerprint__osmatches": [], "_NmapOSFingerprint__ports_used": [], "_NmapOSFingerprint__fingerprints": []}}, "_ipv4_addr": "127.0.0.1", "_ipv6_addr": null, "_mac_addr": null, "_vendor": null, "_main_address": "127.0.0.1", "_address": [{"addr": "127.0.0.1", "addrtype": "ipv4"}]}}, {"__NmapHost__": {"_starttime": "1361737906", "_endtime": "1361738040", "_hostnames": ["scanme.nmap.org", "scanme.nmap.org"], "_status": {"state": "up", "reason": "echo-reply"}, "_services": [{"__NmapService__": {"_portid": 22, "_protocol": "tcp", "_state": {"state": "open", "reason": "syn-ack", "reason_ttl": "53"}, "_service": {"name": "ssh", "method": "table", "conf": "3", "cpelist": []}, "_cpelist": [], "_owner": "", "_reason": "syn-ack", "_reason_ip": "", "_reason_ttl": "53", "_servicefp": "", "_tunnel": "", "_service_extras": {"scripts": []}}}, {"__NmapService__": {"_portid": 25, "_protocol": "tcp", "_state": {"state": "filtered", "reason": "admin-prohibited", "reason_ttl": "253", "reason_ip": "109.133.192.1"}, "_service": {"name": "smtp", "method": "table", "conf": "3", "cpelist": []}, "_cpelist": [], "_owner": "", "_reason": "admin-prohibited", "_reason_ip": "109.133.192.1", "_reason_ttl": "253", "_servicefp": "", "_tunnel": "", "_service_extras": {"scripts": []}}}, {"__NmapService__": {"_portid": 80, "_protocol": "tcp", "_state": {"state": "open", "reason": "syn-ack", "reason_ttl": "51"}, "_service": {"name": "http", "method": "table", "conf": "3", "cpelist": []}, "_cpelist": [], "_owner": "", "_reason": "syn-ack", "_reason_ip": "", "_reason_ttl": "51", "_servicefp": "", "_tunnel": "", "_service_extras": {"scripts": []}}}, {"__NmapService__": {"_portid": 9929, "_protocol": "tcp", "_state": {"state": "open", "reason": "syn-ack", "reason_ttl": "53"}, "_service": {"name": "nping-echo", "method": "table", "conf": "3", "cpelist": []}, "_cpelist": [], "_owner": "", "_reason": "syn-ack", "_reason_ip": "", "_reason_ttl": "53", "_servicefp": "", "_tunnel": "", "_service_extras": {"scripts": []}}}], "_extras": {"extraports": {"state": {"state": "closed", "count": "996"}, "count": {"state": "closed", "count": "996"}, "reasons": [{"reason": "resets", "count": "996"}]}, "times": {"srtt": "177425", "rttvar": "1981", "to": "185349"}}, "_osfingerprinted": false, "os": {"__NmapOSFingerprint__": {"_NmapOSFingerprint__osmatches": [], "_NmapOSFingerprint__ports_used": [], "_NmapOSFingerprint__fingerprints": []}}, "_ipv4_addr": "74.207.244.221", "_ipv6_addr": null, "_mac_addr": null, "_vendor": null, "_main_address": "74.207.244.221", "_address": [{"addr": "74.207.244.221", "addrtype": "ipv4"}]}}], "_runstats": {"finished": {"time": "1361738040", "timestr": "Sun Feb 24 21:34:00 2013", "elapsed": "134.36", "summary": "Nmap done at Sun Feb 24 21:34:00 2013; 2 IP addresses (2 hosts up) scanned in 134.36 seconds", "exit": "success"}, "hosts": {"up": "2", "down": "0", "total": "2"}}}} \ No newline at end of file +{"__NmapReport__": {"_nmaprun": {"scanner": "nmap", "args": "nmap -sS -vv -oX 2_hosts.xml localhost scanme.nmap.org", "start": "1361737906", "startstr": "Sun Feb 24 21:31:46 2013", "version": "5.51", "xmloutputversion": "1.03"}, "_scaninfo": {"type": "syn", "protocol": "tcp", "numservices": "1000", "services": "1,3-4,6-7,9,13,17,19-26,30,32-33,37,42-43,49,53,70,79-85,88-90,99-100,106,109-111,113,119,125,135,139,143-144,146,161,163,179,199,211-212,222,254-256,259,264,280,301,306,311,340,366,389,406-407,416-417,425,427,443-445,458,464-465,481,497,500,512-515,524,541,543-545,548,554-555,563,587,593,616-617,625,631,636,646,648,666-668,683,687,691,700,705,711,714,720,722,726,749,765,777,783,787,800-801,808,843,873,880,888,898,900-903,911-912,981,987,990,992-993,995,999-1002,1007,1009-1011,1021-1100,1102,1104-1108,1110-1114,1117,1119,1121-1124,1126,1130-1132,1137-1138,1141,1145,1147-1149,1151-1152,1154,1163-1166,1169,1174-1175,1183,1185-1187,1192,1198-1199,1201,1213,1216-1218,1233-1234,1236,1244,1247-1248,1259,1271-1272,1277,1287,1296,1300-1301,1309-1311,1322,1328,1334,1352,1417,1433-1434,1443,1455,1461,1494,1500-1501,1503,1521,1524,1533,1556,1580,1583,1594,1600,1641,1658,1666,1687-1688,1700,1717-1721,1723,1755,1761,1782-1783,1801,1805,1812,1839-1840,1862-1864,1875,1900,1914,1935,1947,1971-1972,1974,1984,1998-2010,2013,2020-2022,2030,2033-2035,2038,2040-2043,2045-2049,2065,2068,2099-2100,2103,2105-2107,2111,2119,2121,2126,2135,2144,2160-2161,2170,2179,2190-2191,2196,2200,2222,2251,2260,2288,2301,2323,2366,2381-2383,2393-2394,2399,2401,2492,2500,2522,2525,2557,2601-2602,2604-2605,2607-2608,2638,2701-2702,2710,2717-2718,2725,2800,2809,2811,2869,2875,2909-2910,2920,2967-2968,2998,3000-3001,3003,3005-3007,3011,3013,3017,3030-3031,3052,3071,3077,3128,3168,3211,3221,3260-3261,3268-3269,3283,3300-3301,3306,3322-3325,3333,3351,3367,3369-3372,3389-3390,3404,3476,3493,3517,3527,3546,3551,3580,3659,3689-3690,3703,3737,3766,3784,3800-3801,3809,3814,3826-3828,3851,3869,3871,3878,3880,3889,3905,3914,3918,3920,3945,3971,3986,3995,3998,4000-4006,4045,4111,4125-4126,4129,4224,4242,4279,4321,4343,4443-4446,4449,4550,4567,4662,4848,4899-4900,4998,5000-5004,5009,5030,5033,5050-5051,5054,5060-5061,5080,5087,5100-5102,5120,5190,5200,5214,5221-5222,5225-5226,5269,5280,5298,5357,5405,5414,5431-5432,5440,5500,5510,5544,5550,5555,5560,5566,5631,5633,5666,5678-5679,5718,5730,5800-5802,5810-5811,5815,5822,5825,5850,5859,5862,5877,5900-5904,5906-5907,5910-5911,5915,5922,5925,5950,5952,5959-5963,5987-5989,5998-6007,6009,6025,6059,6100-6101,6106,6112,6123,6129,6156,6346,6389,6502,6510,6543,6547,6565-6567,6580,6646,6666-6669,6689,6692,6699,6779,6788-6789,6792,6839,6881,6901,6969,7000-7002,7004,7007,7019,7025,7070,7100,7103,7106,7200-7201,7402,7435,7443,7496,7512,7625,7627,7676,7741,7777-7778,7800,7911,7920-7921,7937-7938,7999-8002,8007-8011,8021-8022,8031,8042,8045,8080-8090,8093,8099-8100,8180-8181,8192-8194,8200,8222,8254,8290-8292,8300,8333,8383,8400,8402,8443,8500,8600,8649,8651-8652,8654,8701,8800,8873,8888,8899,8994,9000-9003,9009-9011,9040,9050,9071,9080-9081,9090-9091,9099-9103,9110-9111,9200,9207,9220,9290,9415,9418,9485,9500,9502-9503,9535,9575,9593-9595,9618,9666,9876-9878,9898,9900,9917,9929,9943-9944,9968,9998-10004,10009-10010,10012,10024-10025,10082,10180,10215,10243,10566,10616-10617,10621,10626,10628-10629,10778,11110-11111,11967,12000,12174,12265,12345,13456,13722,13782-13783,14000,14238,14441-14442,15000,15002-15004,15660,15742,16000-16001,16012,16016,16018,16080,16113,16992-16993,17877,17988,18040,18101,18988,19101,19283,19315,19350,19780,19801,19842,20000,20005,20031,20221-20222,20828,21571,22939,23502,24444,24800,25734-25735,26214,27000,27352-27353,27355-27356,27715,28201,30000,30718,30951,31038,31337,32768-32785,33354,33899,34571-34573,35500,38292,40193,40911,41511,42510,44176,44442-44443,44501,45100,48080,49152-49161,49163,49165,49167,49175-49176,49400,49999-50003,50006,50300,50389,50500,50636,50800,51103,51493,52673,52822,52848,52869,54045,54328,55055-55056,55555,55600,56737-56738,57294,57797,58080,60020,60443,61532,61900,62078,63331,64623,64680,65000,65129,65389"}, "_hosts": [{"__NmapHost__": {"_starttime": "1361737906", "_endtime": "1361737906", "_hostnames": ["localhost", "localhost"], "_status": {"state": "up", "reason": "localhost-response"}, "_services": [{"__NmapService__": {"_portid": 22, "_protocol": "tcp", "_state": {"state": "open", "reason": "syn-ack", "reason_ttl": "64"}, "_service": {"name": "ssh", "method": "table", "conf": "3", "cpelist": []}, "_cpelist": [], "_owner": "", "_reason": "syn-ack", "_reason_ip": "", "_reason_ttl": "64", "_servicefp": "", "_tunnel": "", "_service_extras": {"scripts": []}}}, {"__NmapService__": {"_portid": 25, "_protocol": "tcp", "_state": {"state": "open", "reason": "syn-ack", "reason_ttl": "64"}, "_service": {"name": "smtp", "method": "table", "conf": "3", "cpelist": []}, "_cpelist": [], "_owner": "", "_reason": "syn-ack", "_reason_ip": "", "_reason_ttl": "64", "_servicefp": "", "_tunnel": "", "_service_extras": {"scripts": []}}}, {"__NmapService__": {"_portid": 111, "_protocol": "tcp", "_state": {"state": "open", "reason": "syn-ack", "reason_ttl": "64"}, "_service": {"name": "rpcbind", "method": "table", "conf": "3", "cpelist": []}, "_cpelist": [], "_owner": "", "_reason": "syn-ack", "_reason_ip": "", "_reason_ttl": "64", "_servicefp": "", "_tunnel": "", "_service_extras": {"scripts": []}}}, {"__NmapService__": {"_portid": 631, "_protocol": "tcp", "_state": {"state": "open", "reason": "syn-ack", "reason_ttl": "64"}, "_service": {"name": "ipp", "method": "table", "conf": "3", "cpelist": []}, "_cpelist": [], "_owner": "", "_reason": "syn-ack", "_reason_ip": "", "_reason_ttl": "64", "_servicefp": "", "_tunnel": "", "_service_extras": {"scripts": []}}}, {"__NmapService__": {"_portid": 3306, "_protocol": "tcp", "_state": {"state": "open", "reason": "syn-ack", "reason_ttl": "64"}, "_service": {"name": "mysql", "method": "table", "conf": "3", "cpelist": []}, "_cpelist": [], "_owner": "", "_reason": "syn-ack", "_reason_ip": "", "_reason_ttl": "64", "_servicefp": "", "_tunnel": "", "_service_extras": {"scripts": []}}}], "_extras": {"extraports": {"state": {"state": "closed", "count": "995"}, "count": {"state": "closed", "count": "995"}, "reasons": [{"reason": "resets", "count": "995"}]}, "times": {"srtt": "7", "rttvar": "0", "to": "100000"}}, "_osfingerprinted": false, "os": {"__NmapOSFingerprint__": {"_NmapOSFingerprint__osmatches": [], "_NmapOSFingerprint__ports_used": [], "_NmapOSFingerprint__fingerprints": []}}, "_ipv4_addr": "127.0.0.1", "_ipv6_addr": null, "_mac_addr": null, "_vendor": null, "_main_address": "127.0.0.1", "_address": [{"addr": "127.0.0.1", "addrtype": "ipv4"}]}}, {"__NmapHost__": {"_starttime": "1361737906", "_endtime": "1361738040", "_hostnames": ["scanme.nmap.org", "scanme.nmap.org"], "_status": {"state": "up", "reason": "echo-reply"}, "_services": [{"__NmapService__": {"_portid": 22, "_protocol": "tcp", "_state": {"state": "open", "reason": "syn-ack", "reason_ttl": "53"}, "_service": {"name": "ssh", "method": "table", "conf": "3", "cpelist": []}, "_cpelist": [], "_owner": "", "_reason": "syn-ack", "_reason_ip": "", "_reason_ttl": "53", "_servicefp": "", "_tunnel": "", "_service_extras": {"scripts": []}}}, {"__NmapService__": {"_portid": 25, "_protocol": "tcp", "_state": {"state": "filtered", "reason": "admin-prohibited", "reason_ttl": "253", "reason_ip": "109.133.192.1"}, "_service": {"name": "smtp", "method": "table", "conf": "3", "cpelist": []}, "_cpelist": [], "_owner": "", "_reason": "admin-prohibited", "_reason_ip": "109.133.192.1", "_reason_ttl": "253", "_servicefp": "", "_tunnel": "", "_service_extras": {"scripts": []}}}, {"__NmapService__": {"_portid": 80, "_protocol": "tcp", "_state": {"state": "open", "reason": "syn-ack", "reason_ttl": "51"}, "_service": {"name": "http", "method": "table", "conf": "3", "cpelist": []}, "_cpelist": [], "_owner": "", "_reason": "syn-ack", "_reason_ip": "", "_reason_ttl": "51", "_servicefp": "", "_tunnel": "", "_service_extras": {"scripts": []}}}, {"__NmapService__": {"_portid": 9929, "_protocol": "tcp", "_state": {"state": "open", "reason": "syn-ack", "reason_ttl": "53"}, "_service": {"name": "nping-echo", "method": "table", "conf": "3", "cpelist": []}, "_cpelist": [], "_owner": "", "_reason": "syn-ack", "_reason_ip": "", "_reason_ttl": "53", "_servicefp": "", "_tunnel": "", "_service_extras": {"scripts": []}}}], "_extras": {"extraports": {"state": {"state": "closed", "count": "996"}, "count": {"state": "closed", "count": "996"}, "reasons": [{"reason": "resets", "count": "996"}]}, "times": {"srtt": "177425", "rttvar": "1981", "to": "185349"}}, "_osfingerprinted": false, "os": {"__NmapOSFingerprint__": {"_NmapOSFingerprint__osmatches": [], "_NmapOSFingerprint__ports_used": [], "_NmapOSFingerprint__fingerprints": []}}, "_ipv4_addr": "74.207.244.221", "_ipv6_addr": null, "_mac_addr": null, "_vendor": null, "_main_address": "74.207.244.221", "_address": [{"addr": "74.207.244.221", "addrtype": "ipv4"}]}}], "_runstats": {"finished": {"time": "1361738040", "timestr": "Sun Feb 24 21:34:00 2013", "elapsed": "134.36", "summary": "Nmap done at Sun Feb 24 21:34:00 2013; 2 IP addresses (2 hosts up) scanned in 134.36 seconds", "exit": "success"}, "hosts": {"up": "2", "down": "0", "total": "2"}}}} diff --git a/libnmap/test/files/defused_et_included.xml b/libnmap/test/files/defused_et_included.xml index 82e04b6..59472a0 100644 --- a/libnmap/test/files/defused_et_included.xml +++ b/libnmap/test/files/defused_et_included.xml @@ -3,4 +3,4 @@ text texttail - \ No newline at end of file + diff --git a/libnmap/test/files/defused_et_local_includer.xml b/libnmap/test/files/defused_et_local_includer.xml index d063ae6..6e90be8 100644 --- a/libnmap/test/files/defused_et_local_includer.xml +++ b/libnmap/test/files/defused_et_local_includer.xml @@ -2,4 +2,4 @@ ]> - \ No newline at end of file + diff --git a/libnmap/test/files/diff_1_host_ping_mac_changed.xml b/libnmap/test/files/diff_1_host_ping_mac_changed.xml index 91d0429..be92c66 100644 --- a/libnmap/test/files/diff_1_host_ping_mac_changed.xml +++ b/libnmap/test/files/diff_1_host_ping_mac_changed.xml @@ -16,4 +16,4 @@ - \ No newline at end of file + diff --git a/libnmap/test/files/dionaea_scan.xml b/libnmap/test/files/dionaea_scan.xml index f891430..14e3b26 100644 --- a/libnmap/test/files/dionaea_scan.xml +++ b/libnmap/test/files/dionaea_scan.xml @@ -342,4 +342,4 @@ - \ No newline at end of file + diff --git a/libnmap/test/files/test_osclass.xml b/libnmap/test/files/test_osclass.xml index bcfd9c6..68850bb 100644 --- a/libnmap/test/files/test_osclass.xml +++ b/libnmap/test/files/test_osclass.xml @@ -43,4 +43,4 @@ - \ No newline at end of file + diff --git a/requirements-dev.txt b/requirements-dev.txt index aaeebb6..2fbfb2f 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -1,6 +1,6 @@ -black==20.8b1 -defusedxml==0.6.0 -isort==5.6.4 +black==20.8b1 +defusedxml==0.6.0 +isort==5.6.4 pre-commit pytest pytest-cov diff --git a/tox.ini b/tox.ini index 45ba18a..f86d9d3 100644 --- a/tox.ini +++ b/tox.ini @@ -4,7 +4,7 @@ envlist = py27, py32, py38, flake8, pycodestyle, formatting, defusedxml, coveral [testenv] deps=pytest pytest-cov -commands=pytest --cov --cov-report term-missing --ignore=libnmap/test/test_backend_plugin_factory.py --ignore=libnmap/test/test_defusedxml.py +commands=pytest --cov --cov-report term-missing --ignore=libnmap/test/test_backend_plugin_factory.py --ignore=libnmap/test/test_defusedxml.py [testenv:defusedxml] deps=pytest @@ -29,7 +29,7 @@ deps = pycodestyle commands = pycodestyle --exclude test,docs,examples,.tox . - + [testenv:formatting] deps = #black==20.8b1 From 2388af3637b58b849c832ef71ff8e931b1ba292c Mon Sep 17 00:00:00 2001 From: savon-noir Date: Thu, 1 Sep 2022 22:05:06 +0200 Subject: [PATCH 57/88] fix(lint): try to fix linting error from psf/black --- .github/workflows/preflight_check.yaml | 1 + .pre-commit-config.yaml | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/preflight_check.yaml b/.github/workflows/preflight_check.yaml index d6c3729..e0e3022 100644 --- a/.github/workflows/preflight_check.yaml +++ b/.github/workflows/preflight_check.yaml @@ -25,6 +25,7 @@ jobs: python -m pip install --upgrade pip pip install black isort flake8 - name: Format checker with psf/black + uses: psf/black@22.8.0 run: black --check -l 79 . - name: Format checker with isort run: isort --check-only -m 3 -l 79 --profile=black . diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index b95e79d..cb54803 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -7,7 +7,7 @@ repos: - id: end-of-file-fixer - id: trailing-whitespace - repo: https://github.com/psf/black - rev: 22.1.0 + rev: 22.8.0 hooks: - id: black args: [--line-length=79] From 1279ccae209fd30bdd3ac156e0922fafd4aaca4b Mon Sep 17 00:00:00 2001 From: savon-noir Date: Thu, 1 Sep 2022 22:15:36 +0200 Subject: [PATCH 58/88] fix(lint): try to fix linting error from psf/black --- .github/workflows/preflight_check.yaml | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/.github/workflows/preflight_check.yaml b/.github/workflows/preflight_check.yaml index e0e3022..1c55e3a 100644 --- a/.github/workflows/preflight_check.yaml +++ b/.github/workflows/preflight_check.yaml @@ -25,8 +25,10 @@ jobs: python -m pip install --upgrade pip pip install black isort flake8 - name: Format checker with psf/black - uses: psf/black@22.8.0 - run: black --check -l 79 . + uses: psf/black@stable + with: + options: "--check -l 79" + version: "28.8.0" - name: Format checker with isort run: isort --check-only -m 3 -l 79 --profile=black . - name: Lint with flake8 From f34abce35c642423934e6992401dda0fae24d9d5 Mon Sep 17 00:00:00 2001 From: savon-noir Date: Thu, 1 Sep 2022 22:18:24 +0200 Subject: [PATCH 59/88] fix(lint): try to fix linting error from psf/black --- .github/workflows/preflight_check.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/preflight_check.yaml b/.github/workflows/preflight_check.yaml index 1c55e3a..0273eb0 100644 --- a/.github/workflows/preflight_check.yaml +++ b/.github/workflows/preflight_check.yaml @@ -28,7 +28,7 @@ jobs: uses: psf/black@stable with: options: "--check -l 79" - version: "28.8.0" + version: "22.8.0" - name: Format checker with isort run: isort --check-only -m 3 -l 79 --profile=black . - name: Lint with flake8 From 20ef8ff06e9da71c70ed8816090a9cc157ffb604 Mon Sep 17 00:00:00 2001 From: savon-noir Date: Thu, 1 Sep 2022 22:22:49 +0200 Subject: [PATCH 60/88] fix(lint): try to fix linting error from psf/black --- .github/workflows/preflight_check.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/preflight_check.yaml b/.github/workflows/preflight_check.yaml index 0273eb0..975ac41 100644 --- a/.github/workflows/preflight_check.yaml +++ b/.github/workflows/preflight_check.yaml @@ -27,7 +27,7 @@ jobs: - name: Format checker with psf/black uses: psf/black@stable with: - options: "--check -l 79" + options: "--check -l 79 --exclude docs/" version: "22.8.0" - name: Format checker with isort run: isort --check-only -m 3 -l 79 --profile=black . From 41a995a264ef694007e991b40a74b574310b1b80 Mon Sep 17 00:00:00 2001 From: savon-noir Date: Thu, 1 Sep 2022 22:27:41 +0200 Subject: [PATCH 61/88] fix(lint): try to fix linting error from coverall --- .github/workflows/preflight_check.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/preflight_check.yaml b/.github/workflows/preflight_check.yaml index 975ac41..ea25bfd 100644 --- a/.github/workflows/preflight_check.yaml +++ b/.github/workflows/preflight_check.yaml @@ -61,7 +61,7 @@ jobs: COVERALLS_FLAG_NAME: ${{ matrix.python-version }} COVERALLS_PARALLEL: true run: | - coveralls + coveralls --service=gihtub coveralls: name: Finish Coveralls needs: test From 6fe33e7460f4e0feab5102eda46b37d766b2f539 Mon Sep 17 00:00:00 2001 From: savon-noir Date: Thu, 1 Sep 2022 22:34:24 +0200 Subject: [PATCH 62/88] fix(lint): try to fix linting error from coveralls --- .github/workflows/preflight_check.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/preflight_check.yaml b/.github/workflows/preflight_check.yaml index ea25bfd..f1643f9 100644 --- a/.github/workflows/preflight_check.yaml +++ b/.github/workflows/preflight_check.yaml @@ -61,7 +61,7 @@ jobs: COVERALLS_FLAG_NAME: ${{ matrix.python-version }} COVERALLS_PARALLEL: true run: | - coveralls --service=gihtub + coveralls --service=github coveralls: name: Finish Coveralls needs: test @@ -71,6 +71,6 @@ jobs: - name: Finished run: | pip3 install --upgrade coveralls - coveralls --finish + coveralls --finish --service=github env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} From 3a4957124901b36bd37cd0c6334d9dd0db2c5f16 Mon Sep 17 00:00:00 2001 From: savon-noir Date: Thu, 1 Sep 2022 23:10:05 +0200 Subject: [PATCH 63/88] fix: updated changelog --- CHANGELOG.md | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8d20aae..d3fd9ad 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,16 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). (or tries to...) +## [v0.7.3] 2022-09-01 + +### Fixed + +- Linting and coveralls issues + +### Security + +- Fix for security issue on arguments injections - [CVE-2022-30284](https://nvd.nist.gov/vuln/detail/CVE-2022-30284) + ## [v0.7.2] 2020-12-16 ### Added From 37092bd825eeccaf3081b15b25f23294a94cf1ac Mon Sep 17 00:00:00 2001 From: savon-noir Date: Thu, 1 Sep 2022 23:32:34 +0200 Subject: [PATCH 64/88] fix: fixed version and documentation for v0.7.3 --- libnmap/objects/report.py | 5 +++++ libnmap/process.py | 24 +++++++++++++++++++----- setup.py | 2 +- 3 files changed, 25 insertions(+), 6 deletions(-) diff --git a/libnmap/objects/report.py b/libnmap/objects/report.py index 8c69eb4..3a18ed6 100644 --- a/libnmap/objects/report.py +++ b/libnmap/objects/report.py @@ -411,6 +411,11 @@ def __ne__(self, other): return rval def __repr__(self): + """ + Returns a string-based representation of the report + + :return: string + """ return "{0}: started at {1} hosts up {2}/{3}".format( self.__class__.__name__, self.started, diff --git a/libnmap/process.py b/libnmap/process.py index d1aba76..d53854f 100644 --- a/libnmap/process.py +++ b/libnmap/process.py @@ -486,8 +486,24 @@ def __build_windows_cmdline(self): @staticmethod def __validate_target(target): - # See https://nmap.org/book/man-target-specification.html for all the - # ways targets can be specified + """ + Check if a provided target is valid. This function was created + in order to address CVE-2022-30284 + + See https://nmap.org/book/man-target-specification.html for all the + ways targets can be specified + + This function verifies the following: + + - matches the user specified target against a list of allowed chars + - check if dashes are used at the start or at the end of target + + FQDN can contain dashes anywhere except at the beginning or end + This check also fixes/prevents CVE-2022-30284, which depends on being + able to pass options such as --script as a target + + :return: False if target contains forbidden characters + """ allowed_characters = frozenset( string.ascii_letters + string.digits + "-.:/% " ) @@ -495,15 +511,13 @@ def __validate_target(target): raise Exception( "Target '{}' contains invalid characters".format(target) ) - # FQDN can contain dashes anywhere except at the beginning or end - # This check also fixes/prevents CVE-2022-30284, which depends on being - # able to pass options such as --script as a target elif target.startswith("-") or target.endswith("-"): raise Exception( "Target '{}' cannot begin or end with a dash ('-')".format( target ) ) + return True @property def command(self): diff --git a/setup.py b/setup.py index 9668371..d6427c6 100644 --- a/setup.py +++ b/setup.py @@ -6,7 +6,7 @@ setup( name="python-libnmap", - version="0.7.2", + version="0.7.3", author="Ronald Bister", author_email="mini.pelle@gmail.com", packages=["libnmap", "libnmap.plugins", "libnmap.objects"], From 16d23c07754d06b1c51e8c4f9db60666b3f93a4c Mon Sep 17 00:00:00 2001 From: dimeko Date: Thu, 4 Apr 2024 13:22:53 +0300 Subject: [PATCH 65/88] Adds trace key --- libnmap/parser.py | 1 + 1 file changed, 1 insertion(+) diff --git a/libnmap/parser.py b/libnmap/parser.py index 21f6a12..1d40e07 100644 --- a/libnmap/parser.py +++ b/libnmap/parser.py @@ -305,6 +305,7 @@ def _parse_xml_host(cls, scanhost_data): "tcpsequence", "ipidsequence", "tcptssequence", + "trace" "times", ] for xh in xelement: From d21dc98019153768642b7ad9bc2c5bfae1753bcc Mon Sep 17 00:00:00 2001 From: dimeko Date: Thu, 4 Apr 2024 13:26:35 +0300 Subject: [PATCH 66/88] Small change --- libnmap/parser.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libnmap/parser.py b/libnmap/parser.py index 1d40e07..b99e6ae 100644 --- a/libnmap/parser.py +++ b/libnmap/parser.py @@ -305,7 +305,7 @@ def _parse_xml_host(cls, scanhost_data): "tcpsequence", "ipidsequence", "tcptssequence", - "trace" + "trace", "times", ] for xh in xelement: From f17139cde1d3c134303d480d5343e2658544eae7 Mon Sep 17 00:00:00 2001 From: dimeko Date: Thu, 4 Apr 2024 14:29:59 +0300 Subject: [PATCH 67/88] Adds trace parsing --- libnmap/parser.py | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/libnmap/parser.py b/libnmap/parser.py index b99e6ae..7907b5b 100644 --- a/libnmap/parser.py +++ b/libnmap/parser.py @@ -327,6 +327,9 @@ def _parse_xml_host(cls, scanhost_data): elif xh.tag == "hostscript": _host_scripts = cls.__parse_host_scripts(xh) _host_extras.update({"hostscript": _host_scripts}) + elif xh.tag == "trace": + _trace = cls.__parse_trace(xh) + _host_extras.update({"trace": _trace}) elif xh.tag in extra_tags: _host_extras[xh.tag] = cls.__format_attributes(xh) # else: @@ -681,6 +684,30 @@ def __parse_runstats(cls, scanrunstats_data): return rdict + @classmethod + def __parse_trace(cls, scantrace_data): + """ + Private method parsing a portion of a nmap scan result. + Receives a XML tag. + + :param scantrace_data: XML tag from a nmap scan + :type scantrace_data: xml.ElementTree.Element or a string + + :return: python dict representing the XML trace tag + """ + + xelement = cls.__format_element(scantrace_data) + + rdict = {} + for xmltag in xelement: + if xmltag.tag in ["port", "proto", "hop"]: + 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): """ From 53072527065fa193c4417f91e08efc4da4b274f1 Mon Sep 17 00:00:00 2001 From: dimeko Date: Thu, 4 Apr 2024 15:26:26 +0300 Subject: [PATCH 68/88] Adds more fields to tarce --- libnmap/parser.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/libnmap/parser.py b/libnmap/parser.py index 7907b5b..dd86884 100644 --- a/libnmap/parser.py +++ b/libnmap/parser.py @@ -675,6 +675,7 @@ def __parse_runstats(cls, scanrunstats_data): xelement = cls.__format_element(scanrunstats_data) rdict = {} + rdict["port"] for xmltag in xelement: if xmltag.tag in ["finished", "hosts"]: rdict[xmltag.tag] = cls.__format_attributes(xmltag) @@ -697,11 +698,15 @@ def __parse_trace(cls, scantrace_data): """ xelement = cls.__format_element(scantrace_data) + _trace_attrs = cls.__format_attributes(xelement) rdict = {} + rdict["port"] = _trace_attrs["port"] + rdict["proto"] = _trace_attrs["proto"] + rdict["hops"] = [] for xmltag in xelement: - if xmltag.tag in ["port", "proto", "hop"]: - rdict[xmltag.tag] = cls.__format_attributes(xmltag) + if xmltag.tag in ["hop"]: + rdict["hops"].append(cls.__format_attributes(xmltag)) else: exmsg = "Unexcepted node in : {0}".format(xmltag.tag) raise NmapParserException(exmsg) From 3a4fa75da4688f5beef06a6bc6f59f58c5d1537b Mon Sep 17 00:00:00 2001 From: dimeko Date: Thu, 4 Apr 2024 15:36:44 +0300 Subject: [PATCH 69/88] WIP: fix --- libnmap/parser.py | 1 - 1 file changed, 1 deletion(-) diff --git a/libnmap/parser.py b/libnmap/parser.py index dd86884..718dac6 100644 --- a/libnmap/parser.py +++ b/libnmap/parser.py @@ -675,7 +675,6 @@ def __parse_runstats(cls, scanrunstats_data): xelement = cls.__format_element(scanrunstats_data) rdict = {} - rdict["port"] for xmltag in xelement: if xmltag.tag in ["finished", "hosts"]: rdict[xmltag.tag] = cls.__format_attributes(xmltag) From a4b355b7f5b10ebbc015e0748fedcfb1f6af743b Mon Sep 17 00:00:00 2001 From: dimeko Date: Wed, 10 Apr 2024 21:46:20 +0300 Subject: [PATCH 70/88] Adds conditions --- libnmap/parser.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/libnmap/parser.py b/libnmap/parser.py index 718dac6..4bb9123 100644 --- a/libnmap/parser.py +++ b/libnmap/parser.py @@ -700,8 +700,12 @@ def __parse_trace(cls, scantrace_data): _trace_attrs = cls.__format_attributes(xelement) rdict = {} - rdict["port"] = _trace_attrs["port"] - rdict["proto"] = _trace_attrs["proto"] + if "proto" not in _trace_attrs: + rdict["port"] = _trace_attrs["port"] + + if "port" not in _trace_attrs: + rdict["port"] = _trace_attrs["proto"] + rdict["hops"] = [] for xmltag in xelement: if xmltag.tag in ["hop"]: From b9a42aa8bd5e446bb06460808f91c54e617efcdb Mon Sep 17 00:00:00 2001 From: dimeko Date: Tue, 16 Apr 2024 15:32:12 +0300 Subject: [PATCH 71/88] Fixes proto and port retrieval --- libnmap/parser.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/libnmap/parser.py b/libnmap/parser.py index 4bb9123..52d78b7 100644 --- a/libnmap/parser.py +++ b/libnmap/parser.py @@ -700,11 +700,12 @@ def __parse_trace(cls, scantrace_data): _trace_attrs = cls.__format_attributes(xelement) rdict = {} - if "proto" not in _trace_attrs: - rdict["port"] = _trace_attrs["port"] - if "port" not in _trace_attrs: - rdict["port"] = _trace_attrs["proto"] + if "proto" in _trace_attrs: + rdict["proto"] = _trace_attrs["proto"] + + if "port" in _trace_attrs: + rdict["port"] = _trace_attrs["port"] rdict["hops"] = [] for xmltag in xelement: From d6f484fc8a4bb766eec18013c01feea1ec93c9e7 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 20 Mar 2024 15:50:20 +0000 Subject: [PATCH 72/88] Bump black from 20.8b1 to 24.3.0 Bumps [black](https://github.com/psf/black) from 20.8b1 to 24.3.0. - [Release notes](https://github.com/psf/black/releases) - [Changelog](https://github.com/psf/black/blob/main/CHANGES.md) - [Commits](https://github.com/psf/black/commits/24.3.0) --- updated-dependencies: - dependency-name: black dependency-type: direct:development ... Signed-off-by: dependabot[bot] --- requirements-dev.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements-dev.txt b/requirements-dev.txt index 2fbfb2f..56c9a07 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -1,4 +1,4 @@ -black==20.8b1 +black==24.3.0 defusedxml==0.6.0 isort==5.6.4 pre-commit From ed0619e9bb9648ce99d52c447c5dde8b6f5449d1 Mon Sep 17 00:00:00 2001 From: Alexandre ZANNI <16578570+noraj@users.noreply.github.com> Date: Fri, 7 Jul 2023 14:33:53 +0200 Subject: [PATCH 73/88] fix python 2 compat fix #138 --- setup.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/setup.py b/setup.py index d6427c6..a2ca71f 100644 --- a/setup.py +++ b/setup.py @@ -1,8 +1,13 @@ # -*- coding: utf-8 -*- from distutils.core import setup +import sys -with open("README.rst", encoding="utf-8") as rfile: - long_description = rfile.read() +if sys.version_info >= (3,0): + with open("README.rst", encoding="utf-8") as rfile: + long_description = rfile.read() +else: # encoding not compatible with python2 + with open("README.rst") as rfile: + long_description = rfile.read() setup( name="python-libnmap", From 6c0ee1ab3c5962cc124f8c4c7d8f51f0591c989f Mon Sep 17 00:00:00 2001 From: Martin Schobert Date: Tue, 12 Dec 2023 17:02:37 +0100 Subject: [PATCH 74/88] Update parser.py documentation with a warning/clarification. --- libnmap/parser.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/libnmap/parser.py b/libnmap/parser.py index 52d78b7..67ff616 100644 --- a/libnmap/parser.py +++ b/libnmap/parser.py @@ -34,7 +34,9 @@ def parse(cls, nmap_data=None, data_type="XML", incomplete=False): :param incomplete: enable you to parse interrupted nmap scans \ and/or incomplete nmap xml blocks by adding a at \ - the end of the scan. + the end of the scan. Be aware that this flag does not work for \ + already valid XML files, because adding an XML tag will \ + invalidate the XML. :type incomplete: boolean As of today, only XML parsing is supported. From e11ff0649bbfa7358fc7ee86c2bf8da841b604c3 Mon Sep 17 00:00:00 2001 From: Ronald Bister Date: Sun, 19 Jan 2025 22:22:25 +0100 Subject: [PATCH 75/88] fix: added versions for python envs --- .github/workflows/preflight_check.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/preflight_check.yaml b/.github/workflows/preflight_check.yaml index f1643f9..284998e 100644 --- a/.github/workflows/preflight_check.yaml +++ b/.github/workflows/preflight_check.yaml @@ -13,7 +13,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - python-version: [3.6, 3.7, 3.8] + python-version: [3.6.15, 3.7.17, 3.8.18] steps: - uses: actions/checkout@v2 - name: Set up Python ${{ matrix.python-version }} @@ -37,7 +37,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - python-version: [2.7, 3.5, 3.6, 3.7, 3.8] + python-version: [3.5.10, 3.6.15, 3.7.17, 3.8.18] steps: - uses: actions/checkout@v2 - name: Setup Python ${{ matrix.python-version }} From 61fb48a6d530b9fcf1afe5e9a601f2ae463f064b Mon Sep 17 00:00:00 2001 From: Ronald Bister Date: Sun, 19 Jan 2025 22:33:12 +0100 Subject: [PATCH 76/88] fix: updated lib version for checks --- .github/workflows/preflight_check.yaml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/preflight_check.yaml b/.github/workflows/preflight_check.yaml index 284998e..1487df0 100644 --- a/.github/workflows/preflight_check.yaml +++ b/.github/workflows/preflight_check.yaml @@ -15,9 +15,9 @@ jobs: matrix: python-version: [3.6.15, 3.7.17, 3.8.18] steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v4 - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v2 + uses: actions/setup-python@v5 with: python-version: ${{ matrix.python-version }} - name: Install dependencies @@ -39,9 +39,9 @@ jobs: matrix: python-version: [3.5.10, 3.6.15, 3.7.17, 3.8.18] steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v4 - name: Setup Python ${{ matrix.python-version }} - uses: actions/setup-python@v2 + uses: actions/setup-python@v5 with: python-version: ${{ matrix.python-version }} - name: Setup Environment From 8bca24a76d0df82ead202c9b274fcd05106335cc Mon Sep 17 00:00:00 2001 From: Ronald Bister Date: Sun, 19 Jan 2025 22:53:19 +0100 Subject: [PATCH 77/88] fix: fixed pyblack codefmt --- .github/workflows/preflight_check.yaml | 2 +- libnmap/objects/os.py | 1 - libnmap/process.py | 2 -- setup.py | 4 ++-- 4 files changed, 3 insertions(+), 6 deletions(-) diff --git a/.github/workflows/preflight_check.yaml b/.github/workflows/preflight_check.yaml index 1487df0..a7f9314 100644 --- a/.github/workflows/preflight_check.yaml +++ b/.github/workflows/preflight_check.yaml @@ -28,7 +28,7 @@ jobs: uses: psf/black@stable with: options: "--check -l 79 --exclude docs/" - version: "22.8.0" + version: "24.10.0" - name: Format checker with isort run: isort --check-only -m 3 -l 79 --profile=black . - name: Lint with flake8 diff --git a/libnmap/objects/os.py b/libnmap/objects/os.py index 30d3f3b..2d2e109 100644 --- a/libnmap/objects/os.py +++ b/libnmap/objects/os.py @@ -89,7 +89,6 @@ def __init__(self, osmatch_dict): 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 diff --git a/libnmap/process.py b/libnmap/process.py index d53854f..a2825cc 100644 --- a/libnmap/process.py +++ b/libnmap/process.py @@ -20,7 +20,6 @@ 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 instantiated. @@ -46,7 +45,6 @@ def __init__(self, name, starttime=0, extrainfo=""): class NmapProcess(Thread): - """ NmapProcess is a class which wraps around the nmap executable. diff --git a/setup.py b/setup.py index a2ca71f..5fa816c 100644 --- a/setup.py +++ b/setup.py @@ -2,10 +2,10 @@ from distutils.core import setup import sys -if sys.version_info >= (3,0): +if sys.version_info >= (3, 0): with open("README.rst", encoding="utf-8") as rfile: long_description = rfile.read() -else: # encoding not compatible with python2 +else: # encoding not compatible with python2 with open("README.rst") as rfile: long_description = rfile.read() From 23528164cff23f7ec5083c22f23907fdaf5142cd Mon Sep 17 00:00:00 2001 From: Ronald Bister Date: Sun, 19 Jan 2025 22:54:28 +0100 Subject: [PATCH 78/88] fix: isort in setup.py --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 5fa816c..eb5c0f5 100644 --- a/setup.py +++ b/setup.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- -from distutils.core import setup import sys +from distutils.core import setup if sys.version_info >= (3, 0): with open("README.rst", encoding="utf-8") as rfile: From 2e8ed2dd0617846b6c2191d63e91531929d91703 Mon Sep 17 00:00:00 2001 From: Ronald Bister Date: Sun, 19 Jan 2025 22:59:12 +0100 Subject: [PATCH 79/88] fix: requirements file --- requirements-dev.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements-dev.txt b/requirements-dev.txt index 56c9a07..cb81983 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -1,4 +1,4 @@ -black==24.3.0 +black==24.10.0 defusedxml==0.6.0 isort==5.6.4 pre-commit From ff63931bd3941719dfb7c5a39bb2819d32682524 Mon Sep 17 00:00:00 2001 From: Ronald Bister Date: Sun, 19 Jan 2025 23:53:01 +0100 Subject: [PATCH 80/88] fix: gh actions fix bllint issue --- .github/workflows/preflight_check.yaml | 12 ++++++------ requirements-dev.txt | 2 +- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/.github/workflows/preflight_check.yaml b/.github/workflows/preflight_check.yaml index a7f9314..5cf7b87 100644 --- a/.github/workflows/preflight_check.yaml +++ b/.github/workflows/preflight_check.yaml @@ -10,10 +10,10 @@ on: jobs: lint: - runs-on: ubuntu-latest + runs-on: ubuntu-22.04 strategy: matrix: - python-version: [3.6.15, 3.7.17, 3.8.18] + python-version: ["3.8", "3.9", "3.10"] steps: - uses: actions/checkout@v4 - name: Set up Python ${{ matrix.python-version }} @@ -28,16 +28,16 @@ jobs: uses: psf/black@stable with: options: "--check -l 79 --exclude docs/" - version: "24.10.0" + version: "22.8.0" - name: Format checker with isort run: isort --check-only -m 3 -l 79 --profile=black . - name: Lint with flake8 run: flake8 --exclude test,docs,examples . test: - runs-on: ubuntu-latest + runs-on: ubuntu-22.04 strategy: matrix: - python-version: [3.5.10, 3.6.15, 3.7.17, 3.8.18] + python-version: ["3.7", "3.8", "3.9", "3.10"] steps: - uses: actions/checkout@v4 - name: Setup Python ${{ matrix.python-version }} @@ -65,7 +65,7 @@ jobs: coveralls: name: Finish Coveralls needs: test - runs-on: ubuntu-latest + runs-on: ubuntu-22.04 container: python:3-slim steps: - name: Finished diff --git a/requirements-dev.txt b/requirements-dev.txt index cb81983..d63813a 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -1,4 +1,4 @@ -black==24.10.0 +black==22.8.0 defusedxml==0.6.0 isort==5.6.4 pre-commit From 6c69c191b4ed7b3161120cd2d3ec14980289524c Mon Sep 17 00:00:00 2001 From: Ronald Date: Mon, 6 Apr 2015 23:35:04 +0200 Subject: [PATCH 81/88] temporary fix for issue40 --- libnmap/objects/__init__.py | 4 ++-- libnmap/objects/host.py | 10 ++++---- libnmap/objects/service.py | 48 +++++++++++++++++++++++++++++++++++++ libnmap/parser.py | 20 ++++++++-------- 4 files changed, 66 insertions(+), 16 deletions(-) diff --git a/libnmap/objects/__init__.py b/libnmap/objects/__init__.py index ddf3699..9cafae9 100644 --- a/libnmap/objects/__init__.py +++ b/libnmap/objects/__init__.py @@ -2,6 +2,6 @@ from libnmap.objects.host import NmapHost from libnmap.objects.report import NmapReport -from libnmap.objects.service import NmapService +from libnmap.objects.service import NmapService, NmapExtraPort -__all__ = ["NmapReport", "NmapHost", "NmapService"] +__all__ = ['NmapReport', 'NmapHost', 'NmapService', 'NmapExtraPort'] diff --git a/libnmap/objects/host.py b/libnmap/objects/host.py index 9425e2e..99be06f 100644 --- a/libnmap/objects/host.py +++ b/libnmap/objects/host.py @@ -38,6 +38,7 @@ def __init__( 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._extraports = self._extras.get('extraports', None) self._osfingerprinted = False self.os = None if "os" in self._extras: @@ -469,12 +470,13 @@ def extraports_state(self): :return: dict with keys 'state' and 'count' or None """ - _xtrports = self._extras.get("extraports", None) + rval = None + _xports = self._extras.get('extraports', None) - if _xtrports is None: - return None + if _xports is not None: + rval = {'state': _xtrports['state'], 'count': _xtrports['count']} - return {"state": _xtrports["state"], "count": _xtrports["count"]} + return rval @property def extraports_reasons(self): diff --git a/libnmap/objects/service.py b/libnmap/objects/service.py index e254a66..a21b1af 100644 --- a/libnmap/objects/service.py +++ b/libnmap/objects/service.py @@ -366,3 +366,51 @@ def diff(self, other): :return: NmapDiff object """ return NmapDiff(self, other) + + +class NmapExtraPort(object): + """ + NmapExtraPort is an object which documents unlisted ports/services + which are possibly closed, filtered, ignored,... + """ + def __init__(self, xdict): + """ + Constructor + :param xdict: python dict containing the following structure: + { + 'state': , + 'count': , + 'reasons': [{'reason': , 'count' }] + } + """ + self._count = xdict.get('count', 0) + self._state = xdict.get('state', 'unknown') + self._reasons = xdict.get('reasons', []) + + @property + def count(self): + """ + Accessor for the number of extraports + + :return: int + """ + return int(self._count) + + @property + def state(self): + """ + Accessor for the state of extraports listed + + :return: string + """ + return self._state + + @property + def reason(self): + """ + Return the first reason available for the extraport listed. + + :return: dict, empty if no extraports reason available + """ + + return self.reasons[0] if len(self.reasons) else {} diff --git a/libnmap/parser.py b/libnmap/parser.py index 67ff616..1c5be59 100644 --- a/libnmap/parser.py +++ b/libnmap/parser.py @@ -8,10 +8,8 @@ import xml.etree.cElementTree as ET except ImportError: import xml.etree.ElementTree as ET - from xml.etree.ElementTree import iselement as et_iselement - -from libnmap.objects import NmapHost, NmapReport, NmapService +from libnmap.objects import NmapHost, NmapService, NmapReport, NmapExtraPort class NmapParser(object): @@ -385,17 +383,18 @@ def _parse_xml_ports(cls, scanports_data): xelement = cls.__format_element(scanports_data) - rdict = {"ports": [], "extraports": None} + rdict = {'ports': [], 'extraports': []} 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)) + rdict['extraports'].append(extraports) + # DEBUG REMOVE ME + else: + print "struct port unknown attr: %s value: %s" % + (h.tag, h.get(h.tag)) return rdict @classmethod @@ -485,8 +484,9 @@ def __parse_extraports(cls, extraports_data): for xelt in xelement: if xelt.tag == "extrareasons": extrareasons_dict = cls.__format_attributes(xelt) - rdict["reasons"].append(extrareasons_dict) - return rdict + rdict['reasons'].append(extrareasons_dict) + robj = NmapExtraPort(rdict) + return robj @classmethod def __parse_script_table(cls, script_table): From 3b4cde5e76a86ceab894b0aa09517c6d0e9adb67 Mon Sep 17 00:00:00 2001 From: Ronald Bister Date: Mon, 27 Jan 2025 22:53:34 +0100 Subject: [PATCH 82/88] fix(extraports): added support for extra ports, fixes #40 --- libnmap/objects/host.py | 30 +-- libnmap/objects/service.py | 8 +- libnmap/parser.py | 10 +- libnmap/test/files/extra_ports.xml | 350 +++++++++++++++++++++++++++++ libnmap/test/test_extraports.py | 36 +++ 5 files changed, 396 insertions(+), 38 deletions(-) create mode 100644 libnmap/test/files/extra_ports.xml create mode 100644 libnmap/test/test_extraports.py diff --git a/libnmap/objects/host.py b/libnmap/objects/host.py index 99be06f..1058a8f 100644 --- a/libnmap/objects/host.py +++ b/libnmap/objects/host.py @@ -463,35 +463,13 @@ def id(self): return self.address @property - def extraports_state(self): + def extraports(self): """ - dictionnary containing state and amount of extra ports scanned - for which a common state, usually, closed was discovered. + Returns a list of NmapExtraPort objects - :return: dict with keys 'state' and 'count' or None + :return: list of NmapExtraPort objects """ - rval = None - _xports = self._extras.get('extraports', None) - - if _xports is not None: - rval = {'state': _xtrports['state'], 'count': _xtrports['count']} - - return rval - - @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", {}) - - if r is None: - return None - - return r.get("reasons", None) + return self._extraports def get_dict(self): """ diff --git a/libnmap/objects/service.py b/libnmap/objects/service.py index a21b1af..ee5ea62 100644 --- a/libnmap/objects/service.py +++ b/libnmap/objects/service.py @@ -388,7 +388,7 @@ def __init__(self, xdict): self._reasons = xdict.get('reasons', []) @property - def count(self): + def extra_count(self): """ Accessor for the number of extraports @@ -397,7 +397,7 @@ def count(self): return int(self._count) @property - def state(self): + def extra_state(self): """ Accessor for the state of extraports listed @@ -406,11 +406,11 @@ def state(self): return self._state @property - def reason(self): + def extra_reasons(self): """ Return the first reason available for the extraport listed. :return: dict, empty if no extraports reason available """ - return self.reasons[0] if len(self.reasons) else {} + return self._reasons diff --git a/libnmap/parser.py b/libnmap/parser.py index 1c5be59..b714731 100644 --- a/libnmap/parser.py +++ b/libnmap/parser.py @@ -391,10 +391,6 @@ def _parse_xml_ports(cls, scanports_data): elif xservice.tag == "extraports": extraports = cls.__parse_extraports(xservice) rdict['extraports'].append(extraports) - # DEBUG REMOVE ME - else: - print "struct port unknown attr: %s value: %s" % - (h.tag, h.get(h.tag)) return rdict @classmethod @@ -477,10 +473,8 @@ def __parse_extraports(cls, extraports_data): 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 + rdict["state"] = extraports_dict.get("state", None) + rdict["count"] = extraports_dict.get("count", None) for xelt in xelement: if xelt.tag == "extrareasons": extrareasons_dict = cls.__format_attributes(xelt) diff --git a/libnmap/test/files/extra_ports.xml b/libnmap/test/files/extra_ports.xml new file mode 100644 index 0000000..26b650a --- /dev/null +++ b/libnmap/test/files/extra_ports.xml @@ -0,0 +1,350 @@ + + + + + + + + + + + + +cpe:/a:mysql:mysql:5.7.16 + + +cpe:/a:memcached:memcached:1.4.25 + + + + + + + + diff --git a/libnmap/test/test_extraports.py b/libnmap/test/test_extraports.py new file mode 100644 index 0000000..08552f1 --- /dev/null +++ b/libnmap/test/test_extraports.py @@ -0,0 +1,36 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +import os +import unittest + +from libnmap.parser import NmapParser, NmapParserException + + +class TestExtraPorts(unittest.TestCase): + def setUp(self): + fdir = os.path.dirname(os.path.realpath(__file__)) + _extrareason = [{'reason': 'filtered', 'count': '3'}, {'reason': 'resets', 'count': '7'}] + self.flist = [ + {"path": "%s/%s" % (fdir, "files/extra_ports.xml"), "extrareason": _extrareason} + ] + + def test_extraports(self): + for fentry in self.flist: + rep1 = NmapParser.parse_fromfile(fentry["path"]) + ep_list = rep1.hosts[0].extraports + self.assertEqual(len(ep_list), 2) + self.assertEqual(ep_list[0].extra_count, 65509) + self.assertEqual(ep_list[0].extra_state, 'closed') + self.assertEqual(len(ep_list[0].extra_reasons), 1) + self.assertEqual(ep_list[1].extra_count, 10) + self.assertEqual(len(ep_list[1].extra_reasons), 2) + self.assertEqual(ep_list[1].extra_reasons, fentry["extrareason"]) + + +if __name__ == "__main__": + test_suite = [ + "test_extraports", + ] + suite = unittest.TestSuite(map(TestExtraPorts, test_suite)) + test_result = unittest.TextTestRunner(verbosity=2).run(suite) From 24d6013c304973d5dcd29ff48ccecc7a2bf717e0 Mon Sep 17 00:00:00 2001 From: Ronald Bister Date: Mon, 27 Jan 2025 23:07:06 +0100 Subject: [PATCH 83/88] fix: fixed pylint issues --- .pre-commit-config.yaml | 1 + libnmap/objects/__init__.py | 4 ++-- libnmap/objects/host.py | 2 +- libnmap/objects/service.py | 37 +++++++++++++++++---------------- libnmap/parser.py | 10 +++++---- libnmap/test/test_extraports.py | 12 ++++++++--- 6 files changed, 38 insertions(+), 28 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index cb54803..3e900c4 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -21,3 +21,4 @@ repos: rev: 3.8.4 hooks: - id: flake8 + exclude: ^libnmap/(test/|docs/|examples/) diff --git a/libnmap/objects/__init__.py b/libnmap/objects/__init__.py index 9cafae9..889e1d3 100644 --- a/libnmap/objects/__init__.py +++ b/libnmap/objects/__init__.py @@ -2,6 +2,6 @@ from libnmap.objects.host import NmapHost from libnmap.objects.report import NmapReport -from libnmap.objects.service import NmapService, NmapExtraPort +from libnmap.objects.service import NmapExtraPort, NmapService -__all__ = ['NmapReport', 'NmapHost', 'NmapService', 'NmapExtraPort'] +__all__ = ["NmapReport", "NmapHost", "NmapService", "NmapExtraPort"] diff --git a/libnmap/objects/host.py b/libnmap/objects/host.py index 1058a8f..95734ea 100644 --- a/libnmap/objects/host.py +++ b/libnmap/objects/host.py @@ -38,7 +38,7 @@ def __init__( 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._extraports = self._extras.get('extraports', None) + self._extraports = self._extras.get("extraports", None) self._osfingerprinted = False self.os = None if "os" in self._extras: diff --git a/libnmap/objects/service.py b/libnmap/objects/service.py index ee5ea62..30c66f7 100644 --- a/libnmap/objects/service.py +++ b/libnmap/objects/service.py @@ -370,47 +370,48 @@ def diff(self, other): class NmapExtraPort(object): """ - NmapExtraPort is an object which documents unlisted ports/services - which are possibly closed, filtered, ignored,... + NmapExtraPort is an object which documents unlisted ports/services + which are possibly closed, filtered, ignored,... """ + def __init__(self, xdict): """ - Constructor - :param xdict: python dict containing the following structure: - { - 'state': , - 'count': , - 'reasons': [{'reason': , 'count' }] - } + Constructor + :param xdict: python dict containing the following structure: + { + 'state': , + 'count': , + 'reasons': [{'reason': , 'count' }] + } """ - self._count = xdict.get('count', 0) - self._state = xdict.get('state', 'unknown') - self._reasons = xdict.get('reasons', []) + self._count = xdict.get("count", 0) + self._state = xdict.get("state", "unknown") + self._reasons = xdict.get("reasons", []) @property def extra_count(self): """ - Accessor for the number of extraports + Accessor for the number of extraports - :return: int + :return: int """ return int(self._count) @property def extra_state(self): """ - Accessor for the state of extraports listed + Accessor for the state of extraports listed - :return: string + :return: string """ return self._state @property def extra_reasons(self): """ - Return the first reason available for the extraport listed. + Return the first reason available for the extraport listed. - :return: dict, empty if no extraports reason available + :return: dict, empty if no extraports reason available """ return self._reasons diff --git a/libnmap/parser.py b/libnmap/parser.py index b714731..9b12e12 100644 --- a/libnmap/parser.py +++ b/libnmap/parser.py @@ -8,8 +8,10 @@ import xml.etree.cElementTree as ET except ImportError: import xml.etree.ElementTree as ET + from xml.etree.ElementTree import iselement as et_iselement -from libnmap.objects import NmapHost, NmapService, NmapReport, NmapExtraPort + +from libnmap.objects import NmapExtraPort, NmapHost, NmapReport, NmapService class NmapParser(object): @@ -383,14 +385,14 @@ def _parse_xml_ports(cls, scanports_data): xelement = cls.__format_element(scanports_data) - rdict = {'ports': [], 'extraports': []} + rdict = {"ports": [], "extraports": []} 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'].append(extraports) + rdict["extraports"].append(extraports) return rdict @classmethod @@ -478,7 +480,7 @@ def __parse_extraports(cls, extraports_data): for xelt in xelement: if xelt.tag == "extrareasons": extrareasons_dict = cls.__format_attributes(xelt) - rdict['reasons'].append(extrareasons_dict) + rdict["reasons"].append(extrareasons_dict) robj = NmapExtraPort(rdict) return robj diff --git a/libnmap/test/test_extraports.py b/libnmap/test/test_extraports.py index 08552f1..c40308b 100644 --- a/libnmap/test/test_extraports.py +++ b/libnmap/test/test_extraports.py @@ -10,9 +10,15 @@ class TestExtraPorts(unittest.TestCase): def setUp(self): fdir = os.path.dirname(os.path.realpath(__file__)) - _extrareason = [{'reason': 'filtered', 'count': '3'}, {'reason': 'resets', 'count': '7'}] + _extrareason = [ + {"reason": "filtered", "count": "3"}, + {"reason": "resets", "count": "7"}, + ] self.flist = [ - {"path": "%s/%s" % (fdir, "files/extra_ports.xml"), "extrareason": _extrareason} + { + "path": "%s/%s" % (fdir, "files/extra_ports.xml"), + "extrareason": _extrareason, + } ] def test_extraports(self): @@ -21,7 +27,7 @@ def test_extraports(self): ep_list = rep1.hosts[0].extraports self.assertEqual(len(ep_list), 2) self.assertEqual(ep_list[0].extra_count, 65509) - self.assertEqual(ep_list[0].extra_state, 'closed') + self.assertEqual(ep_list[0].extra_state, "closed") self.assertEqual(len(ep_list[0].extra_reasons), 1) self.assertEqual(ep_list[1].extra_count, 10) self.assertEqual(len(ep_list[1].extra_reasons), 2) From 1185c32a53e6afbae0b0152121321495f35647e9 Mon Sep 17 00:00:00 2001 From: Ronald Bister Date: Mon, 27 Jan 2025 23:11:12 +0100 Subject: [PATCH 84/88] fix: upgraded pyblack for cve CVE-2024-21503 --- .github/workflows/preflight_check.yaml | 4 ++-- .pre-commit-config.yaml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/preflight_check.yaml b/.github/workflows/preflight_check.yaml index 5cf7b87..c9f52e3 100644 --- a/.github/workflows/preflight_check.yaml +++ b/.github/workflows/preflight_check.yaml @@ -28,7 +28,7 @@ jobs: uses: psf/black@stable with: options: "--check -l 79 --exclude docs/" - version: "22.8.0" + version: "24.3.0" - name: Format checker with isort run: isort --check-only -m 3 -l 79 --profile=black . - name: Lint with flake8 @@ -37,7 +37,7 @@ jobs: runs-on: ubuntu-22.04 strategy: matrix: - python-version: ["3.7", "3.8", "3.9", "3.10"] + python-version: ["3.8", "3.9", "3.10"] steps: - uses: actions/checkout@v4 - name: Setup Python ${{ matrix.python-version }} diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 3e900c4..339924e 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -7,7 +7,7 @@ repos: - id: end-of-file-fixer - id: trailing-whitespace - repo: https://github.com/psf/black - rev: 22.8.0 + rev: 24.3.0 hooks: - id: black args: [--line-length=79] From 00193b3c42563b3b447e86300777485618b80458 Mon Sep 17 00:00:00 2001 From: Ronald Bister Date: Tue, 28 Jan 2025 00:44:55 +0100 Subject: [PATCH 85/88] fix: simplified the extraports structs --- .pre-commit-config.yaml | 54 +++++++++++++++++++-------------- libnmap/objects/__init__.py | 4 +-- libnmap/objects/host.py | 5 +-- libnmap/objects/service.py | 49 ------------------------------ libnmap/parser.py | 11 +++---- libnmap/test/files/2_hosts.json | 2 +- libnmap/test/test_extraports.py | 18 ++++++----- libnmap/test/test_host.py | 19 ------------ 8 files changed, 53 insertions(+), 109 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 339924e..ab2e020 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,24 +1,34 @@ exclude: ^(test/|.tox/|docs) repos: -- repo: https://github.com/pre-commit/pre-commit-hooks - rev: v2.3.0 - hooks: - - id: check-yaml - - id: end-of-file-fixer - - id: trailing-whitespace -- repo: https://github.com/psf/black - rev: 24.3.0 - hooks: - - id: black - args: [--line-length=79] - files: ^libnmap -- repo: https://github.com/pre-commit/mirrors-isort - rev: v5.6.4 - hooks: - - id: isort - args: [--multi-line=3, --line-length=79, --profile=black] -- repo: https://gitlab.com/pycqa/flake8 - rev: 3.8.4 - hooks: - - id: flake8 - exclude: ^libnmap/(test/|docs/|examples/) +- repo: https://github.com/pre-commit/pre-commit-hooks + rev: v2.3.0 + hooks: + - id: check-yaml + - id: end-of-file-fixer + - id: trailing-whitespace +- repo: https://github.com/psf/black + rev: 24.3.0 + hooks: + - id: black + args: [--line-length=79] + files: ^libnmap +- repo: https://github.com/pre-commit/mirrors-isort + rev: v5.6.4 + hooks: + - id: isort + args: [--multi-line=3, --line-length=79, --profile=black] +- repo: https://gitlab.com/pycqa/flake8 + rev: 3.8.4 + hooks: + - id: flake8 + exclude: ^libnmap/(test/|docs/|examples/) +- repo: local + hooks: + - id: pytest-check + name: pytest-check + stages: [pre-commit] + types: [python] + entry: pytest --cov=libnmap/ --ignore=libnmap/test/test_backend_plugin_factory.py + language: system + pass_filenames: false + always_run: true diff --git a/libnmap/objects/__init__.py b/libnmap/objects/__init__.py index 889e1d3..ddf3699 100644 --- a/libnmap/objects/__init__.py +++ b/libnmap/objects/__init__.py @@ -2,6 +2,6 @@ from libnmap.objects.host import NmapHost from libnmap.objects.report import NmapReport -from libnmap.objects.service import NmapExtraPort, NmapService +from libnmap.objects.service import NmapService -__all__ = ["NmapReport", "NmapHost", "NmapService", "NmapExtraPort"] +__all__ = ["NmapReport", "NmapHost", "NmapService"] diff --git a/libnmap/objects/host.py b/libnmap/objects/host.py index 95734ea..dba1653 100644 --- a/libnmap/objects/host.py +++ b/libnmap/objects/host.py @@ -465,9 +465,10 @@ def id(self): @property def extraports(self): """ - Returns a list of NmapExtraPort objects + Returns a list of extraport dict with + with struct { count: "123", state: "filtered, extrareasons: [{}] } - :return: list of NmapExtraPort objects + :return: list of extraport dict """ return self._extraports diff --git a/libnmap/objects/service.py b/libnmap/objects/service.py index 30c66f7..e254a66 100644 --- a/libnmap/objects/service.py +++ b/libnmap/objects/service.py @@ -366,52 +366,3 @@ def diff(self, other): :return: NmapDiff object """ return NmapDiff(self, other) - - -class NmapExtraPort(object): - """ - NmapExtraPort is an object which documents unlisted ports/services - which are possibly closed, filtered, ignored,... - """ - - def __init__(self, xdict): - """ - Constructor - :param xdict: python dict containing the following structure: - { - 'state': , - 'count': , - 'reasons': [{'reason': , 'count' }] - } - """ - self._count = xdict.get("count", 0) - self._state = xdict.get("state", "unknown") - self._reasons = xdict.get("reasons", []) - - @property - def extra_count(self): - """ - Accessor for the number of extraports - - :return: int - """ - return int(self._count) - - @property - def extra_state(self): - """ - Accessor for the state of extraports listed - - :return: string - """ - return self._state - - @property - def extra_reasons(self): - """ - Return the first reason available for the extraport listed. - - :return: dict, empty if no extraports reason available - """ - - return self._reasons diff --git a/libnmap/parser.py b/libnmap/parser.py index 9b12e12..e657d1c 100644 --- a/libnmap/parser.py +++ b/libnmap/parser.py @@ -11,7 +11,7 @@ from xml.etree.ElementTree import iselement as et_iselement -from libnmap.objects import NmapExtraPort, NmapHost, NmapReport, NmapService +from libnmap.objects import NmapHost, NmapReport, NmapService class NmapParser(object): @@ -469,9 +469,9 @@ def __parse_extraports(cls, extraports_data): :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 + :return: python dict with following keys: state, count, reasons """ - rdict = {"state": "", "count": "", "reasons": []} + rdict = {"state": "", "count": "", "extrareasons": []} xelement = cls.__format_element(extraports_data) extraports_dict = cls.__format_attributes(xelement) @@ -480,9 +480,8 @@ def __parse_extraports(cls, extraports_data): for xelt in xelement: if xelt.tag == "extrareasons": extrareasons_dict = cls.__format_attributes(xelt) - rdict["reasons"].append(extrareasons_dict) - robj = NmapExtraPort(rdict) - return robj + rdict["extrareasons"].append(extrareasons_dict) + return rdict @classmethod def __parse_script_table(cls, script_table): diff --git a/libnmap/test/files/2_hosts.json b/libnmap/test/files/2_hosts.json index d6e1727..985f293 100644 --- a/libnmap/test/files/2_hosts.json +++ b/libnmap/test/files/2_hosts.json @@ -1 +1 @@ -{"__NmapReport__": {"_nmaprun": {"scanner": "nmap", "args": "nmap -sS -vv -oX 2_hosts.xml localhost scanme.nmap.org", "start": "1361737906", "startstr": "Sun Feb 24 21:31:46 2013", "version": "5.51", "xmloutputversion": "1.03"}, "_scaninfo": {"type": "syn", "protocol": "tcp", "numservices": "1000", "services": "1,3-4,6-7,9,13,17,19-26,30,32-33,37,42-43,49,53,70,79-85,88-90,99-100,106,109-111,113,119,125,135,139,143-144,146,161,163,179,199,211-212,222,254-256,259,264,280,301,306,311,340,366,389,406-407,416-417,425,427,443-445,458,464-465,481,497,500,512-515,524,541,543-545,548,554-555,563,587,593,616-617,625,631,636,646,648,666-668,683,687,691,700,705,711,714,720,722,726,749,765,777,783,787,800-801,808,843,873,880,888,898,900-903,911-912,981,987,990,992-993,995,999-1002,1007,1009-1011,1021-1100,1102,1104-1108,1110-1114,1117,1119,1121-1124,1126,1130-1132,1137-1138,1141,1145,1147-1149,1151-1152,1154,1163-1166,1169,1174-1175,1183,1185-1187,1192,1198-1199,1201,1213,1216-1218,1233-1234,1236,1244,1247-1248,1259,1271-1272,1277,1287,1296,1300-1301,1309-1311,1322,1328,1334,1352,1417,1433-1434,1443,1455,1461,1494,1500-1501,1503,1521,1524,1533,1556,1580,1583,1594,1600,1641,1658,1666,1687-1688,1700,1717-1721,1723,1755,1761,1782-1783,1801,1805,1812,1839-1840,1862-1864,1875,1900,1914,1935,1947,1971-1972,1974,1984,1998-2010,2013,2020-2022,2030,2033-2035,2038,2040-2043,2045-2049,2065,2068,2099-2100,2103,2105-2107,2111,2119,2121,2126,2135,2144,2160-2161,2170,2179,2190-2191,2196,2200,2222,2251,2260,2288,2301,2323,2366,2381-2383,2393-2394,2399,2401,2492,2500,2522,2525,2557,2601-2602,2604-2605,2607-2608,2638,2701-2702,2710,2717-2718,2725,2800,2809,2811,2869,2875,2909-2910,2920,2967-2968,2998,3000-3001,3003,3005-3007,3011,3013,3017,3030-3031,3052,3071,3077,3128,3168,3211,3221,3260-3261,3268-3269,3283,3300-3301,3306,3322-3325,3333,3351,3367,3369-3372,3389-3390,3404,3476,3493,3517,3527,3546,3551,3580,3659,3689-3690,3703,3737,3766,3784,3800-3801,3809,3814,3826-3828,3851,3869,3871,3878,3880,3889,3905,3914,3918,3920,3945,3971,3986,3995,3998,4000-4006,4045,4111,4125-4126,4129,4224,4242,4279,4321,4343,4443-4446,4449,4550,4567,4662,4848,4899-4900,4998,5000-5004,5009,5030,5033,5050-5051,5054,5060-5061,5080,5087,5100-5102,5120,5190,5200,5214,5221-5222,5225-5226,5269,5280,5298,5357,5405,5414,5431-5432,5440,5500,5510,5544,5550,5555,5560,5566,5631,5633,5666,5678-5679,5718,5730,5800-5802,5810-5811,5815,5822,5825,5850,5859,5862,5877,5900-5904,5906-5907,5910-5911,5915,5922,5925,5950,5952,5959-5963,5987-5989,5998-6007,6009,6025,6059,6100-6101,6106,6112,6123,6129,6156,6346,6389,6502,6510,6543,6547,6565-6567,6580,6646,6666-6669,6689,6692,6699,6779,6788-6789,6792,6839,6881,6901,6969,7000-7002,7004,7007,7019,7025,7070,7100,7103,7106,7200-7201,7402,7435,7443,7496,7512,7625,7627,7676,7741,7777-7778,7800,7911,7920-7921,7937-7938,7999-8002,8007-8011,8021-8022,8031,8042,8045,8080-8090,8093,8099-8100,8180-8181,8192-8194,8200,8222,8254,8290-8292,8300,8333,8383,8400,8402,8443,8500,8600,8649,8651-8652,8654,8701,8800,8873,8888,8899,8994,9000-9003,9009-9011,9040,9050,9071,9080-9081,9090-9091,9099-9103,9110-9111,9200,9207,9220,9290,9415,9418,9485,9500,9502-9503,9535,9575,9593-9595,9618,9666,9876-9878,9898,9900,9917,9929,9943-9944,9968,9998-10004,10009-10010,10012,10024-10025,10082,10180,10215,10243,10566,10616-10617,10621,10626,10628-10629,10778,11110-11111,11967,12000,12174,12265,12345,13456,13722,13782-13783,14000,14238,14441-14442,15000,15002-15004,15660,15742,16000-16001,16012,16016,16018,16080,16113,16992-16993,17877,17988,18040,18101,18988,19101,19283,19315,19350,19780,19801,19842,20000,20005,20031,20221-20222,20828,21571,22939,23502,24444,24800,25734-25735,26214,27000,27352-27353,27355-27356,27715,28201,30000,30718,30951,31038,31337,32768-32785,33354,33899,34571-34573,35500,38292,40193,40911,41511,42510,44176,44442-44443,44501,45100,48080,49152-49161,49163,49165,49167,49175-49176,49400,49999-50003,50006,50300,50389,50500,50636,50800,51103,51493,52673,52822,52848,52869,54045,54328,55055-55056,55555,55600,56737-56738,57294,57797,58080,60020,60443,61532,61900,62078,63331,64623,64680,65000,65129,65389"}, "_hosts": [{"__NmapHost__": {"_starttime": "1361737906", "_endtime": "1361737906", "_hostnames": ["localhost", "localhost"], "_status": {"state": "up", "reason": "localhost-response"}, "_services": [{"__NmapService__": {"_portid": 22, "_protocol": "tcp", "_state": {"state": "open", "reason": "syn-ack", "reason_ttl": "64"}, "_service": {"name": "ssh", "method": "table", "conf": "3", "cpelist": []}, "_cpelist": [], "_owner": "", "_reason": "syn-ack", "_reason_ip": "", "_reason_ttl": "64", "_servicefp": "", "_tunnel": "", "_service_extras": {"scripts": []}}}, {"__NmapService__": {"_portid": 25, "_protocol": "tcp", "_state": {"state": "open", "reason": "syn-ack", "reason_ttl": "64"}, "_service": {"name": "smtp", "method": "table", "conf": "3", "cpelist": []}, "_cpelist": [], "_owner": "", "_reason": "syn-ack", "_reason_ip": "", "_reason_ttl": "64", "_servicefp": "", "_tunnel": "", "_service_extras": {"scripts": []}}}, {"__NmapService__": {"_portid": 111, "_protocol": "tcp", "_state": {"state": "open", "reason": "syn-ack", "reason_ttl": "64"}, "_service": {"name": "rpcbind", "method": "table", "conf": "3", "cpelist": []}, "_cpelist": [], "_owner": "", "_reason": "syn-ack", "_reason_ip": "", "_reason_ttl": "64", "_servicefp": "", "_tunnel": "", "_service_extras": {"scripts": []}}}, {"__NmapService__": {"_portid": 631, "_protocol": "tcp", "_state": {"state": "open", "reason": "syn-ack", "reason_ttl": "64"}, "_service": {"name": "ipp", "method": "table", "conf": "3", "cpelist": []}, "_cpelist": [], "_owner": "", "_reason": "syn-ack", "_reason_ip": "", "_reason_ttl": "64", "_servicefp": "", "_tunnel": "", "_service_extras": {"scripts": []}}}, {"__NmapService__": {"_portid": 3306, "_protocol": "tcp", "_state": {"state": "open", "reason": "syn-ack", "reason_ttl": "64"}, "_service": {"name": "mysql", "method": "table", "conf": "3", "cpelist": []}, "_cpelist": [], "_owner": "", "_reason": "syn-ack", "_reason_ip": "", "_reason_ttl": "64", "_servicefp": "", "_tunnel": "", "_service_extras": {"scripts": []}}}], "_extras": {"extraports": {"state": {"state": "closed", "count": "995"}, "count": {"state": "closed", "count": "995"}, "reasons": [{"reason": "resets", "count": "995"}]}, "times": {"srtt": "7", "rttvar": "0", "to": "100000"}}, "_osfingerprinted": false, "os": {"__NmapOSFingerprint__": {"_NmapOSFingerprint__osmatches": [], "_NmapOSFingerprint__ports_used": [], "_NmapOSFingerprint__fingerprints": []}}, "_ipv4_addr": "127.0.0.1", "_ipv6_addr": null, "_mac_addr": null, "_vendor": null, "_main_address": "127.0.0.1", "_address": [{"addr": "127.0.0.1", "addrtype": "ipv4"}]}}, {"__NmapHost__": {"_starttime": "1361737906", "_endtime": "1361738040", "_hostnames": ["scanme.nmap.org", "scanme.nmap.org"], "_status": {"state": "up", "reason": "echo-reply"}, "_services": [{"__NmapService__": {"_portid": 22, "_protocol": "tcp", "_state": {"state": "open", "reason": "syn-ack", "reason_ttl": "53"}, "_service": {"name": "ssh", "method": "table", "conf": "3", "cpelist": []}, "_cpelist": [], "_owner": "", "_reason": "syn-ack", "_reason_ip": "", "_reason_ttl": "53", "_servicefp": "", "_tunnel": "", "_service_extras": {"scripts": []}}}, {"__NmapService__": {"_portid": 25, "_protocol": "tcp", "_state": {"state": "filtered", "reason": "admin-prohibited", "reason_ttl": "253", "reason_ip": "109.133.192.1"}, "_service": {"name": "smtp", "method": "table", "conf": "3", "cpelist": []}, "_cpelist": [], "_owner": "", "_reason": "admin-prohibited", "_reason_ip": "109.133.192.1", "_reason_ttl": "253", "_servicefp": "", "_tunnel": "", "_service_extras": {"scripts": []}}}, {"__NmapService__": {"_portid": 80, "_protocol": "tcp", "_state": {"state": "open", "reason": "syn-ack", "reason_ttl": "51"}, "_service": {"name": "http", "method": "table", "conf": "3", "cpelist": []}, "_cpelist": [], "_owner": "", "_reason": "syn-ack", "_reason_ip": "", "_reason_ttl": "51", "_servicefp": "", "_tunnel": "", "_service_extras": {"scripts": []}}}, {"__NmapService__": {"_portid": 9929, "_protocol": "tcp", "_state": {"state": "open", "reason": "syn-ack", "reason_ttl": "53"}, "_service": {"name": "nping-echo", "method": "table", "conf": "3", "cpelist": []}, "_cpelist": [], "_owner": "", "_reason": "syn-ack", "_reason_ip": "", "_reason_ttl": "53", "_servicefp": "", "_tunnel": "", "_service_extras": {"scripts": []}}}], "_extras": {"extraports": {"state": {"state": "closed", "count": "996"}, "count": {"state": "closed", "count": "996"}, "reasons": [{"reason": "resets", "count": "996"}]}, "times": {"srtt": "177425", "rttvar": "1981", "to": "185349"}}, "_osfingerprinted": false, "os": {"__NmapOSFingerprint__": {"_NmapOSFingerprint__osmatches": [], "_NmapOSFingerprint__ports_used": [], "_NmapOSFingerprint__fingerprints": []}}, "_ipv4_addr": "74.207.244.221", "_ipv6_addr": null, "_mac_addr": null, "_vendor": null, "_main_address": "74.207.244.221", "_address": [{"addr": "74.207.244.221", "addrtype": "ipv4"}]}}], "_runstats": {"finished": {"time": "1361738040", "timestr": "Sun Feb 24 21:34:00 2013", "elapsed": "134.36", "summary": "Nmap done at Sun Feb 24 21:34:00 2013; 2 IP addresses (2 hosts up) scanned in 134.36 seconds", "exit": "success"}, "hosts": {"up": "2", "down": "0", "total": "2"}}}} +{"__NmapReport__": {"_nmaprun": {"scanner": "nmap", "args": "nmap -sS -vv -oX 2_hosts.xml localhost scanme.nmap.org", "start": "1361737906", "startstr": "Sun Feb 24 21:31:46 2013", "version": "5.51", "xmloutputversion": "1.03"}, "_scaninfo": {"type": "syn", "protocol": "tcp", "numservices": "1000", "services": "1,3-4,6-7,9,13,17,19-26,30,32-33,37,42-43,49,53,70,79-85,88-90,99-100,106,109-111,113,119,125,135,139,143-144,146,161,163,179,199,211-212,222,254-256,259,264,280,301,306,311,340,366,389,406-407,416-417,425,427,443-445,458,464-465,481,497,500,512-515,524,541,543-545,548,554-555,563,587,593,616-617,625,631,636,646,648,666-668,683,687,691,700,705,711,714,720,722,726,749,765,777,783,787,800-801,808,843,873,880,888,898,900-903,911-912,981,987,990,992-993,995,999-1002,1007,1009-1011,1021-1100,1102,1104-1108,1110-1114,1117,1119,1121-1124,1126,1130-1132,1137-1138,1141,1145,1147-1149,1151-1152,1154,1163-1166,1169,1174-1175,1183,1185-1187,1192,1198-1199,1201,1213,1216-1218,1233-1234,1236,1244,1247-1248,1259,1271-1272,1277,1287,1296,1300-1301,1309-1311,1322,1328,1334,1352,1417,1433-1434,1443,1455,1461,1494,1500-1501,1503,1521,1524,1533,1556,1580,1583,1594,1600,1641,1658,1666,1687-1688,1700,1717-1721,1723,1755,1761,1782-1783,1801,1805,1812,1839-1840,1862-1864,1875,1900,1914,1935,1947,1971-1972,1974,1984,1998-2010,2013,2020-2022,2030,2033-2035,2038,2040-2043,2045-2049,2065,2068,2099-2100,2103,2105-2107,2111,2119,2121,2126,2135,2144,2160-2161,2170,2179,2190-2191,2196,2200,2222,2251,2260,2288,2301,2323,2366,2381-2383,2393-2394,2399,2401,2492,2500,2522,2525,2557,2601-2602,2604-2605,2607-2608,2638,2701-2702,2710,2717-2718,2725,2800,2809,2811,2869,2875,2909-2910,2920,2967-2968,2998,3000-3001,3003,3005-3007,3011,3013,3017,3030-3031,3052,3071,3077,3128,3168,3211,3221,3260-3261,3268-3269,3283,3300-3301,3306,3322-3325,3333,3351,3367,3369-3372,3389-3390,3404,3476,3493,3517,3527,3546,3551,3580,3659,3689-3690,3703,3737,3766,3784,3800-3801,3809,3814,3826-3828,3851,3869,3871,3878,3880,3889,3905,3914,3918,3920,3945,3971,3986,3995,3998,4000-4006,4045,4111,4125-4126,4129,4224,4242,4279,4321,4343,4443-4446,4449,4550,4567,4662,4848,4899-4900,4998,5000-5004,5009,5030,5033,5050-5051,5054,5060-5061,5080,5087,5100-5102,5120,5190,5200,5214,5221-5222,5225-5226,5269,5280,5298,5357,5405,5414,5431-5432,5440,5500,5510,5544,5550,5555,5560,5566,5631,5633,5666,5678-5679,5718,5730,5800-5802,5810-5811,5815,5822,5825,5850,5859,5862,5877,5900-5904,5906-5907,5910-5911,5915,5922,5925,5950,5952,5959-5963,5987-5989,5998-6007,6009,6025,6059,6100-6101,6106,6112,6123,6129,6156,6346,6389,6502,6510,6543,6547,6565-6567,6580,6646,6666-6669,6689,6692,6699,6779,6788-6789,6792,6839,6881,6901,6969,7000-7002,7004,7007,7019,7025,7070,7100,7103,7106,7200-7201,7402,7435,7443,7496,7512,7625,7627,7676,7741,7777-7778,7800,7911,7920-7921,7937-7938,7999-8002,8007-8011,8021-8022,8031,8042,8045,8080-8090,8093,8099-8100,8180-8181,8192-8194,8200,8222,8254,8290-8292,8300,8333,8383,8400,8402,8443,8500,8600,8649,8651-8652,8654,8701,8800,8873,8888,8899,8994,9000-9003,9009-9011,9040,9050,9071,9080-9081,9090-9091,9099-9103,9110-9111,9200,9207,9220,9290,9415,9418,9485,9500,9502-9503,9535,9575,9593-9595,9618,9666,9876-9878,9898,9900,9917,9929,9943-9944,9968,9998-10004,10009-10010,10012,10024-10025,10082,10180,10215,10243,10566,10616-10617,10621,10626,10628-10629,10778,11110-11111,11967,12000,12174,12265,12345,13456,13722,13782-13783,14000,14238,14441-14442,15000,15002-15004,15660,15742,16000-16001,16012,16016,16018,16080,16113,16992-16993,17877,17988,18040,18101,18988,19101,19283,19315,19350,19780,19801,19842,20000,20005,20031,20221-20222,20828,21571,22939,23502,24444,24800,25734-25735,26214,27000,27352-27353,27355-27356,27715,28201,30000,30718,30951,31038,31337,32768-32785,33354,33899,34571-34573,35500,38292,40193,40911,41511,42510,44176,44442-44443,44501,45100,48080,49152-49161,49163,49165,49167,49175-49176,49400,49999-50003,50006,50300,50389,50500,50636,50800,51103,51493,52673,52822,52848,52869,54045,54328,55055-55056,55555,55600,56737-56738,57294,57797,58080,60020,60443,61532,61900,62078,63331,64623,64680,65000,65129,65389"}, "_hosts": [{"__NmapHost__": {"_starttime": "1361737906", "_endtime": "1361737906", "_hostnames": ["localhost", "localhost"], "_status": {"state": "up", "reason": "localhost-response"}, "_services": [{"__NmapService__": {"_portid": 22, "_protocol": "tcp", "_state": {"state": "open", "reason": "syn-ack", "reason_ttl": "64"}, "_service": {"name": "ssh", "method": "table", "conf": "3", "cpelist": []}, "_cpelist": [], "_owner": "", "_reason": "syn-ack", "_reason_ip": "", "_reason_ttl": "64", "_servicefp": "", "_tunnel": "", "_service_extras": {"scripts": []}}}, {"__NmapService__": {"_portid": 25, "_protocol": "tcp", "_state": {"state": "open", "reason": "syn-ack", "reason_ttl": "64"}, "_service": {"name": "smtp", "method": "table", "conf": "3", "cpelist": []}, "_cpelist": [], "_owner": "", "_reason": "syn-ack", "_reason_ip": "", "_reason_ttl": "64", "_servicefp": "", "_tunnel": "", "_service_extras": {"scripts": []}}}, {"__NmapService__": {"_portid": 111, "_protocol": "tcp", "_state": {"state": "open", "reason": "syn-ack", "reason_ttl": "64"}, "_service": {"name": "rpcbind", "method": "table", "conf": "3", "cpelist": []}, "_cpelist": [], "_owner": "", "_reason": "syn-ack", "_reason_ip": "", "_reason_ttl": "64", "_servicefp": "", "_tunnel": "", "_service_extras": {"scripts": []}}}, {"__NmapService__": {"_portid": 631, "_protocol": "tcp", "_state": {"state": "open", "reason": "syn-ack", "reason_ttl": "64"}, "_service": {"name": "ipp", "method": "table", "conf": "3", "cpelist": []}, "_cpelist": [], "_owner": "", "_reason": "syn-ack", "_reason_ip": "", "_reason_ttl": "64", "_servicefp": "", "_tunnel": "", "_service_extras": {"scripts": []}}}, {"__NmapService__": {"_portid": 3306, "_protocol": "tcp", "_state": {"state": "open", "reason": "syn-ack", "reason_ttl": "64"}, "_service": {"name": "mysql", "method": "table", "conf": "3", "cpelist": []}, "_cpelist": [], "_owner": "", "_reason": "syn-ack", "_reason_ip": "", "_reason_ttl": "64", "_servicefp": "", "_tunnel": "", "_service_extras": {"scripts": []}}}], "_extras": {"extraports": [{"state": "closed", "count": "995", "extrareasons": [{"reason": "resets", "count": "995"}]}], "times": {"srtt": "7", "rttvar": "0", "to": "100000"}}, "_extraports": [{"state": "closed", "count": "995", "extrareasons": [{"reason": "resets", "count": "995"}]}], "_osfingerprinted": false, "os": {"__NmapOSFingerprint__": {"_NmapOSFingerprint__osmatches": [], "_NmapOSFingerprint__ports_used": [], "_NmapOSFingerprint__fingerprints": []}}, "_ipv4_addr": "127.0.0.1", "_ipv6_addr": null, "_mac_addr": null, "_vendor": null, "_main_address": "127.0.0.1", "_address": [{"addr": "127.0.0.1", "addrtype": "ipv4"}]}}, {"__NmapHost__": {"_starttime": "1361737906", "_endtime": "1361738040", "_hostnames": ["scanme.nmap.org", "scanme.nmap.org"], "_status": {"state": "up", "reason": "echo-reply"}, "_services": [{"__NmapService__": {"_portid": 22, "_protocol": "tcp", "_state": {"state": "open", "reason": "syn-ack", "reason_ttl": "53"}, "_service": {"name": "ssh", "method": "table", "conf": "3", "cpelist": []}, "_cpelist": [], "_owner": "", "_reason": "syn-ack", "_reason_ip": "", "_reason_ttl": "53", "_servicefp": "", "_tunnel": "", "_service_extras": {"scripts": []}}}, {"__NmapService__": {"_portid": 25, "_protocol": "tcp", "_state": {"state": "filtered", "reason": "admin-prohibited", "reason_ttl": "253", "reason_ip": "109.133.192.1"}, "_service": {"name": "smtp", "method": "table", "conf": "3", "cpelist": []}, "_cpelist": [], "_owner": "", "_reason": "admin-prohibited", "_reason_ip": "109.133.192.1", "_reason_ttl": "253", "_servicefp": "", "_tunnel": "", "_service_extras": {"scripts": []}}}, {"__NmapService__": {"_portid": 80, "_protocol": "tcp", "_state": {"state": "open", "reason": "syn-ack", "reason_ttl": "51"}, "_service": {"name": "http", "method": "table", "conf": "3", "cpelist": []}, "_cpelist": [], "_owner": "", "_reason": "syn-ack", "_reason_ip": "", "_reason_ttl": "51", "_servicefp": "", "_tunnel": "", "_service_extras": {"scripts": []}}}, {"__NmapService__": {"_portid": 9929, "_protocol": "tcp", "_state": {"state": "open", "reason": "syn-ack", "reason_ttl": "53"}, "_service": {"name": "nping-echo", "method": "table", "conf": "3", "cpelist": []}, "_cpelist": [], "_owner": "", "_reason": "syn-ack", "_reason_ip": "", "_reason_ttl": "53", "_servicefp": "", "_tunnel": "", "_service_extras": {"scripts": []}}}], "_extras": {"extraports": [{"state": "closed", "count": "996", "extrareasons": [{"reason": "resets", "count": "996"}]}], "times": {"srtt": "177425", "rttvar": "1981", "to": "185349"}}, "_extraports": [{"state": "closed", "count": "996", "extrareasons": [{"reason": "resets", "count": "996"}]}], "_osfingerprinted": false, "os": {"__NmapOSFingerprint__": {"_NmapOSFingerprint__osmatches": [], "_NmapOSFingerprint__ports_used": [], "_NmapOSFingerprint__fingerprints": []}}, "_ipv4_addr": "74.207.244.221", "_ipv6_addr": null, "_mac_addr": null, "_vendor": null, "_main_address": "74.207.244.221", "_address": [{"addr": "74.207.244.221", "addrtype": "ipv4"}]}}], "_runstats": {"finished": {"time": "1361738040", "timestr": "Sun Feb 24 21:34:00 2013", "elapsed": "134.36", "summary": "Nmap done at Sun Feb 24 21:34:00 2013; 2 IP addresses (2 hosts up) scanned in 134.36 seconds", "exit": "success"}, "hosts": {"up": "2", "down": "0", "total": "2"}}}} diff --git a/libnmap/test/test_extraports.py b/libnmap/test/test_extraports.py index c40308b..c52a17a 100644 --- a/libnmap/test/test_extraports.py +++ b/libnmap/test/test_extraports.py @@ -10,14 +10,14 @@ class TestExtraPorts(unittest.TestCase): def setUp(self): fdir = os.path.dirname(os.path.realpath(__file__)) - _extrareason = [ + _extrareasons = [ {"reason": "filtered", "count": "3"}, {"reason": "resets", "count": "7"}, ] self.flist = [ { "path": "%s/%s" % (fdir, "files/extra_ports.xml"), - "extrareason": _extrareason, + "extrareasons": _extrareasons, } ] @@ -26,12 +26,14 @@ def test_extraports(self): rep1 = NmapParser.parse_fromfile(fentry["path"]) ep_list = rep1.hosts[0].extraports self.assertEqual(len(ep_list), 2) - self.assertEqual(ep_list[0].extra_count, 65509) - self.assertEqual(ep_list[0].extra_state, "closed") - self.assertEqual(len(ep_list[0].extra_reasons), 1) - self.assertEqual(ep_list[1].extra_count, 10) - self.assertEqual(len(ep_list[1].extra_reasons), 2) - self.assertEqual(ep_list[1].extra_reasons, fentry["extrareason"]) + self.assertEqual(ep_list[0]["count"], "65509") + self.assertEqual(ep_list[0]["state"], "closed") + self.assertEqual(len(ep_list[0]["extrareasons"]), 1) + self.assertEqual(ep_list[1]["count"], "10") + self.assertEqual(len(ep_list[1]["extrareasons"]), 2) + self.assertEqual( + ep_list[1]["extrareasons"], fentry["extrareasons"] + ) if __name__ == "__main__": diff --git a/libnmap/test/test_host.py b/libnmap/test/test_host.py index d192f32..0dff34d 100644 --- a/libnmap/test/test_host.py +++ b/libnmap/test/test_host.py @@ -181,25 +181,6 @@ 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) From 86478126924f3b002f814814546c7b2ab69250db Mon Sep 17 00:00:00 2001 From: Aquila Macedo Date: Sat, 16 Dec 2023 23:28:11 -0300 Subject: [PATCH 86/88] fix: replace deprecated distutils with setuptools Update setup.py from 'distutils' to 'setuptools' due to 'distutils' being deprecated in Python 3.12 Details: https://peps.python.org/pep-0632/ --- requirements-dev.txt | 1 + setup.py | 6 +++++- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/requirements-dev.txt b/requirements-dev.txt index d63813a..e8abeb1 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -5,3 +5,4 @@ pre-commit pytest pytest-cov flake8 +setuptools diff --git a/setup.py b/setup.py index eb5c0f5..6280910 100644 --- a/setup.py +++ b/setup.py @@ -1,6 +1,10 @@ # -*- coding: utf-8 -*- import sys -from distutils.core import setup +try: + from setuptools import setup +except ImportError: + from distutils.core import setup + if sys.version_info >= (3, 0): with open("README.rst", encoding="utf-8") as rfile: From 01944cadab6eb65eae27e261ef86324ba7fa12ba Mon Sep 17 00:00:00 2001 From: Ronald Bister Date: Wed, 29 Jan 2025 22:59:21 +0100 Subject: [PATCH 87/88] fix: added hybrid support for setuptools --- README.rst | 9 +++++---- requirements-dev.txt | 7 +++---- setup.py | 5 +++-- 3 files changed, 11 insertions(+), 10 deletions(-) diff --git a/README.rst b/README.rst index 023726f..d4ab112 100644 --- a/README.rst +++ b/README.rst @@ -115,21 +115,22 @@ You can install libnmap via pip: ronald@brouette:~$ pip install python-libnmap -or via git and dist utils (à l’ancienne): +or via git and pip: .. code:: bash ronald@brouette:~$ git clone https://github.com/savon-noir/python-libnmap.git ronald@brouette:~$ cd python-libnmap - ronald@brouette:~$ python setup.py install + ronald@brouette:~$ pip install . -or via git and pip: +or via git and dist utils (à l’ancienne/deprecated): .. code:: bash ronald@brouette:~$ git clone https://github.com/savon-noir/python-libnmap.git ronald@brouette:~$ cd python-libnmap - ronald@brouette:~$ pip install . + ronald@brouette:~$ python setup.py install + Examples -------- diff --git a/requirements-dev.txt b/requirements-dev.txt index e8abeb1..cb383eb 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -1,8 +1,7 @@ -black==22.8.0 -defusedxml==0.6.0 -isort==5.6.4 +black==24.3.0 +defusedxml==0.7.1 +isort==6.0.0 pre-commit pytest pytest-cov flake8 -setuptools diff --git a/setup.py b/setup.py index 6280910..bd35ab1 100644 --- a/setup.py +++ b/setup.py @@ -5,11 +5,10 @@ except ImportError: from distutils.core import setup - if sys.version_info >= (3, 0): with open("README.rst", encoding="utf-8") as rfile: long_description = rfile.read() -else: # encoding not compatible with python2 +else: # if encoding not compatible with python2 with open("README.rst") as rfile: long_description = rfile.read() @@ -42,6 +41,8 @@ "Programming Language :: Python :: 3.6", "Programming Language :: Python :: 3.7", "Programming Language :: Python :: 3.8", + "Programming Language :: Python :: 3.9", + "Programming Language :: Python :: 3.10", "Topic :: System :: Networking", ], ) From f9887fc9add90e2e0b3c59671ec5ecd9bee8fcd3 Mon Sep 17 00:00:00 2001 From: Ronald Bister Date: Wed, 29 Jan 2025 23:07:53 +0100 Subject: [PATCH 88/88] fix: linting --- setup.py | 1 + 1 file changed, 1 insertion(+) diff --git a/setup.py b/setup.py index bd35ab1..1ff64c0 100644 --- a/setup.py +++ b/setup.py @@ -1,5 +1,6 @@ # -*- coding: utf-8 -*- import sys + try: from setuptools import setup except ImportError: