python-nmap-0.5.0-1/0000755000175000017500000000000012630530632014030 5ustar xaelxael00000000000000python-nmap-0.5.0-1/nmap/0000755000175000017500000000000012630530632014763 5ustar xaelxael00000000000000python-nmap-0.5.0-1/nmap/test_nmap.py0000644000175000017500000002577712623174230017351 0ustar xaelxael00000000000000#!/usr/bin/env python3 # -*- coding: utf-8 -*- import nmap import datetime import os from nose.tools import assert_equals from nose.tools import raises from nose import with_setup from multiprocessing import Value """ test_nmap.py - tests cases for python-nmap Source code : https://bitbucket.org/xael/python-nmap Author : * Alexandre Norman - norman at xael.org Contributors: * Steve 'Ashcrow' Milner - steve at gnulinux.net * Brian Bustin - brian at bustin.us * old.schepperhand * Johan Lundberg * Thomas D. maaaaz * Robert Bost Licence : GPL v3 or any later version This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . """ ########################################################################################## """ This plugin provides ``--pdb`` and ``--pdb-failures`` options. The ``--pdb`` option will drop the test runner into pdb when it encounters an error. To drop into pdb on failure, use ``--pdb-failures``. """ import pdb from nose.plugins.base import Plugin class Pdb(Plugin): """ Provides --pdb and --pdb-failures options that cause the test runner to drop into pdb if it encounters an error or failure, respectively. """ enabled_for_errors = False enabled_for_failures = False score = 5 # run last, among builtins def options(self, parser, env): """Register commandline options. """ parser.add_option( "--pdb", action="store_true", dest="debugBoth", default=env.get('NOSE_PDB', False), help="Drop into debugger on failures or errors") parser.add_option( "--pdb-failures", action="store_true", dest="debugFailures", default=env.get('NOSE_PDB_FAILURES', False), help="Drop into debugger on failures") parser.add_option( "--pdb-errors", action="store_true", dest="debugErrors", default=env.get('NOSE_PDB_ERRORS', False), help="Drop into debugger on errors") def configure(self, options, conf): """Configure which kinds of exceptions trigger plugin. """ self.conf = conf self.enabled_for_errors = options.debugErrors or options.debugBoth self.enabled_for_failures = options.debugFailures or options.debugBoth self.enabled = self.enabled_for_failures or self.enabled_for_errors def addError(self, test, err): """Enter pdb if configured to debug errors. """ if not self.enabled_for_errors: return self.debug(err) def addFailure(self, test, err): """Enter pdb if configured to debug failures. """ if not self.enabled_for_failures: return self.debug(err) def debug(self, err): import sys # FIXME why is this import here? ec, ev, tb = err stdout = sys.stdout sys.stdout = sys.__stdout__ try: pdb.post_mortem(tb) finally: sys.stdout = stdout ########################################################################################## def setup_module(): global nm nm = nmap.PortScanner() @raises(nmap.PortScannerError) def test_wrong_args(): nm.scan(arguments='-wrongargs') def test_host_scan_error(): assert('error' in nm.scan('noserver.example.com', arguments='-sP')['nmap']['scaninfo']) def xmlfile_read_setup(): nm.analyse_nmap_xml_scan(open('scanme_output.xml').read()) @with_setup(xmlfile_read_setup) def test_command_line(): assert_equals(nm.command_line(), './nmap-6.40/nmap -sV -oX scanme_output.xml scanme.nmap.org') @with_setup(xmlfile_read_setup) def test_scan_info(): assert('tcp' in nm.scaninfo()) assert('method' in nm.scaninfo()['tcp']) assert_equals('connect', nm.scaninfo()['tcp']['method']) assert('services' in nm.scaninfo()['tcp']) @with_setup(xmlfile_read_setup) def test_all_hosts(): assert_equals(['74.207.244.221'], nm.all_hosts()) @with_setup(xmlfile_read_setup) def test_host(): assert_equals('scanme.nmap.org', nm['74.207.244.221'].hostname()) assert({'name':'scanme.nmap.org', 'type':'user'} in nm['74.207.244.221'].hostnames()) assert_equals('up', nm['74.207.244.221'].state()) assert_equals(['tcp'], nm['74.207.244.221'].all_protocols()) def test_host_no_hostname(): # Covers bug : https://bitbucket.org/xael/python-nmap/issues/7/error-with-hostname nm.scan('127.0.0.2') assert_equals('', nm['127.0.0.2'].hostname()) @with_setup(xmlfile_read_setup) def test_port(): assert_equals([80, 9929, 22], list(nm['74.207.244.221']['tcp'].keys())) assert(nm['74.207.244.221'].has_tcp(22)) assert(nm['74.207.244.221'].has_tcp(23) == False) assert('conf' in list(nm['74.207.244.221']['tcp'][22])) assert('cpe' in list(nm['74.207.244.221']['tcp'][22])) assert('name' in list(nm['74.207.244.221']['tcp'][22])) assert('product' in list(nm['74.207.244.221']['tcp'][22])) assert('reason' in list(nm['74.207.244.221']['tcp'][22])) assert('state' in list(nm['74.207.244.221']['tcp'][22])) assert('version' in list(nm['74.207.244.221']['tcp'][22])) assert('10' in nm['74.207.244.221']['tcp'][22]['conf']) assert('cpe:/o:linux:linux_kernel' in nm['74.207.244.221']['tcp'][22]['cpe']) assert('ssh' in nm['74.207.244.221']['tcp'][22]['name']) assert('OpenSSH' in nm['74.207.244.221']['tcp'][22]['product']) assert('syn-ack' in nm['74.207.244.221']['tcp'][22]['reason']) assert('open' in nm['74.207.244.221']['tcp'][22]['state']) assert('5.3p1 Debian 3ubuntu7' in nm['74.207.244.221']['tcp'][22]['version']) assert_equals(nm['74.207.244.221']['tcp'][22], nm['74.207.244.221'].tcp(22)) @with_setup(xmlfile_read_setup) def test_listscan(): assert_equals('1', nm.scanstats()['uphosts']) assert_equals('0', nm.scanstats()['downhosts']) assert_equals('1', nm.scanstats()['totalhosts']) assert('timestr' in nm.scanstats().keys()) assert('elapsed' in nm.scanstats().keys()) @with_setup(xmlfile_read_setup) def test_csv_output(): assert_equals('host;protocol;port;name;state;product;extrainfo;reason;version;conf;cpe', nm.csv().split('\n')[0].strip()) assert_equals('74.207.244.221;tcp;22;ssh;open;OpenSSH;"Ubuntu Linux; protocol 2.0";syn-ack;5.3p1 Debian 3ubuntu7;10;cpe:/o:linux:linux_kernel', nm.csv().split('\n')[1].strip()) def test_listscan(): assert(0 < len(nm.listscan('192.168.1.0/30'))) assert_equals(['127.0.0.0', '127.0.0.1', '127.0.0.2', '127.0.0.3'], nm.listscan('localhost/30')) def test_ipv6(): if os.getuid() == 0: r = nm.scan('127.0.0.1', arguments='-6') else: r = nm.scan('127.0.0.1', arguments='-6', sudo=True) def test_ipv4_async(): global FLAG FLAG = Value('i', 0) nma = nmap.PortScannerAsync() def callback_result(host, scan_result): global FLAG FLAG.value = 1 nma.scan(hosts='127.0.0.1', arguments='-p 22 -Pn', callback=callback_result) while nma.still_scanning(): nma.wait(2) assert_equals(FLAG.value, 1) def test_ipv6_async(): global FLAG FLAG = Value('i', 0) nma = nmap.PortScannerAsync() def callback_result(host, scan_result): global FLAG FLAG.value = 1 nma.scan(hosts='::1', arguments='-6 -p 22 -Pn', callback=callback_result) while nma.still_scanning(): nma.wait(2) assert_equals(FLAG.value, 1) def scan_localhost_sudo_arg_O(): lastnm = nm.get_nmap_last_output() if len(lastnm) > 0: try: nm.analyse_nmap_xml_scan(lastnm) except: pass else: if nm.command_line() == 'nmap -oX - -O 127.0.0.1': return if os.getuid() == 0: nm.scan('127.0.0.1', arguments='-O') else : nm.scan('127.0.0.1', arguments='-O', sudo=True) @with_setup(scan_localhost_sudo_arg_O) def test_sudo(): assert('osmatch' in nm['127.0.0.1']) assert(len(nm['127.0.0.1']['osmatch'][0]['osclass']) > 0) assert_equals('Linux', nm['127.0.0.1']['osmatch'][0]['osclass'][0]['vendor']) @with_setup(scan_localhost_sudo_arg_O) def test_parsing_osmap_osclass_and_others(): # nosetests -v -s nmap/test_nmap.py:test_parsing_osmap_osclass_and_others assert('osmatch' in nm['127.0.0.1']) assert_equals(nm['127.0.0.1']['osmatch'][0]['name'], 'Linux 3.7 - 3.15') assert('accuracy' in nm['127.0.0.1']['osmatch'][0]) assert('line' in nm['127.0.0.1']['osmatch'][0]) assert('osclass' in nm['127.0.0.1']['osmatch'][0]) assert_equals(nm['127.0.0.1']['osmatch'][0]['osclass'][0]['vendor'], 'Linux') assert('type' in nm['127.0.0.1']['osmatch'][0]['osclass'][0]) assert('osfamily' in nm['127.0.0.1']['osmatch'][0]['osclass'][0]) assert('osgen' in nm['127.0.0.1']['osmatch'][0]['osclass'][0]) assert('accuracy' in nm['127.0.0.1']['osmatch'][0]['osclass'][0]) @with_setup(scan_localhost_sudo_arg_O) def test_all_protocols(): assert('addresses' not in nm['127.0.0.1'].all_protocols()) assert('hostnames' not in nm['127.0.0.1'].all_protocols()) assert('status' not in nm['127.0.0.1'].all_protocols()) assert('vendor' not in nm['127.0.0.1'].all_protocols()) assert('osclass' not in nm['127.0.0.1'].all_protocols()) assert('osmatch' not in nm['127.0.0.1'].all_protocols()) assert('uptime' not in nm['127.0.0.1'].all_protocols()) assert('portused' not in nm['127.0.0.1'].all_protocols()) assert('tcp' in nm['127.0.0.1'].all_protocols()) def xmlfile_read_setup_multiple_osmatch(): nm.analyse_nmap_xml_scan(open('osmatch_output.xml').read()) @with_setup(xmlfile_read_setup_multiple_osmatch) def test_multipe_osmatch(): assert('osmatch' in nm['127.0.0.1']) assert('portused' in nm['127.0.0.1']) for osm in nm['127.0.0.1']['osmatch']: assert('accuracy' in osm) assert('line' in osm) assert('name' in osm) assert('osclass' in osm) assert('accuracy' in osm['osclass'][0]) assert('cpe' in osm['osclass'][0]) assert('osfamily' in osm['osclass'][0]) assert('osgen' in osm['osclass'][0]) assert('type' in osm['osclass'][0]) assert('vendor' in osm['osclass'][0]) # def test_host_and_port_as_unicode(): # # nosetests -x -s nmap/test_nmap.py:test_port_as_unicode # # Covers bug : https://bitbucket.org/xael/python-nmap/issues/9/can-not-pass-ports-with-unicode-string-at # nma = nm.scan(hosts=u'127.0.0.1', ports=u'22') # assert_equals(nma['nmap']['scaninfo']['error'], '') python-nmap-0.5.0-1/nmap/nmap.py0000755000175000017500000011517212630415621016302 0ustar xaelxael00000000000000#!/usr/bin/env python # -*- coding: utf-8 -*- """ nmap.py - version and date, see below Source code : https://bitbucket.org/xael/python-nmap Author : * Alexandre Norman - norman at xael.org Contributors: * Steve 'Ashcrow' Milner - steve at gnulinux.net * Brian Bustin - brian at bustin.us * old.schepperhand * Johan Lundberg * Thomas D. maaaaz * Robert Bost * David Peltier Licence : GPL v3 or any later version This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . """ __author__ = 'Alexandre Norman (norman@xael.org)' __version__ = '0.5.0-1' __last_modification__ = '2015.12.05' import collections import csv import io import os import re import shlex import string import subprocess import sys import types from xml.etree import ElementTree as ET try: from multiprocessing import Process except ImportError: # For pre 2.6 releases from threading import Thread as Process ############################################################################ class PortScanner(object): """ PortScanner class allows to use nmap from python """ def __init__(self, nmap_search_path=('nmap','/usr/bin/nmap','/usr/local/bin/nmap','/sw/bin/nmap','/opt/local/bin/nmap') ): """ Initialize PortScanner module * detects nmap on the system and nmap version * may raise PortScannerError exception if nmap is not found in the path :param nmap_search_path: tupple of string where to search for nmap executable. Change this if you want to use a specific version of nmap. :returns: nothing """ self._nmap_path = '' # nmap path self._scan_result = {} self._nmap_version_number = 0 # nmap version number self._nmap_subversion_number = 0 # nmap subversion number self._nmap_last_output = '' # last full ascii nmap output is_nmap_found = False # true if we have found nmap self.__process = None # regex used to detect nmap (http or https) regex = re.compile('Nmap version [0-9]*\.[0-9]*[^ ]* \( http(|s)://.* \)') # launch 'nmap -V', we wait after 'Nmap version 5.0 ( http://nmap.org )' # This is for Mac OSX. When idle3 is launched from the finder, PATH is not set so nmap was not found for nmap_path in nmap_search_path: try: if sys.platform.startswith('freebsd') or sys.platform.startswith('linux') or sys.platform.startswith('darwin'): p = subprocess.Popen([nmap_path, '-V'], bufsize=10000, stdout=subprocess.PIPE, close_fds=True) else: p = subprocess.Popen([nmap_path, '-V'], bufsize=10000, stdout=subprocess.PIPE) except OSError: pass else: self._nmap_path = nmap_path # save path break else: raise PortScannerError('nmap program was not found in path. PATH is : {0}'.format(os.getenv('PATH'))) self._nmap_last_output = bytes.decode(p.communicate()[0]) # store stdout for line in self._nmap_last_output.split(os.linesep): if regex.match(line) is not None: is_nmap_found = True # Search for version number regex_version = re.compile('[0-9]+') regex_subversion = re.compile('\.[0-9]+') rv = regex_version.search(line) rsv = regex_subversion.search(line) if rv is not None and rsv is not None: # extract version/subversion self._nmap_version_number = int(line[rv.start():rv.end()]) self._nmap_subversion_number = int(line[rsv.start()+1:rsv.end()]) break if is_nmap_found == False: raise PortScannerError('nmap program was not found in path') return def get_nmap_last_output(self): """ Returns the last text output of nmap in raw text this may be used for debugging purpose :returns: string containing the last text output of nmap in raw text """ return self._nmap_last_output def nmap_version(self): """ returns nmap version if detected (int version, int subversion) or (0, 0) if unknown :returns: (nmap_version_number, nmap_subversion_number) """ return (self._nmap_version_number, self._nmap_subversion_number) def listscan(self, hosts='127.0.0.1'): """ do not scan but interpret target hosts and return a list a hosts """ assert type(hosts) is str, 'Wrong type for [hosts], should be a string [was {0}]'.format(type(hosts)) output = self.scan(hosts, arguments='-sL') # Test if host was IPV6 try: if 'looks like an IPv6 target specification' in output['nmap']['scaninfo']['error'][0]: self.scan(hosts, arguments='-sL -6') except KeyError: pass return self.all_hosts() def scan(self, hosts='127.0.0.1', ports=None, arguments='-sV', sudo=False): """ Scan given hosts May raise PortScannerError exception if nmap output was not xml Test existance of the following key to know if something went wrong : ['nmap']['scaninfo']['error'] If not present, everything was ok. :param hosts: string for hosts as nmap use it 'scanme.nmap.org' or '198.116.0-255.1-127' or '216.163.128.20/20' :param ports: string for ports as nmap use it '22,53,110,143-4564' :param arguments: string of arguments for nmap '-sU -sX -sC' :param sudo: launch nmap with sudo if True :returns: scan_result as dictionnary """ if sys.version_info[0]==2: assert type(hosts) in (str, unicode), 'Wrong type for [hosts], should be a string [was {0}]'.format(type(hosts)) assert type(ports) in (str, unicode, type(None)), 'Wrong type for [ports], should be a string [was {0}]'.format(type(ports)) assert type(arguments) in (str, unicode), 'Wrong type for [arguments], should be a string [was {0}]'.format(type(arguments)) else: assert type(hosts) is str, 'Wrong type for [hosts], should be a string [was {0}]'.format(type(hosts)) assert type(ports) in (str, type(None)), 'Wrong type for [ports], should be a string [was {0}]'.format(type(ports)) assert type(arguments) is str, 'Wrong type for [arguments], should be a string [was {0}]'.format(type(arguments)) for redirecting_output in ['-oX', '-oA']: assert not redirecting_output in arguments, 'Xml output can\'t be redirected from command line.\nYou can access it after a scan using:\nnmap.nm.get_nmap_last_output()' h_args = shlex.split(hosts) f_args = shlex.split(arguments) # Launch scan args = [self._nmap_path, '-oX', '-'] + h_args + ['-p', ports]*(ports!=None) + f_args if sudo: args = ['sudo'] + args p = subprocess.Popen(args, bufsize=100000, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE) # wait until finished # get output (self._nmap_last_output, nmap_err) = p.communicate() self._nmap_last_output = bytes.decode(self._nmap_last_output) nmap_err = bytes.decode(nmap_err) # If there was something on stderr, there was a problem so abort... in # fact not always. As stated by AlenLPeacock : # This actually makes python-nmap mostly unusable on most real-life # networks -- a particular subnet might have dozens of scannable hosts, # but if a single one is unreachable or unroutable during the scan, # nmap.scan() returns nothing. This behavior also diverges significantly # from commandline nmap, which simply stderrs individual problems but # keeps on trucking. nmap_err_keep_trace = [] if len(nmap_err) > 0: regex_warning = re.compile('^Warning: .*') for line in nmap_err.split(os.linesep): if len(line) > 0: rgw = regex_warning.search(line) if rgw is not None: sys.stderr.write(line+os.linesep) pass else: #raise PortScannerError(nmap_err) nmap_err_keep_trace.append(nmap_err) return self.analyse_nmap_xml_scan(nmap_xml_output = self._nmap_last_output, nmap_err = nmap_err, nmap_err_keep_trace = nmap_err_keep_trace) def analyse_nmap_xml_scan(self, nmap_xml_output=None, nmap_err='', nmap_err_keep_trace=''): """ Analyses NMAP xml scan ouput May raise PortScannerError exception if nmap output was not xml Test existance of the following key to know if something went wrong : ['nmap']['scaninfo']['error'] If not present, everything was ok. :param nmap_xml_output: xml string to analyse :returns: scan_result as dictionnary """ # nmap xml output looks like : # # #
# # # # # # # # # # # # #