#!/usr/bin/env python # ***********************IMPORTANT NMAP LICENSE TERMS************************ # * * # * The Nmap Security Scanner is (C) 1996-2011 Insecure.Com LLC. Nmap is * # * also a registered trademark of Insecure.Com LLC. This program is free * # * software; you may redistribute and/or modify it under the terms of the * # * GNU General Public License as published by the Free Software * # * Foundation; Version 2 with the clarifications and exceptions described * # * below. This guarantees your right to use, modify, and redistribute * # * this software under certain conditions. If you wish to embed Nmap * # * technology into proprietary software, we sell alternative licenses * # * (contact sales@insecure.com). Dozens of software vendors already * # * license Nmap technology such as host discovery, port scanning, OS * # * detection, and version detection. * # * * # * Note that the GPL places important restrictions on "derived works", yet * # * it does not provide a detailed definition of that term. To avoid * # * misunderstandings, we consider an application to constitute a * # * "derivative work" for the purpose of this license if it does any of the * # * following: * # * o Integrates source code from Nmap * # * o Reads or includes Nmap copyrighted data files, such as * # * nmap-os-db or nmap-service-probes. * # * o Executes Nmap and parses the results (as opposed to typical shell or * # * execution-menu apps, which simply display raw Nmap output and so are * # * not derivative works.) * # * o Integrates/includes/aggregates Nmap into a proprietary executable * # * installer, such as those produced by InstallShield. * # * o Links to a library or executes a program that does any of the above * # * * # * The term "Nmap" should be taken to also include any portions or derived * # * works of Nmap. This list is not exclusive, but is meant to clarify our * # * interpretation of derived works with some common examples. Our * # * interpretation applies only to Nmap--we don't speak for other people's * # * GPL works. * # * * # * If you have any questions about the GPL licensing restrictions on using * # * Nmap in non-GPL works, we would be happy to help. As mentioned above, * # * we also offer alternative license to integrate Nmap into proprietary * # * applications and appliances. These contracts have been sold to dozens * # * of software vendors, and generally include a perpetual license as well * # * as providing for priority support and updates as well as helping to * # * fund the continued development of Nmap technology. Please email * # * sales@insecure.com for further information. * # * * # * As a special exception to the GPL terms, Insecure.Com LLC grants * # * permission to link the code of this program with any version of the * # * OpenSSL library which is distributed under a license identical to that * # * listed in the included docs/licenses/OpenSSL.txt file, and distribute * # * linked combinations including the two. You must obey the GNU GPL in all * # * respects for all of the code used other than OpenSSL. If you modify * # * this file, you may extend this exception to your version of the file, * # * but you are not obligated to do so. * # * * # * If you received these files with a written license agreement or * # * contract stating terms other than the terms above, then that * # * alternative license agreement takes precedence over these comments. * # * * # * 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 (none * # * have been found so far). * # * * # * Source code also allows you to port Nmap to new platforms, fix bugs, * # * and add new features. You are highly encouraged to send your changes * # * to nmap-dev@insecure.org for possible incorporation into the main * # * distribution. By sending these changes to Fyodor or one of the * # * Insecure.Org development mailing lists, it is assumed that you are * # * offering the Nmap Project (Insecure.Com LLC) the unlimited, * # * non-exclusive right to reuse, modify, and relicense the code. Nmap * # * will always be available Open Source, but this is important because the * # * inability to relicense code has caused devastating problems for other * # * Free Software projects (such as KDE and NASM). We also occasionally * # * relicense the code to third parties as discussed above. If you wish to * # * specify special license conditions of your contributions, just say so * # * when you send them. * # * * # * This program 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. See the GNU * # * General Public License v2.0 for more details at * # * http://www.gnu.org/licenses/gpl-2.0.html , or in the COPYING file * # * included with Nmap. * # * * # ***************************************************************************/ # This module is responsible for interface present under "Scripting" tab. import gobject import gtk import sys import re from zenmapGUI.higwidgets.higwindows import HIGWindow from zenmapGUI.higwidgets.higboxes import HIGVBox, HIGHBox, HIGSpacer, hig_box_space_holder from zenmapGUI.higwidgets.higlabels import HIGSectionLabel, HIGEntryLabel from zenmapGUI.higwidgets.higscrollers import HIGScrolledWindow from zenmapGUI.higwidgets.higtextviewers import HIGTextView from zenmapGUI.higwidgets.higbuttons import HIGButton from zenmapGUI.higwidgets.higtables import HIGTable from zenmapGUI.higwidgets.higdialogs import HIGAlertDialog, HIGDialog from zenmapCore.ScriptMetadata import * from zenmapCore.ScriptArgsParser import parse_script_args_dict from zenmapCore.NmapCommand import NmapCommand from zenmapCore.NmapOptions import NmapOptions from zenmapCore.Paths import Path import zenmapCore.NSEDocParser import zenmapGUI.FileChoosers from zenmapCore.UmitConf import PathsConfig NMAPDATADIR = Path.nmap_dir paths_config = PathsConfig() def text_buffer_insert_nsedoc(buf, nsedoc): """Inserts NSEDoc at the end of the buffer, with markup turned into proper tags.""" if not buf.tag_table.lookup("NSEDOC_CODE_TAG"): buf.create_tag("NSEDOC_CODE_TAG", font = "Monospace") for event in zenmapCore.NSEDocParser.nsedoc_parse(nsedoc): if event.type == "paragraph_start": buf.insert(buf.get_end_iter(), "\n") elif event.type == "paragraph_end": buf.insert(buf.get_end_iter(), "\n") elif event.type == "list_start": buf.insert(buf.get_end_iter(), "\n") elif event.type == "list_end": pass elif event.type == "list_item_start": buf.insert(buf.get_end_iter(), u"\u2022\u00a0") # bullet nbsp elif event.type == "list_item_end": buf.insert(buf.get_end_iter(), "\n") elif event.type == "text": buf.insert(buf.get_end_iter(), event.text) elif event.type == "code": buf.insert_with_tags_by_name(buf.get_end_iter(), event.text, "NSEDOC_CODE_TAG") class ScriptInterface: # Timeout, in milliseconds, after the user stops typing and we update the # interface from --script. SCRIPT_LIST_DELAY = 500 NMAP_DELAY = 200 # Timer to execute Nmap CHK_NMAP_DELAY = 200 # Timer to check the version of nmap during start. def __init__(self,root_tabs,ops,update_command,help_buf): self.hmainbox = HIGHBox(False,0) self.notscripttab = False # to show profile editor that it is a script tab self.nmap_process = None self.chk_nmap_process = None self.script_list_timeout_id = None self.nmap_timeout_id = None self.chk_nmap_timeout_id = None self.nmap_stat = None self.script_entries = [] self.script_file_chooser = None self.ops = ops self.update_command = update_command self.help_buf = help_buf self.selected_scripts = [] self.arg_values = {} self.current_arguments = [] self.check_nmap() if self.ops["--script"]: self.update_script_list_from_spec(self.ops["--script"]) if self.ops["--script-args"]: self.update_argument_values(self.ops["--script-args"]) self.set_help_texts() self.vleftbox = HIGVBox(False,0) self.vrightbox = HIGVBox(False,5) self.create_list_scripts() self.create_description_box() self.create_arguments_window() self.hmainbox.pack_start(self.vleftbox,False,False,0) self.hmainbox.pack_end(self.vrightbox,True,True,0) self.prev_script_spec = None self.script_expr = re.compile(r"^NSE: Loaded '(.*)'") #creating Tags self.text_buffer.create_tag("Usage", font = "Monospace") self.text_buffer.create_tag("Output", font = "Monospace") self.text_buffer.create_tag("Error", font = "Monospace") self.focussedentry = None def init_nmap(self,rules): """This method starts nmap as a subprocess and sets the timer.""" if rules: ops = NmapOptions() ops.executable = paths_config.nmap_command_path ops["-d"] = 2 ops["--script"] = rules command_string = ops.render_string() self.nmap_process = NmapCommand(command_string) self.nmap_process.run_scan() else: self.selected_scripts = [] self.refresh_list_scripts() def chk_init_nmap(self,rules): """This method starts nmap as a subprocess and sets the timer.""" if rules is not None: ops = NmapOptions() ops.executable = paths_config.nmap_command_path ops["-d"] = 2 ops["--script"] = rules command_string = ops.render_string() self.chk_nmap_process = NmapCommand(command_string) self.chk_nmap_process.run_scan() def is_in_list(self,script_list,script_name): """Checks if the script_list already contains the script_name.""" for i in script_list: if i == script_name: return 1 return 0 def update_script_list_from_spec(self, spec): """Callback method for user edit delay.""" if spec: self.init_nmap(spec) if self.nmap_timeout_id: gobject.source_remove(self.nmap_timeout_id) self.nmap_timeout_id = gobject.timeout_add(self.NMAP_DELAY, self.nmap_parse_op) else: self.selected_scripts = [] self.refresh_list_scripts() def nmap_parse_op(self): """Call back method for Nmap subprocess.""" if self.nmap_process is not None: # recent sacn try: status = self.nmap_process.scan_state() except: status = None if status == True: #still running return True elif status == False: # success nmap_output = self.nmap_process.get_output() self.selected_scripts = [] for line in nmap_output.split("\n"): m = self.script_expr.match(line) if m: filename = m.group(1) self.selected_scripts.append(filename) self.refresh_list_scripts() return False else: # Exception. return False pass def get_hmain_box(self): """Returns main Hbox to ProfileEditor.""" return self.hmainbox def check_nmap(self): """This method will be called to check the version of nmap.""" self.chk_init_nmap("all") if self.chk_nmap_timeout_id: gobject.source_remove(self.chk_nmap_timeout_id) self.chk_nmap_timeout_id = gobject.timeout_add(self.CHK_NMAP_DELAY, self.check_nmap_cb) def check_nmap_cb(self): """This is the callback method for checking nmap version""" if self.chk_nmap_process is None: # Recent scan? return False try: status = self.chk_nmap_process.scan_state() except: status = None if status == True: #still running return True elif status == False: # success nmap_output = self.chk_nmap_process.get_output() for i in nmap_output.split("\n"): m = self.script_expr.search(i) if m is not None: self.script_entries = get_script_entries() self.refresh_list_scripts() return False else: # Exception. #Remove all the components self.hmainbox.remove(self.vleftbox) self.text_buffer.set_text(u"") tags = ["Error"] content = "\nTry updating to latest version of nmap.\nThe current version installed cannot support\nScript selection interface\n" self.text_buffer.insert_with_tags_by_name(self.text_buffer.get_end_iter(),content, *tags) self.vrightbox.remove(self.arg_window) self.vrightbox.remove(self.arglabel) return False def update(self): """Updates the interface when the command entry is changed.""" #updates list of scripts script_list = [] rules = self.ops["--script"] if (self.prev_script_spec != rules): self.renew_script_list_timer(rules) self.prev_script_spec = rules #updates arguments.. raw_argument = self.ops["--script-args"] if raw_argument is not None: self.parse_script_args(raw_argument) self.arg_liststore.clear() for arg in self.current_arguments: arg_name, arg_desc = arg value = self.arg_values.get(arg_name) if not value: self.arg_liststore.append([arg_name, None, arg]) else: self.arg_liststore.append([arg_name, value, arg]) def renew_script_list_timer(self, spec): """Restart the timer to update the script list when the user edits the command. Because updating the script list is an expensive operation involving the creation of a subprocess, we don't do it for every typed character.""" if self.script_list_timeout_id: gobject.source_remove(self.script_list_timeout_id) self.script_list_timeout_id = gobject.timeout_add(self.SCRIPT_LIST_DELAY, self.update_script_list_from_spec, spec) def parse_script_args(self,raw_argument): """When the command line is edited, this function is called to update the script arguments display according to the value of --script-args.""" arg_dict = parse_script_args_dict(raw_argument) if arg_dict is None: # if there is parsing error args_dict holds none self.arg_values.clear() else: for key in arg_dict.keys(): self.arg_values[key] = arg_dict[key] def update_argument_values(self,raw_argument): """When scripting tab starts up, argument values are updated.""" if raw_argument is not None: self.parse_script_args(raw_argument) def set_help_texts(self): """Sets the help texts to be displayed.""" self.list_scripts_help = _("List of scripts\n\nA list of all installed scripts. Activate or deactivate a script by clicking the box next to the script name.") self.description_help = _("Description\n\nThis box shows the categories a script belongs to. In addition, it gives a detailed description of the script which is present in script. A URL points to online NSEDoc documentation.") self.argument_help = _("Arguments\n\nA list of arguments that affect the selected script. Enter a value by clicking in the value field beside the argument name.") def create_list_scripts(self): """Creates and packs widgets associated with left hand side of Interface.""" self.scrolled_window = HIGScrolledWindow() self.scrolled_window.set_policy(gtk.POLICY_ALWAYS,gtk.POLICY_ALWAYS) self.scrolled_window.set_size_request(175,-1) # vertically it expands. self.previewentry = gtk.Entry() #create liststore self.liststore = gtk.ListStore(str,'gboolean',object) #self.refresh_list_scripts() #create listview self.listview = gtk.TreeView(self.liststore) self.listview.set_headers_visible(False) self.listview.connect("enter-notify-event",self.update_help_ls_cb) selection = self.listview.get_selection() selection.connect("changed",self.selection_changed_cb) #create cell renderer self.cell = gtk.CellRendererText() self.togglecell = gtk.CellRendererToggle() self.togglecell.set_property('activatable',True) self.togglecell.connect('toggled',self.toggled_cb,self.liststore) #create listview column self.col = gtk.TreeViewColumn(_('Names')) self.col.set_sizing(gtk.TREE_VIEW_COLUMN_GROW_ONLY) self.col.set_resizable(True) self.togglecol = gtk.TreeViewColumn(None, self.togglecell) self.togglecol.add_attribute(self.togglecell,"active",1) self.listview.append_column(self.togglecol) self.listview.append_column(self.col) self.col.pack_start(self.cell,True) self.col.add_attribute(self.cell,'text',0) self.scrolled_window.add(self.listview) self.scrolled_window.show() #self.vleftbox.pack_start(self.previewentry,False,False,5) self.vleftbox.pack_start(self.scrolled_window,True,True,0) self.file_scrolled_window = HIGScrolledWindow() self.file_scrolled_window.set_policy(gtk.POLICY_ALWAYS, gtk.POLICY_ALWAYS) self.file_scrolled_window.set_size_request(175, -1) self.file_liststore = gtk.ListStore(str, "gboolean") self.file_listview = gtk.TreeView(self.file_liststore) self.file_listview.set_headers_visible(False) self.file_liststore.connect("row-changed", self.file_store_changed_cb) self.file_liststore.connect("row-inserted", self.file_store_added_cb) self.file_liststore.connect("row-deleted", self.file_store_removed_cb) col = gtk.TreeViewColumn(None) self.file_listview.append_column(col) cell = gtk.CellRendererToggle() col.pack_start(cell, True) cell.set_property("activatable", True) col.add_attribute(cell, "active", 1) cell.connect("toggled", self.toggled_cb, self.file_liststore) col = gtk.TreeViewColumn(None) self.file_listview.append_column(col) cell = gtk.CellRendererText() col.pack_start(cell) col.add_attribute(cell, "text", 0) self.file_scrolled_window.add(self.file_listview) self.vleftbox.pack_start(self.file_scrolled_window, False) #create file buttons hbox = HIGHBox(False, 2) self.remove_file_button = HIGButton(stock = gtk.STOCK_REMOVE) self.remove_file_button.connect('clicked', self.remove_file_button_clicked_cb) self.remove_file_button.set_sensitive(False) hbox.pack_end(self.remove_file_button) self.add_file_button = HIGButton(stock = gtk.STOCK_ADD) self.add_file_button.connect('clicked', self.add_file_button_clicked_cb) hbox.pack_end(self.add_file_button) self.vleftbox.pack_start(hbox, False, False, 0) self.file_listview.show_all() self.file_scrolled_window.hide() self.file_scrolled_window.set_no_show_all(True) self.remove_file_button.set_sensitive(False) def refresh_list_scripts(self): """The list of selected scripts is refreshed in the list store.""" self.liststore.clear() selected_scripts = set(self.selected_scripts) scriptdb_selected = set(self.selected_scripts) for entry in self.script_entries: scriptname = self.strip_file_name(entry.filename) for filename in selected_scripts: if entry.filename == os.path.basename(filename): self.liststore.append([scriptname,True,entry]) selected_scripts.remove(filename) break else: self.liststore.append([scriptname,False,entry]) for filename in selected_scripts: for row in self.file_liststore: if row[0] == filename: row[1] = True break else: self.file_liststore.append([filename, True]) def strip_file_name(self,filename): """Removes a ".nse" extension from filename if present.""" if(filename.endswith(".nse")): return filename[:-4] else: return filename def set_script_from_selection(self): scriptsname = [] for entry in self.liststore: if entry[1]: scriptsname.append(self.strip_file_name(entry[0])) for entry in self.file_liststore: if entry[1]: scriptsname.append(entry[0]) if len(scriptsname) == 0: self.ops["--script"] = None else: self.ops["--script"] = ",".join(scriptsname) self.update_command() def toggled_cb(self, cell, path, model): """Callback method, called when the check box in list of scripts is toggled.""" model[path][1] = not model[path][1] self.set_script_from_selection() def create_description_box(self): """Creates and packs widgets related to displaying the description box.""" self.label = gtk.Label(_("Description")) self.label.set_justify(gtk.JUSTIFY_LEFT) #self.vrightbox.pack_start(self.label,False,False,0) self.sw = HIGScrolledWindow() self.sw.set_policy(gtk.POLICY_AUTOMATIC,gtk.POLICY_ALWAYS) self.sw.set_shadow_type(gtk.SHADOW_OUT) self.sw.set_border_width(5) #text view self.text_view = gtk.TextView() self.text_view.connect('enter-notify-event',self.update_help_desc_cb) self.text_buffer = self.text_view.get_buffer() self.text_view.set_wrap_mode(gtk.WRAP_WORD) self.text_view.set_editable(False) self.text_view.set_justification(gtk.JUSTIFY_LEFT) self.sw.add(self.text_view) self.vrightbox.pack_start(self.sw,True,True,0) def create_arguments_window(self): """Creates and packs widgets related to arguments box.""" self.arglabel = gtk.Label(_("Arguments")) self.label.set_justify(gtk.JUSTIFY_LEFT) self.vrightbox.pack_start(self.arglabel,False,False,0) self.arg_window = HIGScrolledWindow() self.arg_window.set_policy(gtk.POLICY_AUTOMATIC,gtk.POLICY_ALWAYS) self.arg_window.set_shadow_type(gtk.SHADOW_OUT) self.create_arg_list_store() self.arg_window.add(self.arg_listview) self.vrightbox.pack_start(self.arg_window,True,True,0) def create_arg_list_store(self): """ListStore for displaying arguments and their values.""" # Arg name, arg value, (name, desc) tuple. self.arg_liststore = gtk.ListStore(str, str, object) self.arg_listview = gtk.TreeView(self.arg_liststore) #help self.arg_listview.connect("motion-notify-event",self.update_help_arg_cb) #cell renderer self.argument = gtk.CellRendererText() self.value = gtk.CellRendererText() self.value.connect("edited",self.value_edited_cb,self.arg_liststore) #treeview column self.arg_col = gtk.TreeViewColumn("Arguments\t") self.val_col = gtk.TreeViewColumn("values") self.arg_listview.append_column(self.arg_col) self.arg_listview.append_column(self.val_col) self.arg_col.pack_start(self.argument,True) self.arg_col.add_attribute(self.argument,'text',0) self.val_col.pack_start(self.value,True) self.val_col.add_attribute(self.value,'text',1) def value_edited_cb(self,cell,path,new_text,model): """Called when the argument cell is edited.""" self.arg_list = [] model[path][1] = new_text argument_name = model[path][0] self.arg_values[argument_name] = new_text self.update_arg_values() def update_arg_values(self): """When the widget is updated with argument value, correspondingly update the command line.""" for key in self.arg_values.keys(): if len(self.arg_values[key]) == 0: del self.arg_values[key] else: self.arg_list.append(key + "=" + self.arg_values[key]) if len(self.arg_list) == 0: self.ops["--script-args"] = None self.arg_values.clear() else: self.ops["--script-args"] = ",".join(self.arg_list) self.update_command() def selection_changed_cb(self,selection): """Called back when the list of scripts is selected.""" model, selection = selection.get_selected_rows() for path in selection: check_value = model.get_value(model.get_iter(path),1) entry = model.get_value(model.get_iter(path),2) self.set_description(entry) self.populate_arg_list(entry) self.focussedentry = entry #Remember the currently pointing script entry def file_store_changed_cb(self, model, path, iter): self.set_script_from_selection() def file_store_added_cb(self, model, path, iter): self.file_scrolled_window.show() self.remove_file_button.set_sensitive(True) self.set_script_from_selection() def file_store_removed_cb(self, model, iter): if len(model) == 0: self.file_scrolled_window.hide() self.remove_file_button.set_sensitive(False) self.set_script_from_selection() def update_help_ls_cb(self,widget,extra): # list of scripts """Callback method to display the help for the list of scripts.""" self.help_buf.set_text(self.list_scripts_help) def update_help_desc_cb(self,widget,extra): """Callback method for displaying description.""" self.help_buf.set_text(self.description_help) def update_help_arg_cb(self, treeview, event): """Callback method for displaying argument help.""" wx, wy = treeview.get_pointer() try: x, y = treeview.convert_widget_to_bin_window_coords(wx, wy) except AttributeError: # convert_widget_to_bin_window_coords was introduced in PyGTK 2.12. return path = treeview.get_path_at_pos(x, y) if not path or not self.focussedentry: self.help_buf.set_text("") return path = path[0] model, selected = treeview.get_selection().get_selected() arg_name, arg_desc = model.get_value(model.get_iter(path), 2) if arg_desc is not None: self.help_buf.set_text("") self.help_buf.insert(self.help_buf.get_end_iter(), text = "%s\n" % arg_name) text_buffer_insert_nsedoc(self.help_buf, arg_desc) else: self.help_buf.set_text("") def add_file_button_clicked_cb(self, button): if self.script_file_chooser is None: self.script_file_chooser = zenmapGUI.FileChoosers.ScriptFileChooserDialog(title = _("Select script files")) response = self.script_file_chooser.run() filenames = self.script_file_chooser.get_filenames() self.script_file_chooser.hide() if response != gtk.RESPONSE_OK: return for filename in filenames: self.file_liststore.append([filename, True]) def remove_file_button_clicked_cb(self, button): selection = self.file_listview.get_selection() model, selection = selection.get_selected_rows() for path in selection: self.file_liststore.remove(model.get_iter(path)) def set_description(self,entry): """Sets the content that is to be displayed in the description box.""" self.text_buffer.set_text(u"") self.text_buffer.insert(self.text_buffer.get_end_iter(), """\ Categories: %(cats)s """ % {"cats": ", ".join(entry.categories)}) text_buffer_insert_nsedoc(self.text_buffer, entry.description) if entry.usage: self.text_buffer.insert(self.text_buffer.get_end_iter(), "\nUsage\n") self.text_buffer.insert_with_tags_by_name(self.text_buffer.get_end_iter(), entry.usage, "Usage") if entry.output: self.text_buffer.insert(self.text_buffer.get_end_iter(), "\nOutput\n") self.text_buffer.insert_with_tags_by_name(self.text_buffer.get_end_iter(), entry.output, "Output") if entry.url: self.text_buffer.insert(self.text_buffer.get_end_iter(), "\n" + entry.url) def populate_arg_list(self,entry): """Called when a particular script is hovered over to display its arguments and values (if any).""" self.arg_liststore.clear() self.current_arguments = [] self.value.set_property('editable',True) for arg in entry.arguments: arg_name, arg_desc = arg self.current_arguments.append(arg) value = self.arg_values.get(arg_name) if not value: self.arg_liststore.append([arg_name, None, arg]) else: self.arg_liststore.append([arg_name, value, arg])