"Fossies" - the Fresh Open Source Software Archive

Member "nmap-7.91/zenmap/zenmapGUI/ScriptInterface.py" (9 Oct 2020, 30635 Bytes) of package /linux/misc/nmap-7.91.tgz:


As a special service "Fossies" has tried to format the requested source page into HTML format using (guessed) Python source code syntax highlighting (style: standard) with prefixed line numbers. Alternatively you can here view or download the uninterpreted source code file. For more information about "ScriptInterface.py" see the Fossies "Dox" file reference documentation and the latest Fossies "Diffs" side-by-side code changes report: 7.90_vs_7.91.

    1 #!/usr/bin/env python
    2 
    3 # ***********************IMPORTANT NMAP LICENSE TERMS************************
    4 # *                                                                         *
    5 # * The Nmap Security Scanner is (C) 1996-2020 Insecure.Com LLC ("The Nmap  *
    6 # * Project"). Nmap is also a registered trademark of the Nmap Project.     *
    7 # *                                                                         *
    8 # * This program is distributed under the terms of the Nmap Public Source   *
    9 # * License (NPSL). The exact license text applying to a particular Nmap    *
   10 # * release or source code control revision is contained in the LICENSE     *
   11 # * file distributed with that version of Nmap or source code control       *
   12 # * revision. More Nmap copyright/legal information is available from       *
   13 # * https://nmap.org/book/man-legal.html, and further information on the    *
   14 # * NPSL license itself can be found at https://nmap.org/npsl. This header  *
   15 # * summarizes some key points from the Nmap license, but is no substitute  *
   16 # * for the actual license text.                                            *
   17 # *                                                                         *
   18 # * Nmap is generally free for end users to download and use themselves,    *
   19 # * including commercial use. It is available from https://nmap.org.        *
   20 # *                                                                         *
   21 # * The Nmap license generally prohibits companies from using and           *
   22 # * redistributing Nmap in commercial products, but we sell a special Nmap  *
   23 # * OEM Edition with a more permissive license and special features for     *
   24 # * this purpose. See https://nmap.org/oem                                  *
   25 # *                                                                         *
   26 # * If you have received a written Nmap license agreement or contract       *
   27 # * stating terms other than these (such as an Nmap OEM license), you may   *
   28 # * choose to use and redistribute Nmap under those terms instead.          *
   29 # *                                                                         *
   30 # * The official Nmap Windows builds include the Npcap software             *
   31 # * (https://npcap.org) for packet capture and transmission. It is under    *
   32 # * separate license terms which forbid redistribution without special      *
   33 # * permission. So the official Nmap Windows builds may not be              *
   34 # * redistributed without special permission (such as an Nmap OEM           *
   35 # * license).                                                               *
   36 # *                                                                         *
   37 # * Source is provided to this software because we believe users have a     *
   38 # * right to know exactly what a program is going to do before they run it. *
   39 # * This also allows you to audit the software for security holes.          *
   40 # *                                                                         *
   41 # * Source code also allows you to port Nmap to new platforms, fix bugs,    *
   42 # * and add new features.  You are highly encouraged to submit your         *
   43 # * changes as a Github PR or by email to the dev@nmap.org mailing list     *
   44 # * for possible incorporation into the main distribution. Unless you       *
   45 # * specify otherwise, it is understood that you are offering us very       *
   46 # * broad rights to use your submissions as described in the Nmap Public    *
   47 # * Source License Contributor Agreement. This is important because we      *
   48 # * fund the project by selling licenses with various terms, and also       *
   49 # * because the inability to relicense code has caused devastating          *
   50 # * problems for other Free Software projects (such as KDE and NASM).       *
   51 # *                                                                         *
   52 # * The free version of Nmap is distributed in the hope that it will be     *
   53 # * useful, but WITHOUT ANY WARRANTY; without even the implied warranty of  *
   54 # * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. Warranties,        *
   55 # * indemnification and commercial support are all available through the    *
   56 # * Npcap OEM program--see https://nmap.org/oem.                            *
   57 # *                                                                         *
   58 # ***************************************************************************/
   59 
   60 # This module is responsible for interface present under "Scripting" tab.
   61 
   62 import gobject
   63 import gtk
   64 import sys
   65 import tempfile
   66 import os
   67 
   68 # Prevent loading PyXML
   69 import xml
   70 xml.__path__ = [x for x in xml.__path__ if "_xmlplus" not in x]
   71 
   72 import xml.sax
   73 
   74 from zenmapGUI.higwidgets.higboxes import HIGVBox, HIGHBox
   75 from zenmapGUI.higwidgets.higscrollers import HIGScrolledWindow
   76 from zenmapGUI.higwidgets.higbuttons import HIGButton
   77 from zenmapCore.ScriptMetadata import get_script_entries
   78 from zenmapCore.ScriptArgsParser import parse_script_args_dict
   79 from zenmapCore.NmapCommand import NmapCommand
   80 from zenmapCore.NmapOptions import NmapOptions
   81 import zenmapCore.NSEDocParser
   82 import zenmapGUI.FileChoosers
   83 from zenmapCore.UmitConf import PathsConfig
   84 from zenmapCore.UmitLogging import log
   85 from zenmapCore.Name import APP_NAME
   86 
   87 paths_config = PathsConfig()
   88 
   89 
   90 def text_buffer_insert_nsedoc(buf, nsedoc):
   91     """Inserts NSEDoc at the end of the buffer, with markup turned into proper
   92     tags."""
   93     if not buf.tag_table.lookup("NSEDOC_CODE_TAG"):
   94         buf.create_tag("NSEDOC_CODE_TAG", font="Monospace")
   95     for event in zenmapCore.NSEDocParser.nsedoc_parse(nsedoc):
   96         if event.type == "paragraph_start":
   97             buf.insert(buf.get_end_iter(), "\n")
   98         elif event.type == "paragraph_end":
   99             buf.insert(buf.get_end_iter(), "\n")
  100         elif event.type == "list_start":
  101             buf.insert(buf.get_end_iter(), "\n")
  102         elif event.type == "list_end":
  103             pass
  104         elif event.type == "list_item_start":
  105             buf.insert(buf.get_end_iter(), u"\u2022\u00a0")  # bullet nbsp
  106         elif event.type == "list_item_end":
  107             buf.insert(buf.get_end_iter(), "\n")
  108         elif event.type == "text":
  109             buf.insert(buf.get_end_iter(), event.text)
  110         elif event.type == "code":
  111             buf.insert_with_tags_by_name(
  112                     buf.get_end_iter(), event.text, "NSEDOC_CODE_TAG")
  113 
  114 
  115 class ScriptHelpXMLContentHandler (xml.sax.handler.ContentHandler):
  116     """A very simple parser for --script-help XML output. This could extract
  117     other information like categories and description, but all it gets is
  118     filenames. (ScriptMetadata gets the other information.)"""
  119     def __init__(self):
  120         xml.sax.handler.ContentHandler.__init__(self)
  121         self.script_filenames = []
  122         self.scripts_dir = None
  123         self.nselib_dir = None
  124 
  125     def startElement(self, name, attrs):
  126         if name == u"directory":
  127             if u"name" not in attrs:
  128                 raise ValueError(
  129                         u'"directory" element did not have "name" attribute')
  130             dirname = attrs[u"name"]
  131             if u"path" not in attrs:
  132                 raise ValueError(
  133                         u'"directory" element did not have "path" attribute')
  134             path = attrs[u"path"].encode("raw_unicode_escape").decode(
  135                     sys.getfilesystemencoding())
  136             if dirname == u"scripts":
  137                 self.scripts_dir = path
  138             elif dirname == u"nselib":
  139                 self.nselib_dir = path
  140             else:
  141                 # Ignore.
  142                 pass
  143         elif name == u"script":
  144             if u"filename" not in attrs:
  145                 raise ValueError(
  146                         u'"script" element did not have "filename" attribute')
  147             self.script_filenames.append(attrs[u"filename"])
  148 
  149     @staticmethod
  150     def parse_nmap_script_help(f):
  151         parser = xml.sax.make_parser()
  152         handler = ScriptHelpXMLContentHandler()
  153         parser.setContentHandler(handler)
  154         parser.parse(f)
  155         return handler
  156 
  157 
  158 class ScriptInterface:
  159     # Timeout, in milliseconds, after the user stops typing and we update the
  160     # interface from --script.
  161     SCRIPT_LIST_DELAY = 500
  162     # Timeout, in milliseconds, between polls of the Nmap subprocess.
  163     NMAP_DELAY = 200
  164 
  165     def __init__(self, root_tabs, ops, update_command, help_buf):
  166         self.hmainbox = HIGHBox(False, 0)
  167         self.notscripttab = False  # show profile editor it is a script tab
  168         self.nmap_process = None
  169         self.script_list_timeout_id = None
  170         self.nmap_timeout_id = None
  171         self.chk_nmap_timeout_id = None
  172         self.script_file_chooser = None
  173         self.ops = ops
  174         self.update_command = update_command
  175         self.help_buf = help_buf
  176         self.arg_values = {}
  177         self.current_arguments = []
  178         self.set_help_texts()
  179         self.prev_script_spec = None
  180         self.focusedentry = None
  181 
  182         self.liststore = gtk.ListStore(str, "gboolean", object)
  183 
  184         self.file_liststore = gtk.ListStore(str, "gboolean")
  185 
  186         # Arg name, arg value, (name, desc) tuple.
  187         self.arg_liststore = gtk.ListStore(str, str, object)
  188 
  189         # This is what is shown initially. After the initial Nmap run to get
  190         # the list of script is finished, this will be replaced with a TreeView
  191         # showing the scripts or an error message.
  192         self.script_list_container = gtk.VBox()
  193         self.script_list_container.pack_start(self.make_please_wait_widget())
  194         self.hmainbox.pack_start(self.script_list_container, False, False, 0)
  195 
  196         self.nmap_error_widget = gtk.Label(_(
  197             "There was an error getting the list of scripts from Nmap. "
  198             "Try upgrading Nmap."))
  199         self.nmap_error_widget.set_line_wrap(True)
  200         self.nmap_error_widget.show_all()
  201 
  202         self.script_list_widget = self.make_script_list_widget()
  203         self.script_list_widget.show_all()
  204 
  205         vbox = HIGVBox(False, 5)
  206         vbox.pack_start(self.make_description_widget(), True, True, 0)
  207         vbox.pack_start(self.make_arguments_widget(), True, True, 0)
  208         self.hmainbox.pack_end(vbox, True, True, 0)
  209 
  210         self.update_argument_values(self.ops["--script-args"])
  211 
  212         # Start the initial backgrounded Nmap run to get the list of all
  213         # available scripts.
  214         self.get_script_list("all", self.initial_script_list_cb)
  215 
  216     def get_script_list(self, rules, callback):
  217         """Start an Nmap subprocess in the background with
  218         "--script-help=<rules> -oX -", and set it up to call the given callback
  219         when finished."""
  220 
  221         ops = NmapOptions()
  222         ops.executable = paths_config.nmap_command_path
  223         ops["--script-help"] = rules
  224         ops["-oX"] = "-"
  225         command_string = ops.render_string()
  226         # Separate stderr to avoid breaking XML parsing with "Warning: File
  227         # ./nse_main.lua exists, but Nmap is using...".
  228         stderr = tempfile.TemporaryFile(
  229                 mode="rb", prefix=APP_NAME + "-script-help-stderr-")
  230         log.debug("Script interface: running %s" % repr(command_string))
  231         nmap_process = NmapCommand(command_string)
  232         try:
  233             nmap_process.run_scan(stderr=stderr)
  234         except Exception, e:
  235             callback(False, None)
  236             stderr.close()
  237             return
  238         stderr.close()
  239 
  240         self.script_list_widget.set_sensitive(False)
  241 
  242         gobject.timeout_add(
  243                 self.NMAP_DELAY, self.script_list_timer_callback,
  244                 nmap_process, callback)
  245 
  246     def script_list_timer_callback(self, process, callback):
  247         try:
  248             status = process.scan_state()
  249         except Exception:
  250             status = None
  251         log.debug("Script interface: script_list_timer_callback %s" %
  252                 repr(status))
  253 
  254         if status is True:
  255             # Still running, schedule this timer to check again.
  256             return True
  257 
  258         self.script_list_widget.set_sensitive(True)
  259 
  260         if status is False:
  261             # Finished with success.
  262             callback(True, process)
  263         else:
  264             # Finished with error.
  265             callback(False, process)
  266 
  267     def initial_script_list_cb(self, status, process):
  268         log.debug("Script interface: initial_script_list_cb %s" % repr(status))
  269         for child in self.script_list_container.get_children():
  270             self.script_list_container.remove(child)
  271         if status and self.handle_initial_script_list_output(process):
  272             self.script_list_container.pack_start(self.script_list_widget)
  273         else:
  274             self.script_list_container.pack_start(self.nmap_error_widget)
  275 
  276     def handle_initial_script_list_output(self, process):
  277         process.stdout_file.seek(0)
  278         try:
  279             handler = ScriptHelpXMLContentHandler.parse_nmap_script_help(
  280                     process.stdout_file)
  281         except (ValueError, xml.sax.SAXParseException), e:
  282             log.debug("--script-help parse exception: %s" % str(e))
  283             return False
  284 
  285         # Check if any scripts were output; if not, Nmap is probably too old.
  286         if len(handler.script_filenames) == 0:
  287             return False
  288 
  289         if not handler.scripts_dir:
  290             log.debug("--script-help error: no scripts directory")
  291             return False
  292         if not handler.nselib_dir:
  293             log.debug("--script-help error: no nselib directory")
  294             return False
  295 
  296         log.debug("Script interface: scripts dir %s" % repr(
  297             handler.scripts_dir))
  298         log.debug("Script interface: nselib dir %s" % repr(handler.nselib_dir))
  299 
  300         # Make a dict of script metadata entries.
  301         entries = {}
  302         for entry in get_script_entries(
  303                 handler.scripts_dir, handler.nselib_dir):
  304             entries[entry.filename] = entry
  305 
  306         self.liststore.clear()
  307         for filename in handler.script_filenames:
  308             basename = os.path.basename(filename)
  309             entry = entries.get(basename)
  310             if entry:
  311                 script_id = self.strip_file_name(basename)
  312                 self.liststore.append([script_id, False, entry])
  313             else:
  314                 # ScriptMetadata found nothing for this script?
  315                 self.file_liststore.append([filename, False])
  316 
  317         # Now figure out which scripts are selected.
  318         self.update_script_list_from_spec(self.ops["--script"])
  319         return True
  320 
  321     def update_script_list_from_spec(self, spec):
  322         """Callback method for user edit delay."""
  323         log.debug("Script interface: update_script_list_from_spec %s" % repr(
  324             spec))
  325         if spec:
  326             self.get_script_list(spec, self.update_script_list_cb)
  327         else:
  328             self.refresh_list_scripts([])
  329 
  330     def update_script_list_cb(self, status, process):
  331         log.debug("Script interface: update_script_list_cb %s" % repr(status))
  332         if status:
  333             self.handle_update_script_list_output(process)
  334         else:
  335             self.refresh_list_scripts([])
  336 
  337     def handle_update_script_list_output(self, process):
  338         process.stdout_file.seek(0)
  339         try:
  340             handler = ScriptHelpXMLContentHandler.parse_nmap_script_help(
  341                     process.stdout_file)
  342         except (ValueError, xml.sax.SAXParseException), e:
  343             log.debug("--script-help parse exception: %s" % str(e))
  344             return False
  345 
  346         self.refresh_list_scripts(handler.script_filenames)
  347 
  348     def get_hmain_box(self):
  349         """Returns main Hbox to ProfileEditor."""
  350         return self.hmainbox
  351 
  352     def update(self):
  353         """Updates the interface when the command entry is changed."""
  354         # updates list of scripts
  355         rules = self.ops["--script"]
  356         if (self.prev_script_spec != rules):
  357             self.renew_script_list_timer(rules)
  358         self.prev_script_spec = rules
  359         # updates arguments..
  360         raw_argument = self.ops["--script-args"]
  361         if raw_argument is not None:
  362             self.parse_script_args(raw_argument)
  363         self.arg_liststore.clear()
  364         for arg in self.current_arguments:
  365             arg_name, arg_desc = arg
  366             value = self.arg_values.get(arg_name)
  367             if not value:
  368                 self.arg_liststore.append([arg_name, None, arg])
  369             else:
  370                 self.arg_liststore.append([arg_name, value, arg])
  371 
  372     def renew_script_list_timer(self, spec):
  373         """Restart the timer to update the script list when the user edits the
  374         command. Because updating the script list is an expensive operation
  375         involving the creation of a subprocess, we don't do it for every typed
  376         character."""
  377         if self.script_list_timeout_id:
  378             gobject.source_remove(self.script_list_timeout_id)
  379         self.script_list_timeout_id = gobject.timeout_add(
  380                 self.SCRIPT_LIST_DELAY,
  381                 self.update_script_list_from_spec, spec)
  382 
  383     def parse_script_args(self, raw_argument):
  384         """When the command line is edited, this function is called to update
  385         the script arguments display according to the value of
  386         --script-args."""
  387         arg_dict = parse_script_args_dict(raw_argument)
  388         if arg_dict is None:  # if there is parsing error args_dict holds none
  389             self.arg_values.clear()
  390         else:
  391             for key in arg_dict.keys():
  392                 self.arg_values[key] = arg_dict[key]
  393 
  394     def update_argument_values(self, raw_argument):
  395         """When scripting tab starts up, argument values are updated."""
  396         if raw_argument is not None:
  397             self.parse_script_args(raw_argument)
  398 
  399     def set_help_texts(self):
  400         """Sets the help texts to be displayed."""
  401         self.list_scripts_help = _("""List of scripts
  402 
  403 A list of all installed scripts. Activate or deactivate a script \
  404 by clicking the box next to the script name.""")
  405         self.description_help = _("""Description
  406 
  407 This box shows the categories a script belongs to. In addition, it gives a \
  408 detailed description of the script which is present in script. A URL points \
  409 to online NSEDoc documentation.""")
  410         self.argument_help = _("""Arguments
  411 
  412 A list of arguments that affect the selected script. Enter a value by \
  413 clicking in the value field beside the argument name.""")
  414 
  415     def make_please_wait_widget(self):
  416         vbox = gtk.VBox()
  417         label = gtk.Label(_("Please wait."))
  418         label.set_line_wrap(True)
  419         vbox.pack_start(label)
  420         return vbox
  421 
  422     def make_script_list_widget(self):
  423         """Creates and packs widgets associated with left hand side of
  424         Interface."""
  425         vbox = gtk.VBox()
  426 
  427         scrolled_window = HIGScrolledWindow()
  428         scrolled_window.set_policy(gtk.POLICY_ALWAYS, gtk.POLICY_ALWAYS)
  429         # Expand only vertically.
  430         scrolled_window.set_size_request(175, -1)
  431         listview = gtk.TreeView(self.liststore)
  432         listview.set_headers_visible(False)
  433         listview.connect("enter-notify-event", self.update_help_ls_cb)
  434         selection = listview.get_selection()
  435         selection.connect("changed", self.selection_changed_cb)
  436         cell = gtk.CellRendererText()
  437         togglecell = gtk.CellRendererToggle()
  438         togglecell.set_property("activatable", True)
  439         togglecell.connect("toggled", self.toggled_cb, self.liststore)
  440         col = gtk.TreeViewColumn(_('Names'))
  441         col.set_sizing(gtk.TREE_VIEW_COLUMN_GROW_ONLY)
  442         col.set_resizable(True)
  443         togglecol = gtk.TreeViewColumn(None, togglecell)
  444         togglecol.add_attribute(togglecell, "active", 1)
  445         listview.append_column(togglecol)
  446         listview.append_column(col)
  447         col.pack_start(cell, True)
  448         col.add_attribute(cell, "text", 0)
  449         scrolled_window.add(listview)
  450         scrolled_window.show()
  451         vbox.pack_start(scrolled_window, True, True, 0)
  452 
  453         self.file_scrolled_window = HIGScrolledWindow()
  454         self.file_scrolled_window.set_policy(
  455                 gtk.POLICY_ALWAYS, gtk.POLICY_ALWAYS)
  456         self.file_scrolled_window.set_size_request(175, -1)
  457         self.file_scrolled_window.hide()
  458         self.file_scrolled_window.set_no_show_all(True)
  459 
  460         self.file_listview = gtk.TreeView(self.file_liststore)
  461         self.file_listview.set_headers_visible(False)
  462         col = gtk.TreeViewColumn(None)
  463         self.file_listview.append_column(col)
  464         cell = gtk.CellRendererToggle()
  465         col.pack_start(cell, True)
  466         cell.set_property("activatable", True)
  467         col.add_attribute(cell, "active", 1)
  468         cell.connect("toggled", self.toggled_cb, self.file_liststore)
  469 
  470         col = gtk.TreeViewColumn(None)
  471         self.file_listview.append_column(col)
  472         cell = gtk.CellRendererText()
  473         col.pack_start(cell)
  474         col.add_attribute(cell, "text", 0)
  475 
  476         self.file_listview.show_all()
  477         self.file_scrolled_window.add(self.file_listview)
  478         vbox.pack_start(self.file_scrolled_window, False)
  479 
  480         hbox = HIGHBox(False, 2)
  481         self.remove_file_button = HIGButton(stock=gtk.STOCK_REMOVE)
  482         self.remove_file_button.connect(
  483                 "clicked", self.remove_file_button_clicked_cb)
  484         self.remove_file_button.set_sensitive(False)
  485         hbox.pack_end(self.remove_file_button)
  486         add_file_button = HIGButton(stock=gtk.STOCK_ADD)
  487         add_file_button.connect("clicked", self.add_file_button_clicked_cb)
  488         hbox.pack_end(add_file_button)
  489 
  490         vbox.pack_start(hbox, False, False, 0)
  491 
  492         return vbox
  493 
  494     def refresh_list_scripts(self, selected_scripts):
  495         """The list of selected scripts is refreshed in the list store."""
  496         for row in self.liststore:
  497             row[1] = False
  498         for row in self.file_liststore:
  499             row[1] = False
  500         for filename in selected_scripts:
  501             for row in self.liststore:
  502                 if row[0] == self.strip_file_name(os.path.basename(filename)):
  503                     row[1] = True
  504                     break
  505             else:
  506                 for row in self.file_liststore:
  507                     if row[0] == filename:
  508                         row[1] = True
  509                         break
  510                 else:
  511                     self.file_liststore.append([filename, True])
  512 
  513     def strip_file_name(self, filename):
  514         """Removes a ".nse" extension from filename if present."""
  515         if(filename.endswith(".nse")):
  516             return filename[:-4]
  517         else:
  518             return filename
  519 
  520     def set_script_from_selection(self):
  521         scriptsname = []
  522         for entry in self.liststore:
  523             if entry[1]:
  524                 scriptsname.append(self.strip_file_name(entry[0]))
  525         for entry in self.file_liststore:
  526             if entry[1]:
  527                 scriptsname.append(entry[0])
  528         if len(scriptsname) == 0:
  529             self.ops["--script"] = None
  530         else:
  531             self.ops["--script"] = ",".join(scriptsname)
  532         self.update_command()
  533 
  534     def toggled_cb(self, cell, path, model):
  535         """Callback method, called when the check box in list of scripts is
  536         toggled."""
  537         model[path][1] = not model[path][1]
  538         self.set_script_from_selection()
  539 
  540     def make_description_widget(self):
  541         """Creates and packs widgets related to displaying the description
  542         box."""
  543         sw = HIGScrolledWindow()
  544         sw.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_ALWAYS)
  545         sw.set_shadow_type(gtk.SHADOW_OUT)
  546         sw.set_border_width(5)
  547         text_view = gtk.TextView()
  548         text_view.connect("enter-notify-event", self.update_help_desc_cb)
  549         self.text_buffer = text_view.get_buffer()
  550         self.text_buffer.create_tag("Usage", font="Monospace")
  551         self.text_buffer.create_tag("Output", font="Monospace")
  552         text_view.set_wrap_mode(gtk.WRAP_WORD)
  553         text_view.set_editable(False)
  554         text_view.set_justification(gtk.JUSTIFY_LEFT)
  555         sw.add(text_view)
  556         return sw
  557 
  558     def make_arguments_widget(self):
  559         """Creates and packs widgets related to arguments box."""
  560         vbox = gtk.VBox()
  561         vbox.pack_start(gtk.Label(_("Arguments")), False, False, 0)
  562         arg_window = HIGScrolledWindow()
  563         arg_window.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_ALWAYS)
  564         arg_window.set_shadow_type(gtk.SHADOW_OUT)
  565 
  566         arg_listview = gtk.TreeView(self.arg_liststore)
  567         arg_listview.connect("motion-notify-event", self.update_help_arg_cb)
  568         argument = gtk.CellRendererText()
  569         self.value = gtk.CellRendererText()
  570         self.value.connect("edited", self.value_edited_cb, self.arg_liststore)
  571         arg_col = gtk.TreeViewColumn("Arguments\t")
  572         val_col = gtk.TreeViewColumn("values")
  573         arg_listview.append_column(arg_col)
  574         arg_listview.append_column(val_col)
  575         arg_col.pack_start(argument, True)
  576         arg_col.add_attribute(argument, "text", 0)
  577         val_col.pack_start(self.value, True)
  578         val_col.add_attribute(self.value, "text", 1)
  579 
  580         arg_window.add(arg_listview)
  581         vbox.pack_start(arg_window, True, True, 0)
  582 
  583         return vbox
  584 
  585     def value_edited_cb(self, cell, path, new_text, model):
  586         """Called when the argument cell is edited."""
  587         self.arg_list = []
  588         model[path][1] = new_text
  589         argument_name = model[path][0]
  590         self.arg_values[argument_name] = new_text
  591         self.update_arg_values()
  592 
  593     def update_arg_values(self):
  594         """When the widget is updated with argument value, correspondingly
  595         update the command line."""
  596         for key in self.arg_values.keys():
  597             if len(self.arg_values[key]) == 0:
  598                 del self.arg_values[key]
  599             else:
  600                 self.arg_list.append(key + "=" + self.arg_values[key])
  601         if len(self.arg_list) == 0:
  602             self.ops["--script-args"] = None
  603             self.arg_values.clear()
  604         else:
  605             self.ops["--script-args"] = ",".join(self.arg_list)
  606         self.update_command()
  607 
  608     def selection_changed_cb(self, selection):
  609         """Called back when the list of scripts is selected."""
  610         model, selection = selection.get_selected_rows()
  611         for path in selection:
  612             entry = model.get_value(model.get_iter(path), 2)
  613             self.set_description(entry)
  614             self.populate_arg_list(entry)
  615             # Remember the currently pointing script entry
  616             self.focusedentry = entry
  617 
  618     def update_help_ls_cb(self, widget, extra):  # list of scripts
  619         """Callback method to display the help for the list of scripts."""
  620         self.help_buf.set_text(self.list_scripts_help)
  621 
  622     def update_help_desc_cb(self, widget, extra):
  623         """Callback method for displaying description."""
  624         self.help_buf.set_text(self.description_help)
  625 
  626     def update_help_arg_cb(self, treeview, event):
  627         """Callback method for displaying argument help."""
  628         wx, wy = treeview.get_pointer()
  629         try:
  630             x, y = treeview.convert_widget_to_bin_window_coords(wx, wy)
  631         except AttributeError:
  632             # convert_widget_to_bin_window_coords was introduced in PyGTK 2.12.
  633             return
  634         path = treeview.get_path_at_pos(x, y)
  635         if not path or not self.focusedentry:
  636             self.help_buf.set_text("")
  637             return
  638         path = path[0]
  639         model, selected = treeview.get_selection().get_selected()
  640         arg_name, arg_desc = model.get_value(model.get_iter(path), 2)
  641         if arg_desc is not None:
  642             self.help_buf.set_text("")
  643             self.help_buf.insert(
  644                     self.help_buf.get_end_iter(), text="%s\n" % arg_name)
  645             text_buffer_insert_nsedoc(self.help_buf, arg_desc)
  646         else:
  647             self.help_buf.set_text("")
  648 
  649     def add_file_button_clicked_cb(self, button):
  650         if self.script_file_chooser is None:
  651             self.script_file_chooser = \
  652                     zenmapGUI.FileChoosers.ScriptFileChooserDialog(
  653                             title=_("Select script files"))
  654         response = self.script_file_chooser.run()
  655         filenames = self.script_file_chooser.get_filenames()
  656         self.script_file_chooser.hide()
  657         if response != gtk.RESPONSE_OK:
  658             return
  659         for filename in filenames:
  660             self.file_liststore.append([filename, True])
  661         if len(self.file_liststore) > 0:
  662             self.file_scrolled_window.show()
  663             self.remove_file_button.set_sensitive(True)
  664         self.set_script_from_selection()
  665 
  666     def remove_file_button_clicked_cb(self, button):
  667         selection = self.file_listview.get_selection()
  668         model, selection = selection.get_selected_rows()
  669         for path in selection:
  670             self.file_liststore.remove(model.get_iter(path))
  671         if len(self.file_liststore) == 0:
  672             self.file_scrolled_window.hide()
  673             self.remove_file_button.set_sensitive(False)
  674         self.set_script_from_selection()
  675 
  676     def set_description(self, entry):
  677         """Sets the content that is to be displayed in the description box."""
  678         self.text_buffer.set_text(u"")
  679 
  680         self.text_buffer.insert(self.text_buffer.get_end_iter(), """\
  681 Categories: %(cats)s
  682 """ % {"cats": ", ".join(entry.categories)})
  683         text_buffer_insert_nsedoc(self.text_buffer, entry.description)
  684         if entry.usage:
  685             self.text_buffer.insert(
  686                     self.text_buffer.get_end_iter(), "\nUsage\n")
  687             self.text_buffer.insert_with_tags_by_name(
  688                     self.text_buffer.get_end_iter(), entry.usage, "Usage")
  689         if entry.output:
  690             self.text_buffer.insert(
  691                     self.text_buffer.get_end_iter(), "\nOutput\n")
  692             self.text_buffer.insert_with_tags_by_name(
  693                     self.text_buffer.get_end_iter(), entry.output, "Output")
  694         if entry.url:
  695             self.text_buffer.insert(
  696                     self.text_buffer.get_end_iter(), "\n" + entry.url)
  697 
  698     def populate_arg_list(self, entry):
  699         """Called when a particular script is hovered over to display its
  700         arguments and values (if any)."""
  701         self.arg_liststore.clear()
  702         self.current_arguments = []
  703         self.value.set_property('editable', True)
  704         for arg in entry.arguments:
  705             arg_name, arg_desc = arg
  706             self.current_arguments.append(arg)
  707             value = self.arg_values.get(arg_name)
  708             if not value:
  709                 self.arg_liststore.append([arg_name, None, arg])
  710             else:
  711                 self.arg_liststore.append([arg_name, value, arg])