diff --git a/.coveragerc b/.coveragerc
new file mode 100644
index 0000000..812fc3b
--- /dev/null
+++ b/.coveragerc
@@ -0,0 +1,4 @@
+[report]
+omit =
+ */python?.?/*
+ */site-packages/nose/*
diff --git a/.github/workflows/preflight_check.yaml b/.github/workflows/preflight_check.yaml
new file mode 100644
index 0000000..c9f52e3
--- /dev/null
+++ b/.github/workflows/preflight_check.yaml
@@ -0,0 +1,76 @@
+name: Preflight Check
+
+on:
+ push:
+ branches:
+ - '**'
+ pull_request:
+ branches:
+ - '**'
+
+jobs:
+ lint:
+ runs-on: ubuntu-22.04
+ strategy:
+ matrix:
+ python-version: ["3.8", "3.9", "3.10"]
+ steps:
+ - uses: actions/checkout@v4
+ - name: Set up Python ${{ matrix.python-version }}
+ uses: actions/setup-python@v5
+ with:
+ python-version: ${{ matrix.python-version }}
+ - name: Install dependencies
+ run: |
+ python -m pip install --upgrade pip
+ pip install black isort flake8
+ - name: Format checker with psf/black
+ uses: psf/black@stable
+ with:
+ options: "--check -l 79 --exclude docs/"
+ version: "24.3.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-22.04
+ strategy:
+ matrix:
+ python-version: ["3.8", "3.9", "3.10"]
+ steps:
+ - uses: actions/checkout@v4
+ - name: Setup Python ${{ matrix.python-version }}
+ uses: actions/setup-python@v5
+ with:
+ python-version: ${{ matrix.python-version }}
+ - name: Setup Environment
+ run: |
+ python -m pip install --upgrade pip
+ pip install pytest pytest-cov defusedxml
+ pip install coveralls
+ sudo apt-get install -y nmap
+ if [ -f requirements.txt ]; then pip install -r requirements.txt; fi
+ - name: Test with pytest
+ run: |
+ pytest --cov=libnmap/ --ignore=libnmap/test/test_backend_plugin_factory.py
+ - name: Upload Coverage
+ if: matrix.python-version != '2.7'
+ env:
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+ COVERALLS_FLAG_NAME: ${{ matrix.python-version }}
+ COVERALLS_PARALLEL: true
+ run: |
+ coveralls --service=github
+ coveralls:
+ name: Finish Coveralls
+ needs: test
+ runs-on: ubuntu-22.04
+ container: python:3-slim
+ steps:
+ - name: Finished
+ run: |
+ pip3 install --upgrade coveralls
+ coveralls --finish --service=github
+ env:
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
diff --git a/.github/workflows/pypi_publish.yaml b/.github/workflows/pypi_publish.yaml
new file mode 100644
index 0000000..583652b
--- /dev/null
+++ b/.github/workflows/pypi_publish.yaml
@@ -0,0 +1,26 @@
+name: Upload Python Package
+
+on:
+ release:
+ types: [published]
+
+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
+ twine upload dist/*
diff --git a/.gitignore b/.gitignore
index e493d62..02d3b53 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,5 +1,6 @@
*.py[cod]
*.swp
+.pylintrc
*~
*.lock
*.DS_Store
@@ -39,3 +40,8 @@ nosetests.xml
.project
.pydevproject
.swp
+
+__pycache__
+.vscode/settings.json
+.noseids
+_build
diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
new file mode 100644
index 0000000..ab2e020
--- /dev/null
+++ b/.pre-commit-config.yaml
@@ -0,0 +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: 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/.travis.yml b/.travis.yml
deleted file mode 100644
index fd063dd..0000000
--- a/.travis.yml
+++ /dev/null
@@ -1,28 +0,0 @@
-language: python
-python:
-# - "2.5" Not needed EL can use epel to upgrade to 2.6
- - "2.6"
- - "2.7"
-# - "3.2" Not ready yet
-# 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
-
-install:
- - "pip install pep8 --use-mirrors"
- - "pip install pyflakes --use-mirrors"
- - "pip install boto --use-mirrors"
- - "pip install pymongo sqlalchemy MySQL-python --use-mirrors"
- - "python setup.py install"
-# - "pip install -r requirements.txt --use-mirrors"
-# # command to run tests
-before_script:
- - "pep8 . --exclude test,docs,examples"
- - "pyflakes ."
- - mysql -e 'create database poulet;'
-script: nosetests
diff --git a/CHANGELOG.md b/CHANGELOG.md
new file mode 100644
index 0000000..d3fd9ad
--- /dev/null
+++ b/CHANGELOG.md
@@ -0,0 +1,329 @@
+# 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.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
+
+- 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/CHANGES.txt b/CHANGES.txt
deleted file mode 100644
index 943b2c0..0000000
--- a/CHANGES.txt
+++ /dev/null
@@ -1,53 +0,0 @@
-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/CODEOWNERS b/CODEOWNERS
new file mode 100644
index 0000000..78c6fef
--- /dev/null
+++ b/CODEOWNERS
@@ -0,0 +1 @@
+* @savon-noir
diff --git a/LICENCE b/LICENCE
new file mode 100644
index 0000000..067f271
--- /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.
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/MANIFEST b/MANIFEST
new file mode 100644
index 0000000..c06a8aa
--- /dev/null
+++ b/MANIFEST
@@ -0,0 +1,34 @@
+# file GENERATED by distutils, do NOT edit
+README.rst
+TODO
+requirements-dev.txt
+setup.py
+docs/diff.rst
+docs/index.rst
+docs/objects.rst
+docs/parser.rst
+docs/plugins_s3.rst
+docs/process.rst
+docs/objects/cpe.rst
+docs/objects/nmaphost.rst
+docs/objects/nmapreport.rst
+docs/objects/nmapservice.rst
+docs/objects/os.rst
+libnmap/__init__.py
+libnmap/diff.py
+libnmap/parser.py
+libnmap/process.py
+libnmap/reportjson.py
+libnmap/objects/__init__.py
+libnmap/objects/cpe.py
+libnmap/objects/host.py
+libnmap/objects/os.py
+libnmap/objects/report.py
+libnmap/objects/service.py
+libnmap/plugins/__init__.py
+libnmap/plugins/backendplugin.py
+libnmap/plugins/backendpluginFactory.py
+libnmap/plugins/es.py
+libnmap/plugins/mongodb.py
+libnmap/plugins/s3.py
+libnmap/plugins/sql.py
diff --git a/NOTICE b/NOTICE
new file mode 100644
index 0000000..615671d
--- /dev/null
+++ b/NOTICE
@@ -0,0 +1,4 @@
+python-libnmap
+Copyright 2020 Ronald Bister
+
+This product includes software developed by Ronald Bister
diff --git a/README.rst b/README.rst
index af24f6b..d4ab112 100644
--- a/README.rst
+++ b/README.rst
@@ -4,91 +4,155 @@ python-libnmap
Code status
-----------
-|Build Status|
+|preflight-check| |Coverage Status| |License|
Use cases
---------
-libnmap is a python library enabling python developpers to manipulate nmap process and data.
+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:
+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 for instance
-- compare and diff nmap scans to generate graphs for instance
-- batch process scan reports
-- ...
+- 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.
+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
- - csv: todo (easy to implement)
- - elastic search: todo
+- **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.
+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.
+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.
+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`_
+- `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.2.
+
+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::
+You can install libnmap via pip:
- pip install libnmap
+.. code:: bash
-or via git::
+ ronald@brouette:~$ pip install python-libnmap
- $ git clone https://github.com/savon-noir/python-libnmap.git
- $ cd python-libnmap
- $ python setup.py install
+or via git and pip:
-Examples
---------
+.. code:: bash
-Some codes samples are available in the examples directory or in the `documentation`_.
+ ronald@brouette:~$ git clone https://github.com/savon-noir/python-libnmap.git
+ ronald@brouette:~$ cd python-libnmap
+ ronald@brouette:~$ pip install .
-Contributors
-------------
+or via git and dist utils (à l’ancienne/deprecated):
+
+.. code:: bash
-Mike @bmx0r Boutillier for S3 and SQL-Alechemy plugins and for the constructive critics. Thanks!
+ ronald@brouette:~$ git clone https://github.com/savon-noir/python-libnmap.git
+ ronald@brouette:~$ cd python-libnmap
+ ronald@brouette:~$ python setup.py install
-.. |Build Status| image:: https://travis-ci.org/savon-noir/python-libnmap.png?branch=master
- :target: https://travis-ci.org/savon-noir/python-libnmap
-.. _read the docs: https://libnmap.readthedocs.org
+Examples
+--------
+
+Some codes samples are available in the examples directory or in the
+`documentation `__.
-.. _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:
-.. _boto: https://github.com/boto/boto
+.. figure:: https://github.com/savon-noir/python-libnmap/blob/es/examples/kibanalibnmap.png
+ :alt: Kibanane
+
+Contributors
+------------
-.. _pymongo: https://github.com/mongodb/mongo-python-driver/
+Mike @bmx0r Boutillier for S3 and SQL-Alechemy plugins and for the
+constructive critics. Thanks!
-.. _sqlalchemy: https://github.com/zzzeek/sqlalchemy
+.. |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/TODO b/TODO
index ae57798..bf03075 100644
--- a/TODO
+++ b/TODO
@@ -1,12 +1,32 @@
-- review OS fingerprint API and add NmapOSFP class
-- add unit test for os fingerprint class
-- complete unit tests
-- review and improve threading for NmapProcess
-- XML interface to support both ElementTree and lxml APIs
-- support for python3
-- support for windows and cywin
-- Add new plugins to support import/export from mysql, couchdb, csv
-- support iterators for NmapReport::hosts
-- add unittest for udp scans, ping sweeping
-- add support for 'resume' capability (see nmap --resume)
-- add support for "not shown ports" (extra ports) in NmapHost (via NmapParser)
+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
+
+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 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: 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/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/docs/conf.py b/docs/conf.py
index d51cc23..6e0d4ee 100644
--- a/docs/conf.py
+++ b/docs/conf.py
@@ -11,204 +11,208 @@
# 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
# 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"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.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 --------------------------------------------
@@ -216,12 +220,11 @@
# 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)
+ ("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 +233,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/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 c1b5e58..7226bbf 100644
--- a/docs/index.rst
+++ b/docs/index.rst
@@ -10,11 +10,18 @@ libnmap is a python toolkit for manipulating nmap. It currently offers the follo
- 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.
+- objects: contains basic nmap objects like NmapHost and NmapService. It is to note that each object can be "diff()ed" with another similar object.
+
+ - report: contains NmapReport class definition
+ - host: contains NmapHost class definition
+ - service: contains NmapService class definition
+ - os: contains NmapOSFingerprint class definition and some other classes like NmapOSMatch, NmapOSClass,...
+ - cpe: contains CPE class defdinition
+
- 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
@@ -35,6 +42,7 @@ The different modules are documented below:
process
parser
objects
+ objects/*
diff
plugins_s3
diff --git a/docs/objects.rst b/docs/objects.rst
index 96cbd22..e10e48e 100644
--- a/docs/objects.rst
+++ b/docs/objects.rst
@@ -10,6 +10,11 @@ This module contains the definition and API of all "NmapObjects" which enables u
2. NmapHost
3. NmapService
+The three objects above are the most common one that one would manipulate. For more advanced usage, the following objects might be useful
+
+1. NmapOSFingerprint (contains: NmapOSMatch, NmapOSClass, OSFPPortUsed)
+2. CPE (Common platform enumeration contained in NmapService or NmapOSClass)
+
The following structure applies by default:
NmapReport contains:
@@ -26,27 +31,10 @@ NmapService contains:
- scan results for this service:
- service state, service name
- optional: service banner
- - optionla: NSE scripts data
+ - optional: NSE scripts data
Each of the above-mentioned objects have a diff() method which enables the user of the lib the compare two different objects
of the same type.
If you read the code you'll see the dirty trick with id() which ensures that proper objects are being compared. The logic of diff will certainly change overtime but the API (i/o) will be kept as is.
For more info on diff, please check the module's `documentation _`.
-
-NmapReport methods
-------------------
-
-.. automodule:: libnmap.objects
-.. autoclass:: NmapReport
- :members:
-
-NmapHost methods
-----------------
-.. autoclass:: NmapHost
- :members:
-
-NmapService methods
--------------------
-.. autoclass:: NmapService
- :members:
diff --git a/docs/objects/cpe.rst b/docs/objects/cpe.rst
new file mode 100644
index 0000000..9f13d8f
--- /dev/null
+++ b/docs/objects/cpe.rst
@@ -0,0 +1,14 @@
+libnmap.objects.cpe
+===================
+
+Using libnmap.objects.cpe module
+--------------------------------
+
+TODO
+
+CPE methods
+-----------
+
+.. automodule:: libnmap.objects.cpe
+.. autoclass:: CPE
+ :members:
diff --git a/docs/objects/nmaphost.rst b/docs/objects/nmaphost.rst
new file mode 100644
index 0000000..ee4ad33
--- /dev/null
+++ b/docs/objects/nmaphost.rst
@@ -0,0 +1,14 @@
+libnmap.objects.host
+====================
+
+Using libnmap.objects.host module
+---------------------------------
+
+TODO
+
+NmapHost methods
+----------------
+
+.. automodule:: libnmap.objects
+.. autoclass:: NmapHost
+ :members:
diff --git a/docs/objects/nmapreport.rst b/docs/objects/nmapreport.rst
new file mode 100644
index 0000000..fedcebe
--- /dev/null
+++ b/docs/objects/nmapreport.rst
@@ -0,0 +1,14 @@
+libnmap.objects.report
+======================
+
+Using libnmap.objects.report module
+-----------------------------------
+
+TODO
+
+NmapReport methods
+------------------
+
+.. automodule:: libnmap.objects
+.. autoclass:: NmapReport
+ :members:
diff --git a/docs/objects/nmapservice.rst b/docs/objects/nmapservice.rst
new file mode 100644
index 0000000..e56d7db
--- /dev/null
+++ b/docs/objects/nmapservice.rst
@@ -0,0 +1,14 @@
+libnmap.objects.service
+=======================
+
+Using libnmap.objects.service module
+------------------------------------
+
+TODO
+
+NmapService methods
+-------------------
+
+.. automodule:: libnmap.objects
+.. autoclass:: NmapService
+ :members:
diff --git a/docs/objects/os.rst b/docs/objects/os.rst
new file mode 100644
index 0000000..23fa536
--- /dev/null
+++ b/docs/objects/os.rst
@@ -0,0 +1,32 @@
+libnmap.objects.os
+==================
+
+Using libnmap.objects.os module
+-------------------------------
+
+TODO
+
+NmapOSFingerprint methods
+-------------------------
+
+.. automodule:: libnmap.objects.os
+.. autoclass:: NmapOSFingerprint
+ :members:
+
+NmapOSMatch methods
+-------------------
+
+.. autoclass:: NmapOSMatch
+ :members:
+
+NmapOSClass methods
+-------------------
+
+.. autoclass:: NmapOSClass
+ :members:
+
+OSFPPortUsed methods
+--------------------
+
+.. autoclass:: OSFPPortUsed
+ :members:
diff --git a/docs/parser.rst b/docs/parser.rst
index fade961..09bc5d2 100644
--- a/docs/parser.rst
+++ b/docs/parser.rst
@@ -1,12 +1,69 @@
libnmap.parser
==============
-Using libnmap.parser module
----------------------------
+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.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
+- 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
+-------------------------
This modules enables you to parse nmap scans' output. For now on, only XML parsing is supported. NmapParser is a factory which will return a NmapReport, NmapHost or NmapService object.
All these objects' API are documented.
-The NmapParser should never be instanciated and only the following methods should be called:
+
+The module is capable of parsing:
+
+- a complete nmap XML scan report
+- an incomplete/interrupted nmap XML scan report
+- partial nmap xml tags: , and
+
+Input the above capabilities could be either a string or a file path.
+
+Based on the provided data, NmapParse.parse() could return the following:
+
+- NmapReport object: in case a full nmap xml/dict report was prodivded
+- NmapHost object: in case a nmap xml section was provided
+- NmapService object: in case a nmap xml section was provided
+- Python dict with following keys: ports and extraports; python lists.
+
+Using libnmap.parser module
+---------------------------
+
+NmapParser parse the whole data and returns nmap objects usable via their documented API.
+
+The NmapParser should never be instantiated and only the following methods should be called:
- NmapParser.parse(string)
- NmapParser.parse_fromfile(file_path)
@@ -19,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
diff --git a/docs/process.rst b/docs/process.rst
index 69a592c..7944dbd 100644
--- a/docs/process.rst
+++ b/docs/process.rst
@@ -1,6 +1,52 @@
libnmap.process
===============
+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 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:
+
+- instantiate NmapProcess
+- call the run*() methods
+
+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 instantiate an NmapProcess instance, call the constructor with the appropriate parameters
+
+Processing of events
+--------------------
+
+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 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 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
+
+You will find below the list of attributes you can use when dealing with NmapTask:
+
+- name: task name (check nmap documentation for the complete list)
+- etc: unix timestamp of estimated time to completion
+- progress: estimated percentage of task completion
+- percent: estimated percentage of task completion (same as progress)
+- remaining: estimated number of seconds to completion
+- status: status of the task ('started' or 'ended')
+- starttime: unix timestamp of when the task started
+- endtime: unix timestamp of when the task ended, 0 if not completed yet
+- extrainfo: extra information stored for specific tasks
+- updated: unix timestamp of last data update for this task
+
Using libnmap.process
---------------------
@@ -67,3 +113,11 @@ NmapProcess methods
.. automodule:: libnmap.process
.. autoclass:: NmapProcess
:members:
+
+ .. automethod:: __init__
+
+NmapTask methods
+-------------------
+
+.. autoclass:: NmapTask
+ :members:
diff --git a/examples/check_cpe.py b/examples/check_cpe.py
new file mode 100644
index 0000000..7c1ad8f
--- /dev/null
+++ b/examples/check_cpe.py
@@ -0,0 +1,39 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+from libnmap.parser import NmapParser
+
+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
+ )
+)
+for _host in rep.hosts:
+ if _host.is_up():
+ 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
+ )
+ )
+ # NmapService.cpelist returns an array of CPE objects
+ for _serv_cpe in s.cpelist:
+ print(" CPE: {0}".format(_serv_cpe.cpestring))
+
+ if _host.os_fingerprinted:
+ print(" OS Fingerprints")
+ for osm in _host.os.osmatches:
+ 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():
+ print("\t CPE: {0}".format(cpe))
diff --git a/examples/diff_sample1.py b/examples/diff_sample1.py
index 3973261..69e3175 100644
--- a/examples/diff_sample1.py
+++ b/examples/diff_sample1.py
@@ -1,28 +1,31 @@
#!/usr/bin/env python
+# -*- coding: utf-8 -*-
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 09cb8c6..0cb711a 100644
--- a/examples/diff_sample2.py
+++ b/examples/diff_sample2.py
@@ -1,4 +1,5 @@
#!/usr/bin/env python
+# -*- coding: utf-8 -*-
from libnmap.parser import NmapParser
@@ -15,43 +16,45 @@ 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)
+ print("+ {0}".format(subobj1))
else:
- print "+ {0} {1}: {2}".format(obj1, akey, getattr(obj1, akey))
+ print("+ {0} {1}: {2}".format(obj1, akey, getattr(obj1, akey)))
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)
+ print("- {0}".format(subobj2))
else:
- print "- {0} {1}: {2}".format(obj2, rkey, getattr(obj2, rkey))
+ print("- {0} {1}: {2}".format(obj2, rkey, getattr(obj2, rkey)))
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):
@@ -63,8 +66,10 @@ 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
new file mode 100644
index 0000000..3be6487
--- /dev/null
+++ b/examples/elastikibana.py
@@ -0,0 +1,113 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+from datetime import datetime
+
+import pygeoip
+from elasticsearch import Elasticsearch
+
+from libnmap.parser import NmapParser
+
+
+def store_report(nmap_report, database, index):
+ rval = True
+ 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)
+ )
+ rval = False
+
+ return rval
+
+
+def get_os(nmap_host):
+ 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()}
+ )
+ return rval
+
+
+def get_geoip_code(address):
+ 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",
+ ]
+ jhost = {}
+ for hkey in host_keys:
+ if hkey == "starttime" or hkey == "endtime":
+ val = getattr(nmap_host, hkey)
+ jhost[hkey] = datetime.fromtimestamp(int(val) if len(val) else 0)
+ else:
+ jhost[hkey] = getattr(nmap_host, hkey)
+
+ 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)
+ return jhost
+
+
+def get_item(nmap_service):
+ service_keys = ["port", "protocol", "state"]
+ ritems = []
+
+ # create report item for basic port scan
+ 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
+ ritems.append(jservice)
+
+ # create report items from nse script output
+ for nse_item in nmap_service.scripts_results:
+ 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"]
+ 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",
+]
+
+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"))
+ db = Elasticsearch()
+ j = store_report(nmap_report, db, index)
diff --git a/examples/es_plugin.py b/examples/es_plugin.py
new file mode 100644
index 0000000..6474399
--- /dev/null
+++ b/examples/es_plugin.py
@@ -0,0 +1,18 @@
+#!/usr/bin/env python
+
+import json
+from datetime import datetime
+
+from libnmap.parser import NmapParser
+from libnmap.plugins.es import NmapElasticsearchPlugin
+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")
+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())
diff --git a/examples/json_serialize.py b/examples/json_serialize.py
index f329bc1..b7d5148 100644
--- a/examples/json_serialize.py
+++ b/examples/json_serialize.py
@@ -1,13 +1,16 @@
+#!/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')
+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)
-print nmap_report_json
+print(nmap_report_json)
# create a NmapReport instance from a json object
nmap_report_obj = json.loads(nmap_report_json, cls=ReportDecoder)
-print nmap_report_obj
-
+print(nmap_report_obj)
diff --git a/examples/kibanalibnmap.png b/examples/kibanalibnmap.png
new file mode 100644
index 0000000..8d5bed4
Binary files /dev/null and b/examples/kibanalibnmap.png differ
diff --git a/examples/nmap_task.py b/examples/nmap_task.py
new file mode 100644
index 0000000..c0988be
--- /dev/null
+++ b/examples/nmap_task.py
@@ -0,0 +1,22 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+from libnmap.process import NmapProcess
+
+
+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
+ )
+ )
+
+
+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
new file mode 100644
index 0000000..30d5c31
--- /dev/null
+++ b/examples/nmap_task_bg.py
@@ -0,0 +1,18 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+from libnmap.process import NmapProcess
+
+nmap_proc = NmapProcess(targets="scanme.nmap.org", options="-sV")
+nmap_proc.run_background()
+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("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
new file mode 100644
index 0000000..718aa87
--- /dev/null
+++ b/examples/os_fingerprint.py
@@ -0,0 +1,22 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+from libnmap.parser import NmapParser
+
+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:
+ if _host.is_up():
+ print("{0} {1}".format(_host.address, " ".join(_host.hostnames)))
+ if _host.os_fingerprinted:
+ print("OS Fingerprint:")
+ msg = ""
+ for osm in _host.os.osmatches:
+ print("Found Match:{0} ({1}%)".format(osm.name, osm.accuracy))
+ for osc in osm.osclasses:
+ print("\tOS Class: {0}".format(osc.description))
+ for cpe in osc.cpelist:
+ print("\tCPE: {0}".format(cpe.cpestring))
+ else:
+ print("No fingerprint available")
diff --git a/examples/proc_async.py b/examples/proc_async.py
index 6732b2b..01c167c 100644
--- a/examples/proc_async.py
+++ b/examples/proc_async.py
@@ -1,14 +1,18 @@
#!/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()
-while nmap_proc.is_alive():
- print "Nmap Scan running: ETC: {0} DONE: {1}%".format(nmap_proc.etc,
- nmap_proc.progress)
+while nmap_proc.is_running():
+ 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)
+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 cfd5e2a..f38a92d 100644
--- a/examples/proc_nmap_like.py
+++ b/examples/proc_nmap_like.py
@@ -1,28 +1,33 @@
#!/usr/bin/env python
-from libnmap.process import NmapProcess
+# -*- coding: utf-8 -*-
+
from libnmap.parser import NmapParser, NmapParserException
+from libnmap.process import NmapProcess
# start a new nmap scan on localhost with some specific options
def do_scan(targets, options):
- nm = NmapProcess(targets, options)
- rc = nm.run()
+ parsed = None
+ nmproc = NmapProcess(targets, options)
+ rc = nmproc.run()
if rc != 0:
- print "nmap scan failed: %s" % (nm.stderr)
+ print("nmap scan failed: {0}".format(nmproc.stderr))
try:
- parsed = NmapParser.parse(nm.stdout)
+ parsed = NmapParser.parse(nmproc.stdout)
except NmapParserException as e:
- print "Exception raised while parsing scan: %s" % (e.msg)
+ print("Exception raised while parsing scan: {0}".format(e.msg))
return parsed
# 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.version, nmap_report.started
+ )
+ )
for host in nmap_report.hosts:
if len(host.hostnames):
@@ -30,24 +35,23 @@ def print_scan(nmap_report):
else:
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"
+ 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
- print nmap_report.summary
+ print(pserv)
+ print(nmap_report.summary)
if __name__ == "__main__":
report = do_scan("127.0.0.1", "-sV")
- print_scan(report)
+ if report:
+ print_scan(report)
+ else:
+ print("No results returned")
diff --git a/libnmap/__init__.py b/libnmap/__init__.py
index b9eaba2..411e762 100644
--- a/libnmap/__init__.py
+++ b/libnmap/__init__.py
@@ -1,6 +1,8 @@
-__author__ = 'Ronald Bister, Mike Boutillier'
-__credits__ = ['Ronald Bister', 'Mike Boutillier']
-__maintainer__ = 'Ronald Bister'
-__email__ = 'mini.pelle@gmail.com'
-__license__ = 'CC-BY'
-__version__ = '0.2'
+# -*- coding: utf-8 -*-
+
+__author__ = "Ronald Bister, Mike Boutillier"
+__credits__ = ["Ronald Bister", "Mike Boutillier"]
+__maintainer__ = "Ronald Bister"
+__email__ = "mini.pelle@gmail.com"
+__license__ = "Apache 2.0"
+__version__ = "0.7.2"
diff --git a/libnmap/diff.py b/libnmap/diff.py
index 2deec7b..8dc649c 100644
--- a/libnmap/diff.py
+++ b/libnmap/diff.py
@@ -1,14 +1,15 @@
-#!/usr/bin/env python
+# -*- coding: utf-8 -*-
class DictDiffer(object):
"""
- Calculate the difference between two dictionaries as:
- (1) items added
- (2) items removed
- (3) keys same in both but changed values
- (4) keys same in both and unchanged values
+ Calculate the difference between two dictionaries as:
+ (1) items added
+ (2) items removed
+ (3) keys same in both but changed values
+ (4) keys same in both and unchanged values
"""
+
def __init__(self, current_dict, past_dict):
self.current_dict = current_dict
self.past_dict = past_dict
@@ -23,47 +24,56 @@ 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):
"""
- 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 comparison.
+ 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__ 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()
@@ -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 756b18d..ddf3699 100644
--- a/libnmap/objects/__init__.py
+++ b/libnmap/objects/__init__.py
@@ -1,5 +1,7 @@
-from libnmap.objects.report import NmapReport
+# -*- coding: utf-8 -*-
+
from libnmap.objects.host import NmapHost
+from libnmap.objects.report import NmapReport
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
new file mode 100644
index 0000000..9476444
--- /dev/null
+++ b/libnmap/objects/cpe.py
@@ -0,0 +1,104 @@
+# -*- coding: utf-8 -*-
+
+
+class CPE(object):
+ """
+ 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
+ """
+
+ 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(":")
+ self._cpedict.update(dict(zip(zk, splitup)))
+
+ @property
+ def cpestring(self):
+ """
+ Accessor for the full CPE string.
+ """
+ return self._cpestring
+
+ @property
+ def cpedict(self):
+ """
+ Accessor for _cpedict
+ """
+ return self._cpedict
+
+ def __repr__(self):
+ return self._cpestring
+
+ def get_part(self):
+ """
+ Returns the cpe part (/o, /h, /a)
+ """
+ return self._cpedict["part"]
+
+ def get_vendor(self):
+ """
+ Returns the vendor name
+ """
+ return self._cpedict["vendor"]
+
+ def get_product(self):
+ """
+ Returns the product name
+ """
+ return self._cpedict["product"]
+
+ def get_version(self):
+ """
+ Returns the version of the cpe
+ """
+ return self._cpedict["version"]
+
+ def get_update(self):
+ """
+ Returns the update version
+ """
+ return self._cpedict["update"]
+
+ def get_edition(self):
+ """
+ Returns the cpe edition
+ """
+ return self._cpedict["edition"]
+
+ def get_language(self):
+ """
+ Returns the cpe language
+ """
+ return self._cpedict["language"]
+
+ def is_application(self):
+ """
+ Returns True if cpe describes an application
+ """
+ return self.get_part() == "/a"
+
+ def is_hardware(self):
+ """
+ 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
+ """
+ return self.get_part() == "/o"
diff --git a/libnmap/objects/host.py b/libnmap/objects/host.py
index e3a244b..dba1653 100644
--- a/libnmap/objects/host.py
+++ b/libnmap/objects/host.py
@@ -1,281 +1,306 @@
-#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
from libnmap.diff import NmapDiff
+from libnmap.objects.os import NmapOSFingerprint
class NmapHost(object):
"""
- NmapHost is a class representing a host object of NmapReport
+ NmapHost is a class representing a host object of NmapReport
"""
- class NmapOSFingerprint(object):
- """
- NmapOSFingerprint is a easier API for using os fingerprinting
- """
- def __init__(self, osfp_data):
- self.__fingerprint = ''
- self.__osmatch = ''
- self.__osclass = ''
- if 'osfingerprint' in osfp_data:
- self.__fingerprint = osfp_data['osfingerprint']
-
- __sortfct = lambda osent: int(osent['accuracy'])
- if 'osmatch' in osfp_data:
- try:
- self.__osmatch = sorted(osfp_data['osmatch'],
- key=__sortfct,
- reverse=True)
- except (KeyError, TypeError):
- self.__osmatch = []
-
- if 'osclass' in osfp_data:
- try:
- self.__osclass = sorted(osfp_data['osclass'],
- key=__sortfct,
- reverse=True)
- except (KeyError, TypeError):
- self.__osclass = []
-
- def osmatch(self, min_accuracy=90):
- os_array = []
- for match_entry in self.__osmatch:
- try:
- if int(match_entry['accuracy']) >= min_accuracy:
- os_array.append(match_entry['name'])
- except (KeyError, TypeError):
- pass
- return os_array
-
- def osclass(self, min_accuracy=90):
- os_array = []
- for osclass_entry in self.__osclass:
- try:
- if int(osclass_entry['accuracy']) >= min_accuracy:
- _relevantkeys = ['type', 'vendor', 'osfamily', 'osgen']
- _ftstr = "|".join([vkey + ": " + osclass_entry[vkey]
- for vkey in osclass_entry
- if vkey in _relevantkeys])
- os_array.append(_ftstr)
- except (KeyError, TypeError):
- pass
- return os_array
-
- def fingerprint(self):
- return self.__fingerprint
-
- def __repr__(self):
- _fmtstr = ''
- if len(self.osmatch()):
- _fmtstr += "OS:\r\n"
- for _osmline in self.osmatch():
- _fmtstr += " {0}\r\n".format(_osmline)
- elif len(self.osclass()):
- _fmtstr += "OS CLASS:\r\n"
- for _oscline in self.osclass():
- _fmtstr += " {0}\r\n".format(_oscline)
- elif len(self.fingerprint):
- _fmtstr += "OS FINGERPRINT:\r\n"
- _fmtstr += " {0}".format(self.fingerprint)
- return _fmtstr
- """
- NmapHost is a class representing a host object of NmapReport
- """
- def __init__(self, starttime='', endtime='', address=None, status=None,
- hostnames=None, services=None, extras=None):
- """
- NmapHost constructor
- :param starttime: unix timestamp of when the scan against
- that host started
- :type starttime: string
- :param endtime: unix timestamp of when the scan against
- that host ended
- :type endtime: string
- :param address: dict ie :{'addr': '127.0.0.1', 'addrtype': 'ipv4'}
- :param status: dict ie:{'reason': 'localhost-response',
- 'state': 'up'}
- :return: NmapHost:
+ def __init__(
+ self,
+ starttime="",
+ endtime="",
+ address=None,
+ status=None,
+ hostnames=None,
+ services=None,
+ extras=None,
+ ):
+ """
+ NmapHost constructor
+ :param starttime: unix timestamp of when the scan against
+ that host started
+ :type starttime: string
+ :param endtime: unix timestamp of when the scan against
+ that host ended
+ :type endtime: string
+ :param address: dict ie :{'addr': '127.0.0.1', 'addrtype': 'ipv4'}
+ :param status: dict ie:{'reason': 'localhost-response',
+ 'state': 'up'}
+ :return: NmapHost:
"""
self._starttime = starttime
self._endtime = endtime
self._hostnames = hostnames if hostnames is not None else []
self._status = status if status is not None else {}
- self._address = address if address 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
- if 'os' in self._extras:
- self.os = self.NmapOSFingerprint(self._extras['os'])
+ self.os = None
+ if "os" in self._extras:
+ self.os = NmapOSFingerprint(self._extras["os"])
self._osfingerprinted = True
+ else:
+ self.os = NmapOSFingerprint({})
+
+ self._ipv4_addr = None
+ self._ipv6_addr = None
+ self._mac_addr = None
+ self._vendor = None
+ for addr in address:
+ if addr["addrtype"] == "ipv4":
+ self._ipv4_addr = addr["addr"]
+ elif addr["addrtype"] == "ipv6":
+ self._ipv6_addr = addr["addr"]
+ elif addr["addrtype"] == "mac":
+ self._mac_addr = addr["addr"]
+ if "vendor" in addr:
+ self._vendor = addr["vendor"]
+
+ self._main_address = self._ipv4_addr or self._ipv6_addr or ""
+ self._address = address
def __eq__(self, other):
"""
- Compare eq NmapHost based on :
+ 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):
- 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):
"""
- 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):
- 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):
"""
- String representing the object
- :return: string
+ 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
+ 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(self._mac_addr)
+ ^ hash(frozenset(self._services))
+ ^ hash(frozenset(" ".join(self._hostnames)))
+ )
def changed(self, other):
"""
- return the number of attribute who have changed
- :param other: NmapHost object to compare
- :return int
+ return 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._address['addr']
+ 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"]
+ elif addrdict["addrtype"] == "ipv6":
+ self._ipv6_addr = addrdict["addr"]
+ elif addrdict["addrtype"] == "mac":
+ self._mac_addr = addrdict["addr"]
+ if "vendor" in addrdict:
+ self._vendor = addrdict["vendor"]
+
+ self._main_address = self._ipv4_addr or self._ipv6_addr or ""
self._address = addrdict
+ @property
+ def ipv4(self):
+ """
+ Accessor for the IPv4 address of the scanned host
+
+ :return: IPv4 address as a string
+ """
+ return self._ipv4_addr or ""
+
+ @property
+ def mac(self):
+ """
+ Accessor for the MAC address of the scanned host
+
+ :return: MAC address as a string
+ """
+ return self._mac_addr or ""
+
+ @property
+ def vendor(self):
+ """
+ Accessor for the vendor attribute of the scanned host
+
+ :return: string (vendor) of empty string if no vendor defined
+ """
+ return self._vendor or ""
+
+ @property
+ def ipv6(self):
+ """
+ Accessor for the IPv6 address of the scanned host
+
+ :return: IPv6 address as a string
+ """
+ return self._ipv6_addr or ""
+
@property
def status(self):
"""
- Accessor for the host's status (up, down, unknown...)
+ Accessor for the host's status (up, down, unknown...)
- :return: string
+ :return: string
"""
- return self._status['state']
+ 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
+
+ :return: bool
+ """
+ rval = False
+ if self.status == "up":
+ rval = True
+ return rval
+
@property
def hostnames(self):
"""
- Accessor returning the list of hostnames (array of strings).
+ 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'])
+ 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')
+ :param portno: int the portnumber
+ :param protocol='tcp': string ('tcp','udp')
- :return: NmapService or None
+ :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
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:
@@ -285,70 +310,60 @@ 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.
- Example [{'accuracy': '96', 'osfamily': 'embedded',
- 'type': 'WAP', 'vendor': 'Netgear'}, {...}]
-
- :return: dict describing the OS class detected and the accuracy
+ :return: Array of NmapOSClass objects
"""
rval = []
- try:
- rval = self._extras['os']['osclass']
- except (KeyError, TypeError):
- pass
+ if self.os is not None:
+ rval = self.os.osclasses
return rval
def os_match_probabilities(self):
"""
- Returns an array of possible OS match detected during
- the OS fingerprinting
+ Returns an array of possible OS match detected during
+ the OS fingerprinting
- :return: dict describing the OS version detected and the accuracy
+ :return: array of NmapOSMatches objects
"""
-
rval = []
- try:
- rval = self._extras['os']['osmatch']
- except (KeyError, TypeError):
- pass
+ if self.os is not None:
+ rval = self.os.osmatches
return rval
@property
def os_fingerprinted(self):
"""
- Specify if the host has OS fingerprint data available
+ 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 = ''
- try:
- rval = self._extras['os']['osfingerprint']
- except (KeyError, TypeError):
- pass
+ rval = ""
+ if self.os is not None:
+ rval = "\n".join(self.os.fingerprints)
return rval
def os_ports_used(self):
"""
- Returns an array of the ports used for OS fingerprinting
+ 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:
- rval = self._extras['ports_used']
+ rval = self._extras["os"]["ports_used"]
except (KeyError, TypeError):
pass
return rval
@@ -356,14 +371,14 @@ 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 = ''
+ rval = ""
try:
- rval = self._extras['tcpsequence']['difficulty']
+ rval = self._extras["tcpsequence"]["difficulty"]
except (KeyError, TypeError):
pass
return rval
@@ -371,13 +386,13 @@ 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 = ''
+ rval = ""
try:
- rval = self._extras['ipidsequence']['class']
+ rval = self._extras["ipidsequence"]["class"]
except (KeyError, TypeError):
pass
return rval
@@ -385,13 +400,13 @@ 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:
- rval = int(self._extras['uptime']['seconds'])
+ rval = int(self._extras["uptime"]["seconds"])
except (KeyError, TypeError):
pass
return rval
@@ -399,13 +414,13 @@ def uptime(self):
@property
def lastboot(self):
"""
- Since when the host was booted.
+ Since when the host was booted.
- :return: string
+ :return: string
"""
- rval = ''
+ rval = ""
try:
- rval = self._extras['uptime']['lastboot']
+ rval = self._extras["uptime"]["lastboot"]
except (KeyError, TypeError):
pass
return rval
@@ -413,13 +428,27 @@ def lastboot(self):
@property
def distance(self):
"""
- Number of hops to host
+ Number of hops to host
- :return: int
+ :return: int
"""
rval = 0
try:
- rval = int(self._extras['distance']['value'])
+ rval = int(self._extras["distance"]["value"])
+ except (KeyError, TypeError):
+ pass
+ return rval
+
+ @property
+ def scripts_results(self):
+ """
+ Scripts results specific to the scanned host
+
+ :return: array of
+
+
+
+
+
+
+cpe:/a:mysql:mysql:5.7.16
+
+
+cpe:/a:memcached:memcached:1.4.25
+
+
+
+
+
+
+cpe:/o:linux:linux_kernel:4
+cpe:/o:linux:linux_kernel:5
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
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:igor_sysoev:nginx
+
+
+
+
+
+
+cpe:/a:mysql:mysql:5.7.16
+
+
+cpe:/a:memcached:memcached:1.4.25
+
+
+
+
+
+
+cpe:/o:linux:linux_kernel:4
+cpe:/o:linux:linux_kernel:5
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/libnmap/test/files/full_sudo5.xml b/libnmap/test/files/full_sudo5.xml
new file mode 100644
index 0000000..4a4730e
--- /dev/null
+++ b/libnmap/test/files/full_sudo5.xml
@@ -0,0 +1,872 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/libnmap/test/files/full_sudo6.xml b/libnmap/test/files/full_sudo6.xml
new file mode 100644
index 0000000..fd461c0
--- /dev/null
+++ b/libnmap/test/files/full_sudo6.xml
@@ -0,0 +1,1010 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+cpe:/o:linux:linux_kernel:2.6.13
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+cpe:/o:microsoft:windows
+cpe:/o:microsoft:windows
+
+
+
+cpe:/o:microsoft:windows_server_2008::beta3
+
+
+cpe:/o:microsoft:windows_7::-:professional
+
+
+cpe:/o:microsoft:windows
+
+
+cpe:/o:microsoft:windows_vista::-cpe:/o:microsoft:windows_vista::sp1
+cpe:/o:microsoft:windows_server_2008::sp1
+cpe:/o:microsoft:windows_7
+
+
+cpe:/o:microsoft:windows_vista::sp2
+cpe:/o:microsoft:windows_7::sp1
+cpe:/o:microsoft:windows_server_2008
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+cpe:/a:heimdal:kerberos
+cpe:/o:microsoft:windows
+cpe:/o:apple:mac_os_x
+
+
+
+cpe:/o:apple:mac_os_x:10.8
+cpe:/o:apple:iphone_os:5
+cpe:/o:apple:iphone_os:5
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/libnmap/test/files/fullscan.xml b/libnmap/test/files/fullscan.xml
new file mode 100644
index 0000000..39e85a6
--- /dev/null
+++ b/libnmap/test/files/fullscan.xml
@@ -0,0 +1,968 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+cpe:/a:openbsd:openssh:5.9p1cpe:/o:linux:linux_kernel
+cpe:/a:apache:http_server:2.2.22
+
+
+
+
+
+
+
+
+
diff --git a/libnmap/test/files/os_scan5.xml b/libnmap/test/files/os_scan5.xml
new file mode 100644
index 0000000..30b787a
--- /dev/null
+++ b/libnmap/test/files/os_scan5.xml
@@ -0,0 +1,864 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/libnmap/test/files/os_scan6.xml b/libnmap/test/files/os_scan6.xml
new file mode 100644
index 0000000..d3c4a9a
--- /dev/null
+++ b/libnmap/test/files/os_scan6.xml
@@ -0,0 +1,878 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+cpe:/o:linux:linux_kernel:2.6.13
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+cpe:/o:microsoft:windows_server_2008::beta3
+
+
+cpe:/o:microsoft:windows_7::-:professional
+
+
+cpe:/o:microsoft:windows
+
+
+cpe:/o:microsoft:windows_vista::-cpe:/o:microsoft:windows_vista::sp1
+cpe:/o:microsoft:windows_server_2008::sp1
+cpe:/o:microsoft:windows_7
+
+
+cpe:/o:microsoft:windows_vista::sp2
+cpe:/o:microsoft:windows_7::sp1
+cpe:/o:microsoft:windows_server_2008
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+cpe:/o:apple:mac_os_x:10.8
+
+
+cpe:/o:apple:mac_os_x:10.8
+cpe:/o:apple:iphone_os:5
+cpe:/o:apple:iphone_os:5
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/libnmap/test/files/test_osclass.xml b/libnmap/test/files/test_osclass.xml
new file mode 100644
index 0000000..68850bb
--- /dev/null
+++ b/libnmap/test/files/test_osclass.xml
@@ -0,0 +1,46 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+cpe:/o:linux:linux_kernel:3
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/libnmap/test/process-stressbox/check_fqp_nmap.py b/libnmap/test/process-stressbox/check_fqp_nmap.py
new file mode 100644
index 0000000..6308c2d
--- /dev/null
+++ b/libnmap/test/process-stressbox/check_fqp_nmap.py
@@ -0,0 +1,63 @@
+#!/usr/bin/env python
+from libnmap.parser import NmapParser, NmapParserException
+from libnmap.process import NmapProcess
+
+
+# start a new nmap scan on localhost with some specific options
+def do_scan(targets, options, fqp=None):
+ parsed = None
+ nm = NmapProcess(targets, options, fqp=fqp)
+ rc = nm.run()
+ if rc != 0:
+ print("nmap scan failed: {0}".format(nm.stderr))
+
+ try:
+ parsed = NmapParser.parse(nm.stdout)
+ except NmapParserException as e:
+ print("Exception raised while parsing scan: {0}".format(e.msg))
+
+ return parsed
+
+
+# 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
+ )
+ )
+
+ for host in nmap_report.hosts:
+ if len(host.hostnames):
+ tmp_host = host.hostnames.pop()
+ else:
+ 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
+ )
+ if len(serv.banner):
+ pserv += " ({0})".format(serv.banner)
+ print(pserv)
+ print(nmap_report.summary)
+
+
+if __name__ == "__main__":
+ report = do_scan("127.0.0.1", "-sT")
+ print_scan(report)
+ # test with full path to bin
+ # /usr/bin/nmap
+ report = do_scan("127.0.0.1", "-sT", fqp="/usr/bin/nmap")
+ print_scan(report)
+ # /usr/bin/lol --> will throw exception
+ try:
+ report = do_scan("127.0.0.1", "-sV", fqp="/usr/bin/lol")
+ print("lolbin")
+ print_scan(report)
+ except Exception as exc:
+ print(exc)
diff --git a/libnmap/test/process-stressbox/multi_nmap_process.py b/libnmap/test/process-stressbox/multi_nmap_process.py
new file mode 100644
index 0000000..d3b138d
--- /dev/null
+++ b/libnmap/test/process-stressbox/multi_nmap_process.py
@@ -0,0 +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")
+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
new file mode 100644
index 0000000..bff25c9
--- /dev/null
+++ b/libnmap/test/process-stressbox/multi_nmap_process_background.py
@@ -0,0 +1,42 @@
+#!/usr/bin/env python
+
+from time import sleep
+
+from libnmap.process import NmapProcess
+
+
+def make_nmproc_obj(targets, options):
+ return NmapProcess(targets=targets, options=options)
+
+
+def start_all_bg(nmprocs):
+ 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} stdout len: {2}".format(
+ nmp.rc, nmp.summary, len(nmp.stdout)
+ )
+ )
+
+
+nm_targets = []
+for h in range(10):
+ 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
new file mode 100644
index 0000000..3aa4a37
--- /dev/null
+++ b/libnmap/test/process-stressbox/proc_async.py
@@ -0,0 +1,20 @@
+#!/usr/bin/env python
+
+from time import sleep
+
+from libnmap.process import NmapProcess
+
+nmap_proc = NmapProcess(targets="scanme.nmap.org", options="-sT")
+nmap_proc.run_background()
+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
+ )
+ )
+ sleep(0.5)
+
+print("rc: {0} output: {1}".format(nmap_proc.rc, nmap_proc.summary))
+print(nmap_proc.stdout)
diff --git a/libnmap/test/process-stressbox/proc_nmap_like.py b/libnmap/test/process-stressbox/proc_nmap_like.py
new file mode 100644
index 0000000..cf5eaae
--- /dev/null
+++ b/libnmap/test/process-stressbox/proc_nmap_like.py
@@ -0,0 +1,51 @@
+#!/usr/bin/env python
+from libnmap.parser import NmapParser, NmapParserException
+from libnmap.process import NmapProcess
+
+
+# start a new nmap scan on localhost with some specific options
+def do_scan(targets, options):
+ nm = NmapProcess(targets, options)
+ rc = nm.run()
+ if rc != 0:
+ print("nmap scan failed: {0}".format(nm.stderr))
+
+ try:
+ parsed = NmapParser.parse(nm.stdout)
+ except NmapParserException as e:
+ print("Exception raised while parsing scan: {0}".format(e.msg))
+
+ return parsed
+
+
+# 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"]
+ )
+ )
+
+ for host in nmap_report.hosts:
+ if len(host.hostnames):
+ tmp_host = host.hostnames.pop()
+ else:
+ 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
+ )
+ if len(serv.banner):
+ pserv += " ({0})".format(serv.banner)
+ print(pserv)
+ print(nmap_report.summary)
+
+
+if __name__ == "__main__":
+ report = do_scan("127.0.0.1", "-sV")
+ print_scan(report)
diff --git a/libnmap/test/process-stressbox/stop_scan.py b/libnmap/test/process-stressbox/stop_scan.py
new file mode 100644
index 0000000..ec9bec8
--- /dev/null
+++ b/libnmap/test/process-stressbox/stop_scan.py
@@ -0,0 +1,22 @@
+#!/usr/bin/env python
+
+from time import sleep
+
+from libnmap.process import NmapProcess
+
+nmap_proc = NmapProcess(targets="scanme.nmap.org", options="-sV")
+nmap_proc.run_background()
+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
+ )
+ )
+ sleep(3)
+ nmap_proc.stop()
+
+print("rc: {0} output: {1}".format(nmap_proc.rc, nmap_proc.summary))
+print(nmap_proc.stdout)
+print(nmap_proc.stderr)
diff --git a/libnmap/test/process-stressbox/stressback.py b/libnmap/test/process-stressbox/stressback.py
new file mode 100644
index 0000000..6f1a05a
--- /dev/null
+++ b/libnmap/test/process-stressbox/stressback.py
@@ -0,0 +1,38 @@
+#!/usr/bin/env python
+
+from time import sleep
+
+from libnmap.process import NmapProcess
+
+
+def make_nmproc_obj(targets, options):
+ return NmapProcess(targets=targets, options=options)
+
+
+def start_all_bg(nmprocs):
+ 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"
+
+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
new file mode 100644
index 0000000..34ca5a8
--- /dev/null
+++ b/libnmap/test/process-stressbox/stresstest.py
@@ -0,0 +1,15 @@
+#!/usr/bin/env python
+from libnmap.parser import NmapParser, NmapParserException
+from libnmap.process import NmapProcess
+
+nm = NmapProcess("127.0.0.1", "-sP")
+rc = nm.run()
+if rc != 0:
+ print("nmap scan failed: {0}".format(nm.stderr))
+
+try:
+ report = NmapParser.parse(nm.stdout)
+except NmapParserException as e:
+ print("Exception raised while parsing scan: {0}".format(e.msg))
+
+print(len(nm.stdout))
diff --git a/libnmap/test/test_backend_plugin_factory.py b/libnmap/test/test_backend_plugin_factory.py
index 3b4d962..9440e94 100644
--- a/libnmap/test/test_backend_plugin_factory.py
+++ b/libnmap/test/test_backend_plugin_factory.py
@@ -1,7 +1,9 @@
#!/usr/bin/env python
+# -*- coding: utf-8 -*-
-import unittest
import os
+import unittest
+
from libnmap.parser import NmapParser
from libnmap.plugins.backendplugin import NmapBackendPlugin
from libnmap.plugins.backendpluginFactory import BackendPluginFactory
@@ -19,102 +21,93 @@ 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': "%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_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
+ # 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':'sql','url':'sqlite://','echo':'debug'},
- {'plugin_name': 'sql',
- 'url': 'sqlite:////tmp/reportdb.sql',
- 'echo': False},
- {'plugin_name': 'sql',
- 'url': 'mysql+mysqldb://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',
- ###},
- ]
+ 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
+ """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()
+ 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
+ """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
+ # create the backend factory object
backend = BackendPluginFactory.create(**url)
- #save the report
+ # save the report
returncode = nrp.save(backend)
- #test return code
+ # 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)
+ """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 = []
@@ -124,8 +117,6 @@ def test_backend_get(self):
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 = []
@@ -135,9 +126,9 @@ 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
+ """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 = []
@@ -152,12 +143,13 @@ def test_backend_delete(self):
result_list = []
-if __name__ == '__main__':
- test_suite = ['test_backend_factory',
- 'test_backend_insert',
- 'test_backend_get',
- 'test_backend_getall',
- 'test_backend_delete'
- ]
+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
new file mode 100644
index 0000000..32a5478
--- /dev/null
+++ b/libnmap/test/test_cpe.py
@@ -0,0 +1,89 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+import unittest
+
+from libnmap.objects.os import CPE
+
+
+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",
+ ]
+
+ def test_cpe(self):
+ apa = CPE(self.cpelist[0])
+
+ self.assertTrue(apa.is_application())
+ self.assertFalse(apa.is_hardware())
+ self.assertFalse(apa.is_operating_system())
+ self.assertEqual(
+ apa.cpedict,
+ {
+ "cpe": "cpe",
+ "edition": "",
+ "language": "",
+ "part": "/a",
+ "product": "http_server",
+ "update": "",
+ "vendor": "apache",
+ "version": "2.2.22",
+ },
+ )
+
+ 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(), "")
+
+ 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",
+ }
+ 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(),
+ }
+ self.assertEqual(objdict, resdict)
+ # self.assertEqual(ocpe.cpedict, resdict)
+ self.assertEqual(str(ocpe), cpestr)
+
+
+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_defusedxml.py b/libnmap/test/test_defusedxml.py
new file mode 100644
index 0000000..5215726
--- /dev/null
+++ b/libnmap/test/test_defusedxml.py
@@ -0,0 +1,63 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+import os
+import sys
+import unittest
+
+from libnmap.parser import NmapParser, NmapParserException
+
+
+class TestDefusedXML(unittest.TestCase):
+ def setUp(self):
+ if int(sys.version[0]) == 3:
+ self._assertRaisesRegex = self.assertRaisesRegex
+ else:
+ self._assertRaisesRegex = self.assertRaisesRegexp
+
+ 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/libnmap/test/test_extraports.py b/libnmap/test/test_extraports.py
new file mode 100644
index 0000000..c52a17a
--- /dev/null
+++ b/libnmap/test/test_extraports.py
@@ -0,0 +1,44 @@
+#!/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__))
+ _extrareasons = [
+ {"reason": "filtered", "count": "3"},
+ {"reason": "resets", "count": "7"},
+ ]
+ self.flist = [
+ {
+ "path": "%s/%s" % (fdir, "files/extra_ports.xml"),
+ "extrareasons": _extrareasons,
+ }
+ ]
+
+ 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]["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__":
+ test_suite = [
+ "test_extraports",
+ ]
+ suite = unittest.TestSuite(map(TestExtraPorts, 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 18b292d..cb599fc 100644
--- a/libnmap/test/test_fp.py
+++ b/libnmap/test/test_fp.py
@@ -1,32 +1,311 @@
#!/usr/bin/env python
+# -*- coding: utf-8 -*-
-import unittest
import os
+import unittest
+
from libnmap.parser import NmapParser
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.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
-if __name__ == '__main__':
- test_suite = ['test_fp']
+ 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",
+ },
+ ],
+ ],
+ ]
+ 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
+ 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,
+ }
+ self.assertEqual(oclines[i][j][k], tdict)
+ k += 1
+ j += 1
+ k = 0
+ j = 0
+ i += 1
+
+ def test_osmatches_new(self):
+ 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
+ for h in hlist:
+ for om in h.os.osmatches:
+ tdict = {
+ "line": om.line,
+ "accuracy": om.accuracy,
+ "name": om.name,
+ }
+ self.assertEqual(baseline[i][j], tdict)
+ j += 1
+ j = 0
+ i += 1
+
+ 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": 90, "name": "WAP:Gemtek:embedded"},
+ {
+ "line": -1,
+ "accuracy": 89,
+ "name": "general purpose:Nokia:Linux",
+ },
+ {"line": -1, "accuracy": 88, "name": "webcam:AXIS:Linux"},
+ ]
+
+ j = 0
+ for om in h1.os.osmatches:
+ tdict = {"line": om.line, "accuracy": om.accuracy, "name": om.name}
+ self.assertEqual(h1osmatches[j], tdict)
+ 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"])
+ 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"])
+ 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"])
+ h1 = rep.hosts.pop()
+ s = h1.services[0]
+ self.assertEqual(s.cpelist[0].cpestring, cpelist[0])
+ self.assertEqual(s.cpelist[1].cpestring, cpelist[1])
+
+ def test_os_class_probabilities(self):
+ p = NmapParser.parse_fromfile(self.fos_class_probabilities)
+ h = p.hosts.pop()
+ osc = h.os_class_probabilities().pop()
+ self.assertEqual(osc.type, "general purpose")
+ self.assertEqual(osc.vendor, "Linux")
+ self.assertEqual(osc.osfamily, "Linux")
+ self.assertEqual(osc.osgen, "3.X")
+ self.assertEqual(osc.accuracy, 100)
+
+ # 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",
+ ]
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 6f2695f..0dff34d 100644
--- a/libnmap/test/test_host.py
+++ b/libnmap/test/test_host.py
@@ -1,6 +1,9 @@
#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+import os
import unittest
+
from libnmap.parser import NmapParser
host1 = """
@@ -109,6 +112,7 @@
"""
+
host4 = """
@@ -147,6 +151,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".format(self.fdir)
+
def test_eq_host(self):
h1 = NmapParser.parse(host1)
h2 = NmapParser.parse(host2)
@@ -163,9 +171,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)
@@ -182,39 +190,65 @@ 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(c2.changed(), set(['status',
- "NmapService::tcp.3306"]))
+ self.assertEqual(
+ c1.unchanged(),
+ set(
+ [
+ "status",
+ "NmapService::tcp.22",
+ "NmapService::tcp.111",
+ "NmapService::tcp.631",
+ "NmapService::tcp.3306",
+ "address",
+ "NmapService::tcp.25",
+ "mac_addr",
+ ]
+ ),
+ )
+
+ 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",
+ "mac_addr",
+ ]
+ ),
+ )
+
+ 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']))
-
-
-if __name__ == '__main__':
- test_suite = ['test_eq_host', 'test_host_api', 'test_diff_host']
+ self.assertEqual(
+ c3.unchanged(),
+ set(
+ [
+ "NmapService::tcp.631",
+ "NmapService::tcp.22",
+ "NmapService::tcp.111",
+ "address",
+ "mac_addr",
+ ]
+ ),
+ )
+
+
+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 f0d3947..4549e2b 100644
--- a/libnmap/test/test_new_parser.py
+++ b/libnmap/test/test_new_parser.py
@@ -1,24 +1,38 @@
#!/usr/bin/env python
+# -*- coding: utf-8 -*-
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")
-
-if __name__ == '__main__':
- test_suite = ['test_parse']
+ 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"]
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 dbde554..b7f2537 100644
--- a/libnmap/test/test_parser.py
+++ b/libnmap/test/test_parser.py
@@ -1,7 +1,9 @@
#!/usr/bin/env python
+# -*- coding: utf-8 -*-
-import unittest
import os
+import unittest
+
from libnmap.parser import NmapParser, NmapParserException
@@ -9,28 +11,32 @@ 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
@@ -91,9 +97,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 = """
@@ -118,9 +121,57 @@
method="probed" conf="10"/>
"""
+port_string_other10 = """
+
+
+
+"""
+
+port_string_other11 = """
+
+
+
+"""
+
+port_string_other12 = """
+
+
+
+"""
+
+port_string_other13 = """
+
+
+
+"""
+
+port_noservice = """
+
+
+"""
+
+port_owner = """
+
+
+
+
+"""
+
+port_tunnel = """
+
+
+
+"""
+
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)
@@ -135,22 +186,25 @@ 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']))
+ self.assertEqual(
+ nservice1.diff(nservice2).unchanged(),
+ set(["banner", "protocol", "port", "service", "id", "reason"]),
+ )
def test_port_service_changed(self):
nservice1 = NmapParser.parse(port_string)
@@ -160,13 +214,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)
@@ -179,16 +230,56 @@ 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']))
+ 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([]))
-if __name__ == '__main__':
- test_suite = ['test_port_state_changed', 'test_port_state_unchanged',
- 'test_port_service_changed', 'test_eq_service',
- 'test_diff_service']
+ 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"]))
+
+ def test_noservice(self):
+ noservice = NmapParser.parse(port_noservice)
+ self.assertEqual(noservice.service, "")
+
+ def test_owner(self):
+ serviceowner = NmapParser.parse(port_owner)
+ self.assertEqual(serviceowner.owner, "edwige")
+
+ 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 = [
+ "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/requirements-dev.txt b/requirements-dev.txt
new file mode 100644
index 0000000..cb383eb
--- /dev/null
+++ b/requirements-dev.txt
@@ -0,0 +1,7 @@
+black==24.3.0
+defusedxml==0.7.1
+isort==6.0.0
+pre-commit
+pytest
+pytest-cov
+flake8
diff --git a/setup.py b/setup.py
index 213a0dc..1ff64c0 100644
--- a/setup.py
+++ b/setup.py
@@ -1,22 +1,49 @@
-from distutils.core import setup
+# -*- coding: utf-8 -*-
+import sys
-with open("README.rst") as rfile:
- long_description = rfile.read()
+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:
+ long_description = rfile.read()
+else: # if encoding not compatible with python2
+ with open("README.rst") as rfile:
+ long_description = rfile.read()
setup(
- name='python-libnmap',
- version='0.4.0',
- author='Ronald Bister',
- author_email='mini.pelle@gmail.com',
- packages=['libnmap', 'libnmap.plugins', 'libnmap.objects'],
- url='http://pypi.python.org/pypi/python-libnmap/',
- license='Creative Common "Attribution" license (CC-BY) v3',
- description=('Python NMAP library enabling you to start async nmap tasks, '
- 'parse and compare/diff scan results'),
+ name="python-libnmap",
+ version="0.7.3",
+ author="Ronald Bister",
+ author_email="mini.pelle@gmail.com",
+ packages=["libnmap", "libnmap.plugins", "libnmap.objects"],
+ url="http://pypi.python.org/pypi/python-libnmap/",
+ 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=["Development Status :: 5 - Production/Stable",
- "Environment :: Console",
- "Programming Language :: Python :: 2.6",
- "Programming Language :: Python :: 2.7",
- "Topic :: System :: Networking"]
+ classifiers=[
+ "License :: OSI Approved :: Apache Software License",
+ "Development Status :: 5 - Production/Stable",
+ "Environment :: Console",
+ "Programming Language :: Python :: 2",
+ "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",
+ "Programming Language :: Python :: 3.9",
+ "Programming Language :: Python :: 3.10",
+ "Topic :: System :: Networking",
+ ],
)
diff --git a/tox.ini b/tox.ini
new file mode 100644
index 0000000..f86d9d3
--- /dev/null
+++ b/tox.ini
@@ -0,0 +1,40 @@
+[tox]
+envlist = py27, py32, py38, flake8, pycodestyle, formatting, defusedxml, coveralls
+
+[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
+
+[testenv:defusedxml]
+deps=pytest
+ defusedxml
+commands=pytest --ignore=libnmap/test/test_backend_plugin_factory.py
+
+[testenv:dbbackend]
+deps=pytest
+ pymongo
+ sqlalchemy
+ pymysql
+commands=pytest --ignore=libnmap/test/test_defusedxml.py
+
+[testenv:flake8]
+deps =
+ flake8
+commands =
+ flake8 --exclude test,docs,examples,.tox .
+
+[testenv:pycodestyle]
+deps =
+ pycodestyle
+commands =
+ pycodestyle --exclude test,docs,examples,.tox .
+
+[testenv:formatting]
+deps =
+ #black==20.8b1
+ black
+ isort
+commands =
+ black --check -l 79 --exclude="venv|.tox" .
+ isort --check-only -m 3 -l 79 --profile=black .