diff --git a/zenmap/pyproject.toml b/zenmap/pyproject.toml new file mode 100644 index 000000000..7b851d675 --- /dev/null +++ b/zenmap/pyproject.toml @@ -0,0 +1,48 @@ +[build-system] +requires = ["setuptools"] +build-backend = "setuptools.build_meta" + +[project] +name = "zenmap" +license = {text = "Nmap License (https://nmap.org/book/man-legal.html)"} +dynamic = ["version"] +authors = [{name = "The Nmap Project"}] +description = "Nmap frontend and results viewer" +readme = {file = "README", content-type = "text/plain"} +dependencies = [ + "PyGObject", +] + +[project.urls] +Homepage = "https://nmap.org/zenmap/" +Documentation = "https://nmap.org/book/zenmap.html" +Repository = "https://github.com/nmap/nmap.git" +Issues = "https://github.com/nmap/nmap/issues" +Changelog = "https://nmap.org/changelog" + +[project.gui-scripts] +zenmap = "zenmapGUI.App:run" + +[tool.setuptools] +packages = [ +"zenmapCore", +"zenmapGUI", +"zenmapGUI.higwidgets", +"radialnet", +"radialnet.bestwidgets", +"radialnet.core", +"radialnet.gui", +"radialnet.util", +"share", +] + +[tool.setuptools.dynamic] +version = {attr = "zenmapCore.Version.VERSION"} + +[tool.setuptools.package-data] +"share.zenmap.pixmaps" = ["*.gif", "*.png"] +"share.zenmap.pixmaps.radialnet" = ["*.png"] +"share.zenmap.locale" = ["*.mo"] +"share.zenmap.config" = ["zenmap.conf", "scan_profile.usp", "zenmap_version"] +"share.zenmap.docs" = ["*.html"] +"share.zenmap.misc" = ["*.xml"] diff --git a/zenmap/setup.py b/zenmap/setup.py deleted file mode 100755 index 657d4ab08..000000000 --- a/zenmap/setup.py +++ /dev/null @@ -1,586 +0,0 @@ -#!/usr/bin/env python3 - -# ***********************IMPORTANT NMAP LICENSE TERMS************************ -# * -# * The Nmap Security Scanner is (C) 1996-2024 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 logging -from setuptools import setup, Command -from setuptools.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 setuptools 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. setuptools 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: - logging.warning("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 setuptools 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: - logging.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: - logging.info("Removing '%s'.", file) - try: - if not self.dry_run: - os.remove(file) - except OSError as e: - logging.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: - logging.info("Removing the directory '%s'.", dir) - if not self.dry_run: - os.rmdir(dir) - except OSError as e: - if e.errno == errno.ENOTEMPTY: - logging.info("Directory '%s' not empty; not removing.", - dir) - else: - logging.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)