From 0d47952611198ef6b1163f366dc03922d20b1475 Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Wed, 17 Apr 2024 09:42:04 +0200 Subject: Adding upstream version 7.94+git20230807.3be01efb1+dfsg. Signed-off-by: Daniel Baumann --- zenmap/zenmapGUI/OptionBuilder.py | 498 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 498 insertions(+) create mode 100644 zenmap/zenmapGUI/OptionBuilder.py (limited to 'zenmap/zenmapGUI/OptionBuilder.py') diff --git a/zenmap/zenmapGUI/OptionBuilder.py b/zenmap/zenmapGUI/OptionBuilder.py new file mode 100644 index 0000000..2ef450b --- /dev/null +++ b/zenmap/zenmapGUI/OptionBuilder.py @@ -0,0 +1,498 @@ +#!/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 gi + +gi.require_version("Gtk", "3.0") +from gi.repository import Gtk, GObject + + +# Prevent loading PyXML +import xml +xml.__path__ = [x for x in xml.__path__ if "_xmlplus" not in x] + +from xml.dom import minidom + +from zenmapGUI.higwidgets.higlabels import HIGEntryLabel +from zenmapGUI.higwidgets.higbuttons import HIGButton + +from zenmapGUI.FileChoosers import AllFilesFileChooserDialog +from zenmapGUI.ProfileHelp import ProfileHelp + +from zenmapCore.NmapOptions import NmapOptions, split_quoted, join_quoted +import zenmapCore.I18N # lgtm[py/unused-import] +from zenmapGUI.ScriptInterface import ScriptInterface + + +def get_option_check_auxiliary_widget(option, ops, check): + if option in ("-sI", "-b", "--script", "--script-args", "--exclude", "-p", + "-D", "-S", "--source-port", "-e", "--ttl", "-iR", "--max-retries", + "--host-timeout", "--max-rtt-timeout", "--min-rtt-timeout", + "--initial-rtt-timeout", "--max-hostgroup", "--min-hostgroup", + "--max-parallelism", "--min-parallelism", "--max-scan-delay", + "--scan-delay", "-PA", "-PS", "-PU", "-PO", "-PY"): + return OptionEntry(option, ops, check) + elif option in ("-d", "-v"): + return OptionLevel(option, ops, check) + elif option in ("--excludefile", "-iL"): + return OptionFile(option, ops, check) + elif option in ("-A", "-O", "-sV", "-n", "-6", "-Pn", "-PE", "-PP", "-PM", + "-PB", "-sC", "--script-trace", "-F", "-f", "--packet-trace", "-r", + "--traceroute"): + return None + elif option in ("",): + return OptionExtras(option, ops, check) + else: + assert False, "Unknown option %s" % option + + +class OptionEntry(Gtk.Entry): + def __init__(self, option, ops, check): + Gtk.Entry.__init__(self) + self.option = option + self.ops = ops + self.check = check + self.connect("changed", self.changed_cb) + self.check.connect("toggled", self.check_toggled_cb) + self.update() + + def update(self): + if self.ops[self.option] is not None: + self.set_text(str(self.ops[self.option])) + self.check.set_active(True) + else: + self.set_text("") + self.check.set_active(False) + + def check_toggled_cb(self, check): + if check.get_active(): + self.ops[self.option] = self.get_text() + else: + self.ops[self.option] = None + + def changed_cb(self, widget): + self.check.set_active(True) + self.ops[self.option] = self.get_text() + + +class OptionExtras(Gtk.Entry): + def __init__(self, option, ops, check): + Gtk.Entry.__init__(self) + self.ops = ops + self.check = check + self.connect("changed", self.changed_cb) + self.check.connect("toggled", self.check_toggled_cb) + self.update() + + def update(self): + if len(self.ops.extras) > 0: + self.set_text(" ".join(self.ops.extras)) + self.check.set_active(True) + else: + self.set_text("") + self.check.set_active(False) + + def check_toggled_cb(self, check): + if check.get_active(): + self.ops.extras = [self.get_text()] + else: + self.ops.extras = [] + + def changed_cb(self, widget): + self.check.set_active(True) + self.ops.extras = [self.get_text()] + + +class OptionLevel(Gtk.SpinButton): + def __init__(self, option, ops, check): + adjustment = Gtk.Adjustment.new(0, 0, 10, 1, 0, 0) + Gtk.SpinButton.__init__(self, adjustment=adjustment, climb_rate=0.0, digits=0) + self.option = option + self.ops = ops + self.check = check + self.connect("changed", self.changed_cb) + self.check.connect("toggled", self.check_toggled_cb) + self.update() + + def update(self): + level = self.ops[self.option] + if level is not None and level > 0: + self.get_adjustment().set_value(int(level)) + self.check.set_active(True) + else: + self.get_adjustment().set_value(0) + self.check.set_active(False) + + def check_toggled_cb(self, check): + if check.get_active(): + self.ops[self.option] = int(self.get_adjustment().get_value()) + else: + self.ops[self.option] = 0 + + def changed_cb(self, widget): + self.check.set_active(True) + self.ops[self.option] = int(self.get_adjustment().get_value()) + + +class OptionFile(Gtk.Box): + __gsignals__ = { + "changed": (GObject.SignalFlags.RUN_FIRST, GObject.TYPE_NONE, ()) + } + + def __init__(self, option, ops, check): + Gtk.Box.__init__(self, orientation=Gtk.Orientation.HORIZONTAL) + + self.option = option + self.ops = ops + self.check = check + + self.entry = Gtk.Entry() + self.pack_start(self.entry, True, True, 0) + button = HIGButton(stock=Gtk.STOCK_OPEN) + self.pack_start(button, False, True, 0) + + button.connect("clicked", self.clicked_cb) + + self.entry.connect("changed", lambda x: self.emit("changed")) + self.entry.connect("changed", self.changed_cb) + self.check.connect("toggled", self.check_toggled_cb) + self.update() + + def update(self): + if self.ops[self.option] is not None: + self.entry.set_text(self.ops[self.option]) + self.check.set_active(True) + else: + self.entry.set_text("") + self.check.set_active(False) + + def check_toggled_cb(self, check): + if check.get_active(): + self.ops[self.option] = self.entry.get_text() + else: + self.ops[self.option] = None + + def changed_cb(self, widget): + self.check.set_active(True) + self.ops[self.option] = self.entry.get_text() + + def clicked_cb(self, button): + dialog = AllFilesFileChooserDialog(_("Choose file")) + if dialog.run() == Gtk.ResponseType.OK: + self.entry.set_text(dialog.get_filename()) + dialog.destroy() + + +class TargetEntry(Gtk.Entry): + def __init__(self, ops): + Gtk.Entry.__init__(self) + self.ops = ops + self.connect("changed", self.changed_cb) + self.update() + + def update(self): + self.set_text(" ".join(self.ops.target_specs)) + + def changed_cb(self, widget): + self.ops.target_specs = self.get_targets() + + def get_targets(self): + return split_quoted(self.get_text()) + + +class OptionTab(object): + def __init__(self, root_tab, ops, update_command, help_buf): + actions = {'target': self.__parse_target, + 'option_list': self.__parse_option_list, + 'option_check': self.__parse_option_check} + + self.ops = ops + self.update_command = update_command + self.help_buf = help_buf + + self.profilehelp = ProfileHelp() + self.notscripttab = False # assume every tab is scripting tab + self.widgets_list = [] + for option_element in root_tab.childNodes: + if (hasattr(option_element, "tagName") and + option_element.tagName in actions.keys()): + parse_func = actions[option_element.tagName] + widget = parse_func(option_element) + self.widgets_list.append(widget) + + def __parse_target(self, target_element): + label = _(target_element.getAttribute('label')) + label_widget = HIGEntryLabel(label) + target_widget = TargetEntry(self.ops) + target_widget.connect("changed", self.update_target) + return label_widget, target_widget + + def __parse_option_list(self, option_list_element): + children = option_list_element.getElementsByTagName('option') + + label_widget = HIGEntryLabel( + _(option_list_element.getAttribute('label'))) + option_list_widget = OptionList(self.ops) + + for child in children: + option = child.getAttribute('option') + argument = child.getAttribute('argument') + label = _(child.getAttribute('label')) + option_list_widget.append(option, argument, label) + self.profilehelp.add_label(option, label) + self.profilehelp.add_shortdesc( + option, _(child.getAttribute('short_desc'))) + self.profilehelp.add_example( + option, child.getAttribute('example')) + + option_list_widget.update() + + option_list_widget.connect("changed", self.update_list_option) + + return label_widget, option_list_widget + + def __parse_option_check(self, option_check): + option = option_check.getAttribute('option') + label = _(option_check.getAttribute('label')) + short_desc = _(option_check.getAttribute('short_desc')) + example = option_check.getAttribute('example') + + self.profilehelp.add_label(option, label) + self.profilehelp.add_shortdesc(option, short_desc) + self.profilehelp.add_example(option, example) + + check = OptionCheck(option, label) + auxiliary_widget = get_option_check_auxiliary_widget( + option, self.ops, check) + if auxiliary_widget is not None: + auxiliary_widget.connect("changed", self.update_auxiliary_widget) + auxiliary_widget.connect( + 'enter-notify-event', self.enter_notify_event_cb, option) + else: + check.set_active(not not self.ops[option]) + + check.connect('toggled', self.update_check, auxiliary_widget) + check.connect('enter-notify-event', self.enter_notify_event_cb, option) + + return check, auxiliary_widget + + def fill_table(self, table, expand_fill=True): + yopt = (0, Gtk.AttachOptions.EXPAND | Gtk.AttachOptions.FILL)[expand_fill] + for y, widget in enumerate(self.widgets_list): + if widget[1] is None: + table.attach(widget[0], 0, 2, y, y + 1, yoptions=yopt) + else: + table.attach(widget[0], 0, 1, y, y + 1, yoptions=yopt) + table.attach(widget[1], 1, 2, y, y + 1, yoptions=yopt) + + def update_auxiliary_widget(self, auxiliary_widget): + self.update_command() + + def update(self): + for check, auxiliary_widget in self.widgets_list: + if auxiliary_widget is not None: + auxiliary_widget.update() + else: + check.set_active(not not self.ops[check.option]) + + def update_target(self, entry): + self.ops.target_specs = entry.get_targets() + self.update_command() + + def update_check(self, check, auxiliary_widget): + if auxiliary_widget is None: + if check.get_active(): + self.ops[check.option] = True + else: + self.ops[check.option] = False + self.update_command() + + def update_list_option(self, widget): + if widget.last_selected: + self.ops[widget.last_selected] = None + + opt, arg, label = widget.list[widget.get_active()] + if opt: + if arg: + self.ops[opt] = arg + else: + self.ops[opt] = True + + widget.last_selected = opt + + self.show_help_for_option(opt) + + self.update_command() + + def show_help_for_option(self, option): + self.profilehelp.handler(option) + text = "" + if self.profilehelp.get_currentstate() == "Default": + text = "" + else: + text += self.profilehelp.get_label() + text += "\n\n" + text += self.profilehelp.get_shortdesc() + if self.profilehelp.get_example(): + text += "\n\nExample input:\n" + text += self.profilehelp.get_example() + self.help_buf.set_text(text) + + def enter_notify_event_cb(self, event, widget, option): + self.show_help_for_option(option) + + +class OptionBuilder(object): + def __init__(self, xml_file, ops, update_func, help_buf): + """ + xml_file is a UI description xml-file + ops is an NmapOptions instance + """ + xml_desc = open(xml_file) + self.xml = minidom.parse(xml_desc) + # Closing file to avoid problems with file descriptors + xml_desc.close() + + self.ops = ops + self.help_buf = help_buf + self.update_func = update_func + + self.root_tag = "interface" + + self.xml = self.xml.getElementsByTagName(self.root_tag)[0] + + self.groups = self.__parse_groups() + self.section_names = self.__parse_section_names() + self.tabs = self.__parse_tabs() + + def update(self): + for tab in self.tabs.values(): + tab.update() + + def __parse_section_names(self): + dic = {} + for group in self.groups: + grp = self.xml.getElementsByTagName(group)[0] + dic[group] = grp.getAttribute('label') + return dic + + def __parse_groups(self): + return [g_name.getAttribute('name') for g_name in + self.xml.getElementsByTagName('groups')[0].getElementsByTagName('group')] # noqa + + def __parse_tabs(self): + dic = {} + for tab_name in self.groups: + if tab_name != "Scripting": + dic[tab_name] = OptionTab( + self.xml.getElementsByTagName(tab_name)[0], self.ops, + self.update_func, self.help_buf) + dic[tab_name].notscripttab = True + else: + dic[tab_name] = ScriptInterface( + None, self.ops, self.update_func, self.help_buf) + return dic + + +class OptionList(Gtk.ComboBox): + def __init__(self, ops): + self.ops = ops + + self.list = Gtk.ListStore.new([str, str, str]) + Gtk.ComboBox.__init__(self, model=self.list) + + cell = Gtk.CellRendererText() + self.pack_start(cell, True) + self.add_attribute(cell, 'text', 2) + + self.last_selected = None + self.options = [] + + def update(self): + selected = 0 + for i, row in enumerate(self.list): + opt, arg = row[0], row[1] + if opt == "": + continue + if ((not arg and self.ops[opt]) or + (arg and str(self.ops[opt]) == arg)): + selected = i + self.set_active(selected) + + def append(self, option, argument, label): + opt = label + ops = NmapOptions() + if option is not None and option != "": + if argument: + ops[option] = argument + else: + ops[option] = True + opt += " (%s)" % join_quoted(ops.render()[1:]) + + self.list.append([option, argument, opt]) + self.options.append(option) + + +class OptionCheck(Gtk.CheckButton): + def __init__(self, option, label): + opt = label + if option is not None and option != "": + opt += " (%s)" % option + + Gtk.CheckButton.__init__(self, label=opt, use_underline=False) + + self.option = option -- cgit v1.2.3