summaryrefslogtreecommitdiffstats
path: root/zenmap/zenmapCore/NmapCommand.py
diff options
context:
space:
mode:
Diffstat (limited to 'zenmap/zenmapCore/NmapCommand.py')
-rw-r--r--zenmap/zenmapCore/NmapCommand.py269
1 files changed, 269 insertions, 0 deletions
diff --git a/zenmap/zenmapCore/NmapCommand.py b/zenmap/zenmapCore/NmapCommand.py
new file mode 100644
index 0000000..d266c0a
--- /dev/null
+++ b/zenmap/zenmapCore/NmapCommand.py
@@ -0,0 +1,269 @@
+#!/usr/bin/env python3
+
+# ***********************IMPORTANT NMAP LICENSE TERMS************************
+# *
+# * The Nmap Security Scanner is (C) 1996-2023 Nmap Software LLC ("The Nmap
+# * Project"). Nmap is also a registered trademark of the Nmap Project.
+# *
+# * This program is distributed under the terms of the Nmap Public Source
+# * License (NPSL). The exact license text applying to a particular Nmap
+# * release or source code control revision is contained in the LICENSE
+# * file distributed with that version of Nmap or source code control
+# * revision. More Nmap copyright/legal information is available from
+# * https://nmap.org/book/man-legal.html, and further information on the
+# * NPSL license itself can be found at https://nmap.org/npsl/ . This
+# * header summarizes some key points from the Nmap license, but is no
+# * substitute for the actual license text.
+# *
+# * Nmap is generally free for end users to download and use themselves,
+# * including commercial use. It is available from https://nmap.org.
+# *
+# * The Nmap license generally prohibits companies from using and
+# * redistributing Nmap in commercial products, but we sell a special Nmap
+# * OEM Edition with a more permissive license and special features for
+# * this purpose. See https://nmap.org/oem/
+# *
+# * If you have received a written Nmap license agreement or contract
+# * stating terms other than these (such as an Nmap OEM license), you may
+# * choose to use and redistribute Nmap under those terms instead.
+# *
+# * The official Nmap Windows builds include the Npcap software
+# * (https://npcap.com) for packet capture and transmission. It is under
+# * separate license terms which forbid redistribution without special
+# * permission. So the official Nmap Windows builds may not be redistributed
+# * without special permission (such as an Nmap OEM license).
+# *
+# * Source is provided to this software because we believe users have a
+# * right to know exactly what a program is going to do before they run it.
+# * This also allows you to audit the software for security holes.
+# *
+# * Source code also allows you to port Nmap to new platforms, fix bugs, and add
+# * new features. You are highly encouraged to submit your changes as a Github PR
+# * or by email to the dev@nmap.org mailing list for possible incorporation into
+# * the main distribution. Unless you specify otherwise, it is understood that
+# * you are offering us very broad rights to use your submissions as described in
+# * the Nmap Public Source License Contributor Agreement. This is important
+# * because we fund the project by selling licenses with various terms, and also
+# * because the inability to relicense code has caused devastating problems for
+# * other Free Software projects (such as KDE and NASM).
+# *
+# * The free version of Nmap 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. Warranties,
+# * indemnification and commercial support are all available through the
+# * Npcap OEM program--see https://nmap.org/oem/
+# *
+# ***************************************************************************/
+
+# This file contains the definitions of the NmapCommand class, which represents
+# and runs an Nmap command line.
+
+import codecs
+import errno
+import locale
+import sys
+import os
+import tempfile
+import unittest
+
+import zenmapCore.I18N # lgtm[py/unused-import]
+
+import subprocess
+
+import zenmapCore.Paths
+from zenmapCore.NmapOptions import NmapOptions
+from zenmapCore.UmitLogging import log
+from zenmapCore.UmitConf import PathsConfig
+from zenmapCore.Name import APP_NAME
+
+# The [paths] configuration from zenmap.conf, used to get nmap_command_path.
+paths_config = PathsConfig()
+
+log.debug(">>> Platform: %s" % sys.platform)
+
+
+def escape_nmap_filename(filename):
+ """Escape '%' characters so they are not interpreted as strftime format
+ specifiers, which are not supported by Zenmap."""
+ return filename.replace("%", "%%")
+
+
+class NmapCommand(object):
+ """This class represents an Nmap command line. It is responsible for
+ starting, stopping, and returning the results from a command-line scan. A
+ command line is represented as a string but it is split into a list of
+ arguments for execution.
+
+ The normal output (stdout and stderr) are written to the file object
+ self.stdout_file."""
+
+ def __init__(self, command):
+ """Initialize an Nmap command. This creates temporary files for
+ redirecting the various types of output and sets the backing
+ command-line string."""
+ self.command = command
+ self.command_process = None
+
+ self.stdout_file = None
+
+ self.ops = NmapOptions()
+ self.ops.parse_string(command)
+ # Replace the executable name with the value of nmap_command_path.
+ self.ops.executable = paths_config.nmap_command_path
+
+ # Normally we generate a random temporary filename to save XML output
+ # to. If we find -oX or -oA, the user has chosen his own output file.
+ # Set self.xml_is_temp to False and don't delete the file when we're
+ # done.
+ self.xml_is_temp = True
+ self.xml_output_filename = None
+ if self.ops["-oX"]:
+ self.xml_is_temp = False
+ self.xml_output_filename = self.ops["-oX"]
+ if self.ops["-oA"]:
+ self.xml_is_temp = False
+ self.xml_output_filename = self.ops["-oA"] + ".xml"
+
+ # Escape '%' to avoid strftime expansion.
+ for op in ("-oA", "-oX", "-oG", "-oN", "-oS"):
+ if self.ops[op]:
+ self.ops[op] = escape_nmap_filename(self.ops[op])
+
+ if self.xml_is_temp:
+ fh, self.xml_output_filename = tempfile.mkstemp(
+ prefix=APP_NAME + "-", suffix=".xml")
+ os.close(fh)
+ self.ops["-oX"] = escape_nmap_filename(self.xml_output_filename)
+
+ log.debug(">>> Temporary files:")
+ log.debug(">>> XML OUTPUT: %s" % self.xml_output_filename)
+
+ def close(self):
+ """Close and remove temporary output files used by the command."""
+ self.stdout_file.close()
+ if self.xml_is_temp:
+ try:
+ os.remove(self.xml_output_filename)
+ except OSError as e:
+ if e.errno != errno.ENOENT:
+ raise
+
+ def kill(self):
+ """Kill the nmap subprocess."""
+ from time import sleep
+
+ log.debug(">>> Killing scan process %s" % self.command_process.pid)
+
+ if sys.platform != "win32":
+ try:
+ from signal import SIGTERM, SIGKILL
+ os.kill(self.command_process.pid, SIGTERM)
+ for i in range(10):
+ sleep(0.5)
+ if self.command_process.poll() is not None:
+ # Process has been TERMinated
+ break
+ else:
+ log.debug(">>> SIGTERM has not worked even after waiting for 5 seconds. Using SIGKILL.") # noqa
+ os.kill(self.command_process.pid, SIGKILL)
+ self.command_process.wait()
+ except Exception:
+ pass
+ else:
+ try:
+ import ctypes
+ ctypes.windll.kernel32.TerminateProcess(
+ int(self.command_process._handle), -1)
+ except Exception:
+ pass
+
+ def get_path(self):
+ """Return a value for the PATH environment variable that is appropriate
+ for the current platform. It will be the PATH from the environment plus
+ possibly some platform-specific directories."""
+ path_env = os.getenv("PATH")
+ if path_env is None:
+ search_paths = []
+ else:
+ search_paths = path_env.split(os.pathsep)
+ for path in zenmapCore.Paths.get_extra_executable_search_paths():
+ if path not in search_paths:
+ search_paths.append(path)
+ return os.pathsep.join(search_paths)
+
+ def run_scan(self, stderr=None):
+ """Run the command represented by this class."""
+
+ # We don't need a file name for stdout output, just a handle. A
+ # TemporaryFile is deleted as soon as it is closed, and in Unix is
+ # unlinked immediately after creation so it's not even visible.
+ f = tempfile.TemporaryFile(mode="r", prefix=APP_NAME + "-stdout-")
+ self.stdout_file = f
+ if stderr is None:
+ stderr = f
+
+ search_paths = self.get_path()
+ env = dict(os.environ)
+ env["PATH"] = search_paths
+ log.debug("PATH=%s" % env["PATH"])
+
+ command_list = self.ops.render()
+ log.debug("Running command: %s" % repr(command_list))
+
+ startupinfo = None
+ if sys.platform == "win32":
+ # This keeps a terminal window from opening.
+ startupinfo = subprocess.STARTUPINFO()
+ try:
+ startupinfo.dwFlags |= \
+ subprocess._subprocess.STARTF_USESHOWWINDOW
+ except AttributeError:
+ # This name is used before Python 2.6.5.
+ startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW
+
+ self.command_process = subprocess.Popen(command_list, bufsize=1,
+ universal_newlines=True,
+ stdin=subprocess.PIPE,
+ stdout=f,
+ stderr=stderr,
+ startupinfo=startupinfo,
+ env=env)
+
+ def scan_state(self):
+ """Return the current state of a running scan. A return value of True
+ means the scan is running and a return value of False means the scan
+ subprocess completed successfully. If the subprocess terminated with an
+ error an exception is raised. The scan must have been started with
+ run_scan before calling this method."""
+ if self.command_process is None:
+ raise Exception("Scan is not running yet!")
+
+ state = self.command_process.poll()
+
+ if state is None:
+ return True # True means that the process is still running
+ elif state == 0:
+ return False # False means that the process had a successful exit
+ else:
+ log.warning("An error occurred during the scan execution!")
+ log.warning("Command that raised the exception: '%s'" %
+ self.ops.render_string())
+ log.warning("Scan output:\n%s" % self.get_output())
+
+ raise Exception(
+ "An error occurred during the scan execution!\n\n'%s'" %
+ self.get_output())
+
+ def get_output(self):
+ """Return the complete contents of the self.stdout_file. This modifies
+ the file pointer."""
+ self.stdout_file.seek(0)
+ return self.stdout_file.read()
+
+ def get_xml_output_filename(self):
+ """Return the name of the XML (-oX) output file."""
+ return self.xml_output_filename
+
+if __name__ == '__main__':
+ unittest.TextTestRunner().run(
+ unittest.TestLoader().loadTestsFromTestCase(SplitQuotedTest))