diff --git a/nmap/__init__.py b/nmap/__init__.py new file mode 100644 index 0000000..d15a222 --- /dev/null +++ b/nmap/__init__.py @@ -0,0 +1,44 @@ +# -*- coding: latin-1 -*- + +""" +python-nmap - 2010.12.17 + +python-nmap is a python library which helps in using nmap port scanner. +It allows to easilly manipulate nmap scan results and will be a perfect +tool for systems administrators who want to automatize scanning task +and reports. It also supports nmap script outputs. + + +Author : + +* Alexandre Norman - norman@xael.org + +Contributors: + +* Steve 'Ashcrow' Milner - steve@gnulinux.net +* Brian Bustin - brian at bustin.us +* old.schepperhand +* Johan Lundberg +* Thomas D. maaaaz + +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 . +""" + +from .nmap import * +from .nmap import __author__ +from .nmap import __version__ +from .nmap import __last_modification__ diff --git a/nmap/nmap.py b/nmap/nmap.py new file mode 100755 index 0000000..9f72b7e --- /dev/null +++ b/nmap/nmap.py @@ -0,0 +1,1075 @@ +#!/usr/bin/env python3 +# -*- 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 + +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.4.3' +__last_modification__ = '2015.09.11' + + +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)) + + self.scan(hosts, arguments='-sL') + 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)) + 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 : + # + # + #
+ # + # + # + # + # + # + # + # + # + # + # + # + #