summaryrefslogtreecommitdiffstats
path: root/zenmap/setup.py
diff options
context:
space:
mode:
Diffstat (limited to 'zenmap/setup.py')
-rwxr-xr-xzenmap/setup.py585
1 files changed, 585 insertions, 0 deletions
diff --git a/zenmap/setup.py b/zenmap/setup.py
new file mode 100755
index 0000000..97e9ef6
--- /dev/null
+++ b/zenmap/setup.py
@@ -0,0 +1,585 @@
+#!/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/
+# *
+# ***************************************************************************/
+import sys
+
+if sys.version_info[0] != 3:
+ sys.exit("Sorry, Zenmap requires Python 3")
+
+import errno
+import os
+import os.path
+import re
+
+import distutils.sysconfig
+from distutils import log
+from distutils.core import setup, Command
+from distutils.command.install import install
+
+from glob import glob
+from stat import S_IRGRP, S_IROTH, S_IRUSR, S_IRWXU, S_IWUSR, S_IXGRP, S_IXOTH, ST_MODE
+
+from zenmapCore.Version import VERSION
+from zenmapCore.Name import APP_NAME, APP_DISPLAY_NAME, APP_WEB_SITE,\
+ APP_DOWNLOAD_SITE, NMAP_DISPLAY_NAME
+
+# The name of the file used to record the list of installed files, so that the
+# uninstall command can remove them.
+INSTALLED_FILES_NAME = "INSTALLED_FILES"
+
+# Directories for POSIX operating systems
+# These are created after a "install" or "py2exe" command
+# These directories are relative to the installation or dist directory
+data_dir = os.path.join('share', APP_NAME)
+pixmaps_dir = os.path.join(data_dir, 'pixmaps')
+locale_dir = os.path.join(data_dir, 'locale')
+config_dir = os.path.join(data_dir, 'config')
+docs_dir = os.path.join(data_dir, 'docs')
+misc_dir = os.path.join(data_dir, 'misc')
+
+# Where to install .desktop files.
+desktop_dir = os.path.join('share', 'applications')
+
+
+def mo_find(result, dirname, fnames):
+ files = []
+ for f in fnames:
+ p = os.path.join(dirname, f)
+ if os.path.isfile(p) and f.endswith(".mo"):
+ files.append(p)
+
+ if files:
+ result.append((dirname, files))
+
+###############################################################################
+# Installation variables
+
+data_files = [
+ (pixmaps_dir, glob(os.path.join(pixmaps_dir, '*.gif')) +
+ glob(os.path.join(pixmaps_dir, '*.png'))),
+
+ (os.path.join(pixmaps_dir, "radialnet"),
+ glob(os.path.join(pixmaps_dir, "radialnet", '*.png'))),
+
+ (config_dir, [os.path.join(config_dir, APP_NAME + '.conf'),
+ os.path.join(config_dir, 'scan_profile.usp'),
+ os.path.join(config_dir, APP_NAME + '_version')]),
+
+ (misc_dir, glob(os.path.join(misc_dir, '*.xml'))),
+
+ (docs_dir, [os.path.join(docs_dir, 'help.html')])
+ ]
+
+# Add i18n files to data_files list
+os.walk(locale_dir, mo_find, data_files)
+
+
+# path_startswith and path_strip_prefix are used to deal with the installation
+# root (--root option, also known as DESTDIR).
+def path_startswith(path, prefix):
+ """Returns True if path starts with prefix. It's a little more intelligent
+ than str.startswith because it normalizes the paths to remove multiple
+ directory separators and down-up traversals."""
+ path = os.path.normpath(path)
+ prefix = os.path.normpath(prefix)
+ return path.startswith(prefix)
+
+
+def path_strip_prefix(path, prefix):
+ """Return path stripped of its directory prefix if it starts with prefix,
+ otherwise return path unmodified. This only works correctly with Unix
+ paths; for example it will not replace the drive letter on a Windows path.
+ Examples:
+ >>> path_strip_prefix('/tmp/destdir/usr/bin', '/tmp/destdir')
+ '/usr/bin'
+ >>> path_strip_prefix('/tmp/../tmp/destdir/usr/bin', '/tmp///destdir')
+ '/usr/bin'
+ >>> path_strip_prefix('/etc', '/tmp/destdir')
+ '/etc'
+ >>> path_strip_prefix('/etc', '/')
+ '/etc'
+ >>> path_strip_prefix('/etc', '')
+ '/etc'
+ """
+ absolute = os.path.isabs(path)
+ path = os.path.normpath(path)
+ prefix = os.path.normpath(prefix)
+ if path.startswith(prefix) and prefix != os.sep:
+ path = path[len(prefix):]
+ # Absolute paths must remain absolute and relative paths must remain
+ # relative.
+ assert os.path.isabs(path) == absolute
+ return path
+
+###############################################################################
+# Distutils subclasses
+
+
+class my_install(install):
+ def finalize_options(self):
+ # Ubuntu's python2.6-2.6.4-0ubuntu3 package changes sys.prefix in
+ # install.finalize_options when sys.prefix is "/usr/local" (our
+ # default). Because we need the unchanged value later, remember it
+ # here.
+ self.saved_prefix = self.prefix
+ install.finalize_options(self)
+
+ def run(self):
+ install.run(self)
+
+ self.set_perms()
+ self.set_modules_path()
+ self.fix_paths()
+ self.create_uninstaller()
+ self.write_installed_files()
+
+ def get_installed_files(self):
+ """Return a list of installed files and directories, each prefixed with
+ the installation root if given. The list of installed directories
+ doesn't come from distutils so it may be incomplete."""
+ installed_files = self.get_outputs()
+ for package in self.distribution.packages:
+ dir = package.replace(".", "/")
+ installed_files.append(os.path.join(self.install_lib, dir))
+ # Recursively include all the directories in data_dir (share/zenmap).
+ # This is mainly for convenience in listing locale directories.
+ installed_files.append(os.path.join(self.install_data, data_dir))
+ for dirpath, dirs, files in os.walk(
+ os.path.join(self.install_data, data_dir)):
+ for dir in dirs:
+ installed_files.append(os.path.join(dirpath, dir))
+ installed_files.append(
+ os.path.join(self.install_scripts, "uninstall_" + APP_NAME))
+ return installed_files
+
+ def create_uninstaller(self):
+ uninstaller_filename = os.path.join(
+ self.install_scripts, "uninstall_" + APP_NAME)
+
+ uninstaller = """\
+#!/usr/bin/env python3
+import errno, os, os.path, sys
+
+print('Uninstall %(name)s %(version)s')
+
+answer = raw_input('Are you sure that you want to uninstall '
+ '%(name)s %(version)s? (yes/no) ')
+
+if answer != 'yes' and answer != 'y':
+ print('Not uninstalling.')
+ sys.exit(0)
+
+""" % {'name': APP_DISPLAY_NAME, 'version': VERSION}
+
+ installed_files = []
+ for output in self.get_installed_files():
+ if self.root is not None:
+ # If we have a root (DESTDIR), we need to strip it off the
+ # front of paths so the uninstaller runs on the target host.
+ # The path manipulations are tricky, but made easier because
+ # the uninstaller only has to run on Unix.
+ if not path_startswith(output, self.root):
+ # This should never happen (everything gets installed
+ # inside the root), but if it does, be safe and don't
+ # delete anything.
+ uninstaller += ("print('%s was not installed inside "
+ "the root %s; skipping.')\n" % (output, self.root))
+ continue
+ output = path_strip_prefix(output, self.root)
+ assert os.path.isabs(output)
+ installed_files.append(output)
+
+ uninstaller += """\
+INSTALLED_FILES = (
+"""
+ for file in installed_files:
+ uninstaller += " %s,\n" % repr(file)
+ uninstaller += """\
+)
+
+# Split the list into lists of files and directories.
+files = []
+dirs = []
+for path in INSTALLED_FILES:
+ if os.path.isfile(path) or os.path.islink(path):
+ files.append(path)
+ elif os.path.isdir(path):
+ dirs.append(path)
+# Delete the files.
+for file in files:
+ print("Removing '%s'." % file)
+ try:
+ os.remove(file)
+ except OSError as e:
+ print(' Error: %s.' % str(e), file=sys.stderr)
+# Delete the directories. First reverse-sort the normalized paths by
+# length so that child directories are deleted before their parents.
+dirs = [os.path.normpath(dir) for dir in dirs]
+dirs.sort(key = len, reverse = True)
+for dir in dirs:
+ try:
+ print("Removing the directory '%s'." % dir)
+ os.rmdir(dir)
+ except OSError as e:
+ if e.errno == errno.ENOTEMPTY:
+ print("Directory '%s' not empty; not removing." % dir)
+ else:
+ print(str(e), file=sys.stderr)
+"""
+
+ uninstaller_file = open(uninstaller_filename, 'w')
+ uninstaller_file.write(uninstaller)
+ uninstaller_file.close()
+
+ # Set exec bit for uninstaller
+ mode = ((os.stat(uninstaller_filename)[ST_MODE]) | 0o555) & 0o7777
+ os.chmod(uninstaller_filename, mode)
+
+ def set_modules_path(self):
+ app_file_name = os.path.join(self.install_scripts, APP_NAME)
+ # Find where the modules are installed. distutils will put them in
+ # self.install_lib, but that path can contain the root (DESTDIR), so we
+ # must strip it off if necessary.
+ modules_dir = self.install_lib
+ if self.root is not None:
+ modules_dir = path_strip_prefix(modules_dir, self.root)
+
+ app_file = open(app_file_name, "r")
+ lines = app_file.readlines()
+ app_file.close()
+
+ for i in range(len(lines)):
+ if re.match(r'^INSTALL_LIB =', lines[i]):
+ lines[i] = "INSTALL_LIB = %s\n" % repr(modules_dir)
+ break
+ else:
+ raise ValueError(
+ "INSTALL_LIB replacement not found in %s" % app_file_name)
+
+ app_file = open(app_file_name, "w")
+ app_file.writelines(lines)
+ app_file.close()
+
+ def set_perms(self):
+ re_bin = re.compile("(bin|\.sh)")
+ for output in self.get_installed_files():
+ if re_bin.findall(output):
+ continue
+
+ if os.path.isdir(output):
+ os.chmod(output, S_IRWXU |
+ S_IRGRP |
+ S_IXGRP |
+ S_IROTH |
+ S_IXOTH)
+ else:
+ os.chmod(output, S_IRUSR |
+ S_IWUSR |
+ S_IRGRP |
+ S_IROTH)
+
+ def fix_paths(self):
+ """Replace some hardcoded paths to match where files were installed."""
+ interesting_paths = {
+ "CONFIG_DIR": os.path.join(self.saved_prefix, config_dir),
+ "DOCS_DIR": os.path.join(self.saved_prefix, docs_dir),
+ "LOCALE_DIR": os.path.join(self.saved_prefix, locale_dir),
+ "MISC_DIR": os.path.join(self.saved_prefix, misc_dir),
+ "PIXMAPS_DIR": os.path.join(self.saved_prefix, pixmaps_dir),
+ # See $(nmapdatadir) in nmap/Makefile.in.
+ "NMAPDATADIR": os.path.join(self.saved_prefix, "share", "nmap")
+ }
+
+ # Find and read the Paths.py file.
+ pcontent = ""
+ paths_file = os.path.join("zenmapCore", "Paths.py")
+ installed_files = self.get_outputs()
+ for f in installed_files:
+ if re.findall("(%s)" % re.escape(paths_file), f):
+ paths_file = f
+ pf = open(paths_file)
+ pcontent = pf.read()
+ pf.close()
+ break
+
+ # Replace the path definitions.
+ for path, replacement in interesting_paths.items():
+ pcontent = re.sub("%s\s+=\s+.+" % path,
+ "%s = %s" % (path, repr(replacement)),
+ pcontent)
+
+ # Write the modified file.
+ pf = open(paths_file, "w")
+ pf.write(pcontent)
+ pf.close()
+
+ # Rewrite the zenmap.desktop and zenmap-root.desktop files to point to
+ # the installed locations of the su-to-zenmap.sh script and application
+ # icon.
+ su_filename = os.path.join(
+ self.saved_prefix, data_dir, "su-to-zenmap.sh")
+ icon_filename = os.path.join(
+ self.saved_prefix, pixmaps_dir, "zenmap.png")
+
+ desktop_filename = None
+ root_desktop_filename = None
+ for f in installed_files:
+ if re.search("%s$" % re.escape("zenmap-root.desktop"), f):
+ root_desktop_filename = f
+ elif re.search("%s$" % re.escape("zenmap.desktop"), f):
+ desktop_filename = f
+
+ if desktop_filename is not None:
+ df = open(desktop_filename, "r")
+ dcontent = df.read()
+ df.close()
+ regex = re.compile("^(Icon *= *).*$", re.MULTILINE)
+ dcontent = regex.sub("\\1%s" % icon_filename, dcontent)
+ df = open(desktop_filename, "w")
+ df.write(dcontent)
+ df.close()
+
+ if root_desktop_filename is not None:
+ df = open(root_desktop_filename, "r")
+ dcontent = df.read()
+ df.close()
+ regex = re.compile(
+ "^((?:Exec|TryExec) *= *).*su-to-zenmap.sh(.*)$",
+ re.MULTILINE)
+ dcontent = regex.sub("\\1%s\\2" % su_filename, dcontent)
+ regex = re.compile("^(Icon *= *).*$", re.MULTILINE)
+ dcontent = regex.sub("\\1%s" % icon_filename, dcontent)
+ df = open(root_desktop_filename, "w")
+ df.write(dcontent)
+ df.close()
+
+ def write_installed_files(self):
+ """Write a list of installed files for use by the uninstall command.
+ This is similar to what happens with the --record option except that it
+ doesn't strip off the installation root, if any. File names containing
+ newline characters are not handled."""
+ if INSTALLED_FILES_NAME == self.record:
+ distutils.log.warn("warning: installation record is overwriting "
+ "--record file '%s'." % self.record)
+ with open(INSTALLED_FILES_NAME, "w") as f:
+ for output in self.get_installed_files():
+ assert "\n" not in output
+ print(output, file=f)
+
+
+class my_uninstall(Command):
+ """A distutils command that performs uninstallation. It reads the list of
+ installed files written by the install command."""
+
+ command_name = "uninstall"
+ description = "uninstall installed files recorded in '%s'" % (
+ INSTALLED_FILES_NAME)
+ user_options = []
+
+ def initialize_options(self):
+ pass
+
+ def finalize_options(self):
+ pass
+
+ def run(self):
+ # Read the list of installed files.
+ try:
+ f = open(INSTALLED_FILES_NAME, "r")
+ except IOError as e:
+ if e.errno == errno.ENOENT:
+ log.error("Couldn't open the installation record '%s'. "
+ "Have you installed yet?" % INSTALLED_FILES_NAME)
+ return
+ installed_files = [file.rstrip("\n") for file in f.readlines()]
+ f.close()
+ # Delete the installation record too.
+ installed_files.append(INSTALLED_FILES_NAME)
+ # Split the list into lists of files and directories.
+ files = []
+ dirs = []
+ for path in installed_files:
+ if os.path.isfile(path) or os.path.islink(path):
+ files.append(path)
+ elif os.path.isdir(path):
+ dirs.append(path)
+ # Delete the files.
+ for file in files:
+ log.info("Removing '%s'." % file)
+ try:
+ if not self.dry_run:
+ os.remove(file)
+ except OSError as e:
+ log.error(str(e))
+ # Delete the directories. First reverse-sort the normalized paths by
+ # length so that child directories are deleted before their parents.
+ dirs = [os.path.normpath(dir) for dir in dirs]
+ dirs.sort(key=len, reverse=True)
+ for dir in dirs:
+ try:
+ log.info("Removing the directory '%s'." % dir)
+ if not self.dry_run:
+ os.rmdir(dir)
+ except OSError as e:
+ if e.errno == errno.ENOTEMPTY:
+ log.info("Directory '%s' not empty; not removing." % dir)
+ else:
+ log.error(str(e))
+
+# setup can be called in different ways depending on what we're doing. (For
+# example py2exe needs special handling.) These arguments are common between
+# all the operations.
+COMMON_SETUP_ARGS = {
+ 'name': APP_NAME,
+ 'license': 'Nmap License (https://nmap.org/book/man-legal.html)',
+ 'url': APP_WEB_SITE,
+ 'download_url': APP_DOWNLOAD_SITE,
+ 'author': 'Nmap Project',
+ 'maintainer': 'Nmap Project',
+ 'description': "%s frontend and results viewer" % NMAP_DISPLAY_NAME,
+ 'long_description': "%s is an %s frontend that is really useful"
+ "for advanced users and easy to be used by newbies." % (
+ APP_DISPLAY_NAME, NMAP_DISPLAY_NAME),
+ 'version': VERSION,
+ 'scripts': [APP_NAME],
+ 'packages': ['zenmapCore', 'zenmapGUI', 'zenmapGUI.higwidgets',
+ 'radialnet', 'radialnet.bestwidgets', 'radialnet.core',
+ 'radialnet.gui', 'radialnet.util'],
+ 'data_files': data_files,
+}
+
+# All of the arguments to setup are collected in setup_args.
+setup_args = {}
+setup_args.update(COMMON_SETUP_ARGS)
+
+if 'py2exe' in sys.argv:
+ # Windows- and py2exe-specific args.
+ import py2exe
+
+ WINDOWS_SETUP_ARGS = {
+ 'zipfile': 'py2exe/library.zip',
+ 'name': APP_NAME,
+ 'windows': [{
+ "script": APP_NAME,
+ "icon_resources": [(1, "install_scripts/windows/nmap-eye.ico")]
+ }],
+ # On Windows we build Ndiff here in Zenmap's setup.py so the two Python
+ # programs will share a common runtime.
+ 'py_modules': ["ndiff"],
+ # override the package search path to let Ndiff be found
+ 'package_dir': {
+ 'zenmapCore': 'zenmapCore',
+ 'zenmapGUI': 'zenmapGUI',
+ 'radialnet': 'radialnet',
+ '': '../ndiff'
+ },
+ 'console': [{
+ "script": "../ndiff/scripts/ndiff",
+ "description": "Nmap scan comparison tool"
+ }],
+ 'options': {"py2exe": {
+ "compressed": 1,
+ "optimize": 2,
+ "packages": ["encodings"],
+ "includes": ["pango", "atk", "gobject", "gio", "pickle", "bz2",
+ "encodings", "encodings.*", "cairo", "pangocairo"],
+ "dll_excludes": ["USP10.dll", "NSI.dll", "MSIMG32.dll",
+ "DNSAPI.dll"],
+ "custom_boot_script": "install_scripts/windows/boot_script.py",
+ }
+ }
+ }
+
+ setup_args.update(WINDOWS_SETUP_ARGS)
+elif 'py2app' in sys.argv:
+ # Args for Mac OS X and py2app.
+ import py2app
+ import shutil
+
+ # py2app requires a ".py" suffix.
+ extended_app_name = APP_NAME + ".py"
+ shutil.copyfile(APP_NAME, extended_app_name)
+
+ MACOSX_SETUP_ARGS = {
+ 'app': [extended_app_name],
+ 'options': {"py2app": {
+ "packages": ["gio", "gobject", "gtk", "cairo"],
+ "includes": ["atk", "pango", "pangocairo"],
+ "argv_emulation": True,
+ "compressed": True,
+ "plist": "install_scripts/macosx/Info.plist",
+ "iconfile": "install_scripts/macosx/zenmap.icns"
+ }}
+ }
+
+ setup_args.update(MACOSX_SETUP_ARGS)
+elif 'vanilla' in sys.argv:
+ # Don't create uninstaller, don't fix paths. Used for bundling on OS X
+ sys.argv.remove('vanilla')
+else:
+ # Default args.
+ DEFAULT_SETUP_ARGS = {
+ 'cmdclass': {'install': my_install, 'uninstall': my_uninstall},
+ }
+ setup_args.update(DEFAULT_SETUP_ARGS)
+
+ data_files = [
+ (desktop_dir, glob('install_scripts/unix/*.desktop')),
+ (data_dir, ['install_scripts/unix/su-to-zenmap.sh'])
+ ]
+ setup_args["data_files"].extend(data_files)
+
+setup(**setup_args)