1#!/usr/bin/env python 2# -*- coding: utf-8 -*- 3 4# ***********************IMPORTANT NMAP LICENSE TERMS************************ 5# * * 6# * The Nmap Security Scanner is (C) 1996-2020 Insecure.Com LLC ("The Nmap * 7# * Project"). Nmap is also a registered trademark of the Nmap Project. * 8# * * 9# * This program is distributed under the terms of the Nmap Public Source * 10# * License (NPSL). The exact license text applying to a particular Nmap * 11# * release or source code control revision is contained in the LICENSE * 12# * file distributed with that version of Nmap or source code control * 13# * revision. More Nmap copyright/legal information is available from * 14# * https://nmap.org/book/man-legal.html, and further information on the * 15# * NPSL license itself can be found at https://nmap.org/npsl. This header * 16# * summarizes some key points from the Nmap license, but is no substitute * 17# * for the actual license text. * 18# * * 19# * Nmap is generally free for end users to download and use themselves, * 20# * including commercial use. It is available from https://nmap.org. * 21# * * 22# * The Nmap license generally prohibits companies from using and * 23# * redistributing Nmap in commercial products, but we sell a special Nmap * 24# * OEM Edition with a more permissive license and special features for * 25# * this purpose. See https://nmap.org/oem * 26# * * 27# * If you have received a written Nmap license agreement or contract * 28# * stating terms other than these (such as an Nmap OEM license), you may * 29# * choose to use and redistribute Nmap under those terms instead. * 30# * * 31# * The official Nmap Windows builds include the Npcap software * 32# * (https://npcap.org) for packet capture and transmission. It is under * 33# * separate license terms which forbid redistribution without special * 34# * permission. So the official Nmap Windows builds may not be * 35# * redistributed without special permission (such as an Nmap OEM * 36# * license). * 37# * * 38# * Source is provided to this software because we believe users have a * 39# * right to know exactly what a program is going to do before they run it. * 40# * This also allows you to audit the software for security holes. * 41# * * 42# * Source code also allows you to port Nmap to new platforms, fix bugs, * 43# * and add new features. You are highly encouraged to submit your * 44# * changes as a Github PR or by email to the dev@nmap.org mailing list * 45# * for possible incorporation into the main distribution. Unless you * 46# * specify otherwise, it is understood that you are offering us very * 47# * broad rights to use your submissions as described in the Nmap Public * 48# * Source License Contributor Agreement. This is important because we * 49# * fund the project by selling licenses with various terms, and also * 50# * because the inability to relicense code has caused devastating * 51# * problems for other Free Software projects (such as KDE and NASM). * 52# * * 53# * The free version of Nmap is distributed in the hope that it will be * 54# * useful, but WITHOUT ANY WARRANTY; without even the implied warranty of * 55# * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. Warranties, * 56# * indemnification and commercial support are all available through the * 57# * Npcap OEM program--see https://nmap.org/oem. * 58# * * 59# ***************************************************************************/ 60 61import gtk 62 63import sys 64import os 65from os.path import split, isfile, join, abspath 66 67# Prevent loading PyXML 68import xml 69xml.__path__ = [x for x in xml.__path__ if "_xmlplus" not in x] 70 71import xml.sax.saxutils 72 73from zenmapGUI.higwidgets.higwindows import HIGMainWindow 74from zenmapGUI.higwidgets.higdialogs import HIGDialog, HIGAlertDialog 75from zenmapGUI.higwidgets.higlabels import HIGEntryLabel 76from zenmapGUI.higwidgets.higboxes import HIGHBox, HIGVBox 77 78import zenmapGUI.App 79from zenmapGUI.FileChoosers import RESPONSE_OPEN_DIRECTORY, \ 80 ResultsFileChooserDialog, SaveResultsFileChooserDialog, \ 81 SaveToDirectoryChooserDialog 82from zenmapGUI.ScanInterface import ScanInterface 83from zenmapGUI.ProfileEditor import ProfileEditor 84from zenmapGUI.About import About 85from zenmapGUI.DiffCompare import DiffWindow 86from zenmapGUI.SearchWindow import SearchWindow 87from zenmapGUI.BugReport import BugReport 88 89from zenmapCore.Name import APP_DISPLAY_NAME, APP_DOCUMENTATION_SITE 90from zenmapCore.BasePaths import fs_enc 91from zenmapCore.Paths import Path 92from zenmapCore.RecentScans import recent_scans 93from zenmapCore.UmitLogging import log 94import zenmapCore.I18N # lgtm[py/unused-import] 95import zenmapGUI.Print 96from zenmapCore.UmitConf import SearchConfig, is_maemo, WindowConfig, config_parser 97 98UmitScanWindow = None 99hildon = None 100 101if is_maemo(): 102 import hildon 103 104 class UmitScanWindow(hildon.Window): 105 def __init__(self): 106 hildon.Window.__init__(self) 107 self.set_resizable(False) 108 self.set_border_width(0) 109 self.vbox = gtk.VBox() 110 self.vbox.set_border_width(0) 111 self.vbox.set_spacing(0) 112 113else: 114 class UmitScanWindow(HIGMainWindow): 115 def __init__(self): 116 HIGMainWindow.__init__(self) 117 self.vbox = gtk.VBox() 118 119 120def can_print(): 121 """Return true if we have printing operations (PyGTK 2.10 or later) or 122 false otherwise.""" 123 try: 124 gtk.PrintOperation 125 except AttributeError: 126 return False 127 else: 128 return True 129 130 131class ScanWindow(UmitScanWindow): 132 def __init__(self): 133 UmitScanWindow.__init__(self) 134 135 window = WindowConfig() 136 137 self.set_title(_(APP_DISPLAY_NAME)) 138 self.move(window.x, window.y) 139 self.set_default_size(window.width, window.height) 140 141 self.scan_interface = ScanInterface() 142 143 self.main_accel_group = gtk.AccelGroup() 144 145 self.add_accel_group(self.main_accel_group) 146 147 # self.vbox is a container for the menubar and the scan interface 148 self.add(self.vbox) 149 150 self.connect('delete-event', self._exit_cb) 151 self._create_ui_manager() 152 self._create_menubar() 153 self._create_scan_interface() 154 155 self._results_filechooser_dialog = None 156 self._about_dialog = None 157 158 def _create_ui_manager(self): 159 """Creates the UI Manager and a default set of actions, and builds 160 the menus using those actions.""" 161 self.ui_manager = gtk.UIManager() 162 163 # See info on ActionGroup at: 164 # * http://www.pygtk.org/pygtk2reference/class-gtkactiongroup.html 165 # * http://www.gtk.org/api/2.6/gtk/GtkActionGroup.html 166 self.main_action_group = gtk.ActionGroup('MainActionGroup') 167 168 # See info on Action at: 169 # * http://www.pygtk.org/pygtk2reference/class-gtkaction.html 170 # * http://www.gtk.org/api/2.6/gtk/GtkAction.html 171 172 # Each action tuple can go from 1 to six fields, example: 173 # ('Open Scan Results', -> Name of the action 174 # gtk.STOCK_OPEN, -> 175 # _('_Open Scan Results'), -> 176 # None, 177 # _('Open the results of a previous scan'), 178 # lambda x: True) 179 180 # gtk.STOCK_ABOUT is only available in PyGTK 2.6 and later. 181 try: 182 about_icon = gtk.STOCK_ABOUT 183 except AttributeError: 184 about_icon = None 185 186 self.main_actions = [ 187 # Top level 188 ('Scan', None, _('Sc_an'), None), 189 190 ('Save Scan', 191 gtk.STOCK_SAVE, 192 _('_Save Scan'), 193 None, 194 _('Save current scan results'), 195 self._save_scan_results_cb), 196 197 ('Save All Scans to Directory', 198 gtk.STOCK_SAVE, 199 _('Save All Scans to _Directory'), 200 "<Control><Alt>s", 201 _('Save all scans into a directory'), 202 self._save_to_directory_cb), 203 204 ('Open Scan', 205 gtk.STOCK_OPEN, 206 _('_Open Scan'), 207 None, 208 _('Open the results of a previous scan'), 209 self._load_scan_results_cb), 210 211 ('Append Scan', 212 gtk.STOCK_ADD, 213 _('_Open Scan in This Window'), 214 None, 215 _('Append a saved scan to the list of scans in this window.'), 216 self._append_scan_results_cb), 217 218 219 ('Tools', None, _('_Tools'), None), 220 221 ('New Window', 222 gtk.STOCK_NEW, 223 _('_New Window'), 224 "<Control>N", 225 _('Open a new scan window'), 226 self._new_scan_cb), 227 228 ('Close Window', 229 gtk.STOCK_CLOSE, 230 _('Close Window'), 231 "<Control>w", 232 _('Close this scan window'), 233 self._exit_cb), 234 235 ('Print...', 236 gtk.STOCK_PRINT, 237 _('Print...'), 238 None, 239 _('Print the current scan'), 240 self._print_cb), 241 242 ('Quit', 243 gtk.STOCK_QUIT, 244 _('Quit'), 245 "<Control>q", 246 _('Quit the application'), 247 self._quit_cb), 248 249 ('New Profile', 250 gtk.STOCK_JUSTIFY_LEFT, 251 _('New _Profile or Command'), 252 '<Control>p', 253 _('Create a new scan profile using the current command'), 254 self._new_scan_profile_cb), 255 256 ('Search Scan', 257 gtk.STOCK_FIND, 258 _('Search Scan Results'), 259 '<Control>f', 260 _('Search for a scan result'), 261 self._search_scan_result), 262 263 ('Filter Hosts', 264 gtk.STOCK_FIND, 265 _('Filter Hosts'), 266 '<Control>l', 267 _('Search for host by criteria'), 268 self._filter_cb), 269 270 ('Edit Profile', 271 gtk.STOCK_PROPERTIES, 272 _('_Edit Selected Profile'), 273 '<Control>e', 274 _('Edit selected scan profile'), 275 self._edit_scan_profile_cb), 276 277 # Top Level 278 ('Profile', None, _('_Profile'), None), 279 280 ('Compare Results', 281 gtk.STOCK_DND_MULTIPLE, 282 _('Compare Results'), 283 "<Control>D", 284 _('Compare Scan Results using Diffies'), 285 self._load_diff_compare_cb), 286 287 288 # Top Level 289 ('Help', None, _('_Help'), None), 290 291 ('Report a bug', 292 gtk.STOCK_DIALOG_INFO, 293 _('_Report a bug'), 294 '<Control>b', 295 _("Report a bug"), 296 self._show_bug_report 297 ), 298 299 ('About', 300 about_icon, 301 _('_About'), 302 None, 303 _("About %s") % APP_DISPLAY_NAME, 304 self._show_about_cb 305 ), 306 307 ('Show Help', 308 gtk.STOCK_HELP, 309 _('_Help'), 310 None, 311 _('Shows the application help'), 312 self._help_cb), 313 ] 314 315 # See info on UIManager at: 316 # * http://www.pygtk.org/pygtk2reference/class-gtkuimanager.html 317 # * http://www.gtk.org/api/2.6/gtk/GtkUIManager.html 318 319 # UIManager supports UI "merging" and "unmerging". So, suppose there's 320 # no scan running or scan results opened, we should have a minimal 321 # interface. When we one scan running, we should "merge" the scan UI. 322 # When we get multiple tabs opened, we might merge the tab UI. 323 324 # This is the default, minimal UI 325 self.default_ui = """<menubar> 326 <menu action='Scan'> 327 <menuitem action='New Window'/> 328 <menuitem action='Open Scan'/> 329 <menuitem action='Append Scan'/> 330 %s 331 <separator/> 332 <menuitem action='Save Scan'/> 333 <menuitem action='Save All Scans to Directory'/> 334 """ 335 if can_print(): 336 self.default_ui += """ 337 <separator/> 338 <menuitem action='Print...'/> 339 """ 340 self.default_ui += """ 341 <separator/> 342 <menuitem action='Close Window'/> 343 <menuitem action='Quit'/> 344 </menu> 345 346 <menu action='Tools'> 347 <menuitem action='Compare Results'/> 348 <menuitem action='Search Scan'/> 349 <menuitem action='Filter Hosts'/> 350 </menu> 351 352 <menu action='Profile'> 353 <menuitem action='New Profile'/> 354 <menuitem action='Edit Profile'/> 355 </menu> 356 357 <menu action='Help'> 358 <menuitem action='Show Help'/> 359 <menuitem action='Report a bug'/> 360 <menuitem action='About'/> 361 </menu> 362 363 </menubar> 364 """ 365 366 self.get_recent_scans() 367 368 self.main_action_group.add_actions(self.main_actions) 369 370 for action in self.main_action_group.list_actions(): 371 action.set_accel_group(self.main_accel_group) 372 action.connect_accelerator() 373 374 self.ui_manager.insert_action_group(self.main_action_group, 0) 375 self.ui_manager.add_ui_from_string(self.default_ui) 376 377 def _show_bug_report(self, widget): 378 """Displays a 'How to report a bug' window.""" 379 bug = BugReport() 380 bug.show_all() 381 382 def _search_scan_result(self, widget): 383 """Displays a search window.""" 384 search_window = SearchWindow( 385 self._load_search_result, self._append_search_result) 386 search_window.show_all() 387 388 def _filter_cb(self, widget): 389 self.scan_interface.toggle_filter_bar() 390 391 def _load_search_result(self, results): 392 """This function is passed as an argument to the SearchWindow.__init__ 393 method. When the user selects scans in the search window and clicks on 394 "Open", this function is called to load each of the selected scans into 395 a new window.""" 396 for result in results: 397 self._load(self.get_empty_interface(), 398 parsed_result=results[result][1]) 399 400 def _append_search_result(self, results): 401 """This function is passed as an argument to the SearchWindow.__init__ 402 method. When the user selects scans in the search window and clicks on 403 "Append", this function is called to append the selected scans into the 404 current window.""" 405 for result in results: 406 self._load(self.scan_interface, parsed_result=results[result][1]) 407 408 def store_result(self, scan_interface): 409 """Stores the network inventory into the database.""" 410 log.debug(">>> Saving result into database...") 411 try: 412 scan_interface.inventory.save_to_db() 413 except Exception, e: 414 alert = HIGAlertDialog( 415 message_format=_("Can't save to database"), 416 secondary_text=_("Can't store unsaved scans to the " 417 "recent scans database:\n%s") % str(e)) 418 alert.run() 419 alert.destroy() 420 log.debug(">>> Can't save result to database: %s." % str(e)) 421 422 def get_recent_scans(self): 423 """Gets seven most recent scans and appends them to the default UI 424 definition.""" 425 r_scans = recent_scans.get_recent_scans_list() 426 new_rscan_xml = '' 427 428 for scan in r_scans[:7]: 429 scan = scan.replace('\n', '') 430 if os.access(split(scan)[0], os.R_OK) and isfile(scan): 431 scan = scan.replace('\n', '') 432 new_rscan = ( 433 scan, None, scan, None, scan, self._load_recent_scan) 434 new_rscan_xml += "<menuitem action=%s/>\n" % ( 435 xml.sax.saxutils.quoteattr(scan)) 436 437 self.main_actions.append(new_rscan) 438 439 new_rscan_xml += "<separator />\n" 440 441 self.default_ui %= new_rscan_xml 442 443 def _create_menubar(self): 444 # Get and pack the menubar 445 menubar = self.ui_manager.get_widget('/menubar') 446 447 if is_maemo(): 448 menu = gtk.Menu() 449 for child in menubar.get_children(): 450 child.reparent(menu) 451 self.set_menu(menu) 452 menubar.destroy() 453 self.menubar = menu 454 else: 455 self.menubar = menubar 456 self.vbox.pack_start(self.menubar, False, False, 0) 457 458 self.menubar.show_all() 459 460 def _create_scan_interface(self): 461 notebook = self.scan_interface.scan_result.scan_result_notebook 462 notebook.scans_list.append_button.connect( 463 "clicked", self._append_scan_results_cb) 464 notebook.nmap_output.connect("changed", self._displayed_scan_change_cb) 465 self._displayed_scan_change_cb(None) 466 self.scan_interface.show_all() 467 self.vbox.pack_start(self.scan_interface, True, True, 0) 468 469 def show_open_dialog(self, title=None): 470 """Show a load file chooser and return the filename chosen.""" 471 if self._results_filechooser_dialog is None: 472 self._results_filechooser_dialog = ResultsFileChooserDialog( 473 title=title) 474 475 filename = None 476 response = self._results_filechooser_dialog.run() 477 if response == gtk.RESPONSE_OK: 478 filename = self._results_filechooser_dialog.get_filename() 479 elif response == RESPONSE_OPEN_DIRECTORY: 480 filename = self._results_filechooser_dialog.get_filename() 481 482 # Check if the selected filename is a directory. If not, we take 483 # only the directory part of the path, omitting the actual name of 484 # the selected file. 485 if filename is not None and not os.path.isdir(filename): 486 filename = os.path.dirname(filename) 487 488 self._results_filechooser_dialog.hide() 489 return filename 490 491 def _load_scan_results_cb(self, p): 492 """'Open Scan' callback function. Displays a file chooser dialog and 493 loads the scan from the selected file or from the selected 494 directory.""" 495 filename = self.show_open_dialog(p.get_name()) 496 if filename is not None: 497 scan_interface = self.get_empty_interface() 498 if os.path.isdir(filename): 499 self._load_directory(scan_interface, filename) 500 else: 501 self._load(scan_interface, filename) 502 503 def _append_scan_results_cb(self, p): 504 """'Append Scan' callback function. Displays a file chooser dialog and 505 appends the scan from the selected file into the current window.""" 506 filename = self.show_open_dialog(p.get_name()) 507 if filename is not None: 508 if os.path.isdir(filename): 509 self._load_directory(self.scan_interface, filename) 510 else: 511 self._load(self.scan_interface, filename) 512 513 def _displayed_scan_change_cb(self, widget): 514 """Called when the currently shown scan output is changed.""" 515 # Set the Print... menu item sensitive if there is something to print. 516 widget = self.ui_manager.get_widget("/ui/menubar/Scan/Print...") 517 if widget is None: 518 # Don't have a Print menu item for lack of support. 519 return 520 entry = self.scan_interface.scan_result.scan_result_notebook.nmap_output.get_active_entry() # noqa 521 widget.set_sensitive(entry is not None) 522 523 def _load_recent_scan(self, widget): 524 """A helper function for loading a recent scan directly from the 525 menu.""" 526 self._load(self.get_empty_interface(), widget.get_name()) 527 528 def _load(self, scan_interface, filename=None, parsed_result=None): 529 """Loads the scan from a file or from a parsed result into the given 530 scan interface.""" 531 if not (filename or parsed_result): 532 return None 533 534 if filename: 535 # Load scan result from file 536 log.debug(">>> Loading file: %s" % filename) 537 try: 538 # Parse result 539 scan_interface.load_from_file(filename) 540 except Exception, e: 541 alert = HIGAlertDialog(message_format=_('Error loading file'), 542 secondary_text=str(e)) 543 alert.run() 544 alert.destroy() 545 return 546 scan_interface.saved_filename = filename 547 elif parsed_result: 548 # Load scan result from parsed object 549 scan_interface.load_from_parsed_result(parsed_result) 550 551 def _load_directory(self, scan_interface, directory): 552 for file in os.listdir(directory): 553 if os.path.isdir(os.path.join(directory, file)): 554 continue 555 self._load(scan_interface, filename=os.path.join(directory, file)) 556 557 def _save_scan_results_cb(self, widget): 558 """'Save Scan' callback function. If it's OK to save the scan, it 559 displays a 'Save File' dialog and saves the scan. If not, it displays 560 an appropriate alert dialog.""" 561 num_scans = len(self.scan_interface.inventory.get_scans()) 562 if num_scans == 0: 563 alert = HIGAlertDialog( 564 message_format=_('Nothing to save'), 565 secondary_text=_( 566 'There are no scans with results to be saved. ' 567 'Run a scan with the "Scan" button first.')) 568 alert.run() 569 alert.destroy() 570 return 571 num_scans_running = self.scan_interface.num_scans_running() 572 if num_scans_running > 0: 573 if num_scans_running == 1: 574 text = _("There is a scan still running. " 575 "Wait until it finishes and then save.") 576 else: 577 text = _("There are %u scans still running. Wait until they " 578 "finish and then save.") % num_scans_running 579 alert = HIGAlertDialog(message_format=_('Scan is running'), 580 secondary_text=text) 581 alert.run() 582 alert.destroy() 583 return 584 585 # If there's more than one scan in the inventory, display a warning 586 # dialog saying that only the most recent scan will be saved 587 selected = 0 588 if num_scans > 1: 589 #text = _("You have %u scans loaded in the current view. " 590 # "Only the most recent scan will be saved." % num_scans) 591 #alert = HIGAlertDialog( 592 # message_format=_("More than one scan loaded"), 593 # secondary_text=text) 594 #alert.run() 595 #alert.destroy() 596 dlg = HIGDialog( 597 title="Choose a scan to save", 598 parent=self, 599 flags=gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT, 600 buttons=(gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL, 601 gtk.STOCK_SAVE, gtk.RESPONSE_OK)) 602 dlg.vbox.pack_start(gtk.Label( 603 "You have %u scans loaded in the current view.\n" 604 "Select the scan which you would like to save." % num_scans), 605 False) 606 scan_combo = gtk.combo_box_new_text() 607 for scan in self.scan_interface.inventory.get_scans(): 608 scan_combo.append_text(scan.nmap_command) 609 scan_combo.set_active(0) 610 dlg.vbox.pack_start(scan_combo, False) 611 dlg.vbox.show_all() 612 if dlg.run() == gtk.RESPONSE_OK: 613 selected = scan_combo.get_active() 614 dlg.destroy() 615 else: 616 dlg.destroy() 617 return 618 619 # Show the dialog to choose the path to save scan result 620 self._save_results_filechooser_dialog = \ 621 SaveResultsFileChooserDialog(title=_('Save Scan')) 622 # Supply a default file name if this scan was previously saved. 623 if self.scan_interface.saved_filename: 624 self._save_results_filechooser_dialog.set_filename( 625 self.scan_interface.saved_filename) 626 627 response = self._save_results_filechooser_dialog.run() 628 629 filename = None 630 if (response == gtk.RESPONSE_OK): 631 filename = self._save_results_filechooser_dialog.get_filename() 632 format = self._save_results_filechooser_dialog.get_format() 633 # add .xml to filename if there is no other extension 634 if filename.find('.') == -1: 635 filename += ".xml" 636 self._save(self.scan_interface, filename, selected, format) 637 638 self._save_results_filechooser_dialog.destroy() 639 self._save_results_filechooser_dialog = None 640 641 def _save_to_directory_cb(self, widget): 642 if self.scan_interface.empty: 643 alert = HIGAlertDialog(message_format=_('Nothing to save'), 644 secondary_text=_('\ 645This scan has not been run yet. Start the scan with the "Scan" button first.')) 646 alert.run() 647 alert.destroy() 648 return 649 num_scans_running = self.scan_interface.num_scans_running() 650 if num_scans_running > 0: 651 if num_scans_running == 1: 652 text = _("There is a scan still running. " 653 "Wait until it finishes and then save.") 654 else: 655 text = _("There are %u scans still running. Wait until they " 656 "finish and then save.") % num_scans_running 657 alert = HIGAlertDialog(message_format=_('Scan is running'), 658 secondary_text=text) 659 alert.run() 660 alert.destroy() 661 return 662 663 # We have multiple scans in our network inventory, so we need to 664 # display a directory chooser dialog 665 dir_chooser = SaveToDirectoryChooserDialog( 666 title=_("Choose a directory to save scans into")) 667 if dir_chooser.run() == gtk.RESPONSE_OK: 668 self._save_all(self.scan_interface, dir_chooser.get_filename()) 669 dir_chooser.destroy() 670 671 def _about_cb_response(self, dialog, response_id): 672 if response_id == gtk.RESPONSE_DELETE_EVENT: 673 self._about_dialog = None 674 else: 675 self._about_dialog.hide() 676 677 def _show_about_cb(self, widget): 678 if self._about_dialog is None: 679 self._about_dialog = About() 680 self._about_dialog.connect("response", self._about_cb_response) 681 self._about_dialog.present() 682 683 def _save_all(self, scan_interface, directory): 684 """Saves all scans in saving_page's inventory to a given directory. 685 Displays an alert dialog if the save fails.""" 686 try: 687 filenames = scan_interface.inventory.save_to_dir(directory) 688 for scan in scan_interface.inventory.get_scans(): 689 scan.unsaved = False 690 except Exception, ex: 691 alert = HIGAlertDialog(message_format=_('Can\'t save file'), 692 secondary_text=str(ex)) 693 alert.run() 694 alert.destroy() 695 else: 696 scan_interface.saved_filename = directory 697 698 # Saving recent scan information 699 try: 700 for filename in filenames: 701 recent_scans.add_recent_scan(filename) 702 recent_scans.save() 703 except (OSError, IOError), e: 704 alert = HIGAlertDialog( 705 message_format=_( 706 "Can't save recent scan information"), 707 secondary_text=_( 708 "Can't open file to write.\n%s") % str(e)) 709 alert.run() 710 alert.destroy() 711 712 def _save(self, scan_interface, saved_filename, selected_index, 713 format="xml"): 714 """Saves the scan into a file with a given filename. Displays an alert 715 dialog if the save fails.""" 716 log.debug(">>> File being saved: %s" % saved_filename) 717 try: 718 scan_interface.inventory.save_to_file( 719 saved_filename, selected_index, format) 720 scan_interface.inventory.get_scans()[selected_index].unsaved = False # noqa 721 except (OSError, IOError), e: 722 alert = HIGAlertDialog( 723 message_format=_("Can't save file"), 724 secondary_text=_("Can't open file to write.\n%s") % str(e)) 725 alert.run() 726 alert.destroy() 727 else: 728 scan_interface.saved_filename = saved_filename 729 730 log.debug(">>> Changes on page? %s" % scan_interface.changed) 731 log.debug(">>> File saved at: %s" % scan_interface.saved_filename) 732 733 if format == "xml": 734 # Saving recent scan information 735 try: 736 recent_scans.add_recent_scan(saved_filename) 737 recent_scans.save() 738 except (OSError, IOError), e: 739 alert = HIGAlertDialog( 740 message_format=_( 741 "Can't save recent scan information"), 742 secondary_text=_( 743 "Can't open file to write.\n%s") % str(e)) 744 alert.run() 745 alert.destroy() 746 747 def get_empty_interface(self): 748 """Return this window if it is empty, otherwise create and return a new 749 one.""" 750 if self.scan_interface.empty: 751 return self.scan_interface 752 return self._new_scan_cb().scan_interface 753 754 def _new_scan_cb(self, widget=None, data=None): 755 """Create a new scan window.""" 756 w = zenmapGUI.App.new_window() 757 w.show_all() 758 return w 759 760 def _new_scan_profile_cb(self, p): 761 pe = ProfileEditor( 762 command=self.scan_interface.command_toolbar.command, 763 deletable=False) 764 pe.set_scan_interface(self.scan_interface) 765 pe.show_all() 766 767 def _edit_scan_profile_cb(self, p): 768 pe = ProfileEditor( 769 profile_name=self.scan_interface.toolbar.selected_profile, 770 deletable=True, overwrite=True) 771 pe.set_scan_interface(self.scan_interface) 772 pe.show_all() 773 774 def _help_cb(self, action): 775 show_help() 776 777 def _exit_cb(self, *args): 778 """Closes the window, prompting for confirmation if necessary. If one 779 of the tabs couldn't be closed, the function returns True and doesn't 780 exit the application.""" 781 if self.scan_interface.changed: 782 log.debug("Found changes on closing window") 783 dialog = HIGDialog( 784 buttons=(_('Close anyway').encode('utf-8'), 785 gtk.RESPONSE_CLOSE, gtk.STOCK_CANCEL, 786 gtk.RESPONSE_CANCEL)) 787 788 alert = HIGEntryLabel('<b>%s</b>' % _("Unsaved changes")) 789 790 text = HIGEntryLabel(_("The given scan has unsaved changes.\n" 791 "What do you want to do?")) 792 hbox = HIGHBox() 793 hbox.set_border_width(5) 794 hbox.set_spacing(12) 795 796 vbox = HIGVBox() 797 vbox.set_border_width(5) 798 vbox.set_spacing(12) 799 800 image = gtk.Image() 801 image.set_from_stock( 802 gtk.STOCK_DIALOG_QUESTION, gtk.ICON_SIZE_DIALOG) 803 804 vbox.pack_start(alert) 805 vbox.pack_start(text) 806 hbox.pack_start(image) 807 hbox.pack_start(vbox) 808 809 dialog.vbox.pack_start(hbox) 810 dialog.vbox.show_all() 811 812 response = dialog.run() 813 dialog.destroy() 814 815 if response == gtk.RESPONSE_CANCEL: 816 return True 817 818 search_config = SearchConfig() 819 if search_config.store_results: 820 self.store_result(self.scan_interface) 821 822 elif self.scan_interface.num_scans_running() > 0: 823 log.debug("Trying to close a window with a running scan") 824 dialog = HIGDialog( 825 buttons=(_('Close anyway').encode('utf-8'), 826 gtk.RESPONSE_CLOSE, gtk.STOCK_CANCEL, 827 gtk.RESPONSE_CANCEL)) 828 829 alert = HIGEntryLabel('<b>%s</b>' % _("Trying to close")) 830 831 text = HIGEntryLabel(_( 832 "The window you are trying to close has a scan running in " 833 "the background.\nWhat do you want to do?")) 834 hbox = HIGHBox() 835 hbox.set_border_width(5) 836 hbox.set_spacing(12) 837 838 vbox = HIGVBox() 839 vbox.set_border_width(5) 840 vbox.set_spacing(12) 841 842 image = gtk.Image() 843 image.set_from_stock( 844 gtk.STOCK_DIALOG_WARNING, gtk.ICON_SIZE_DIALOG) 845 846 vbox.pack_start(alert) 847 vbox.pack_start(text) 848 hbox.pack_start(image) 849 hbox.pack_start(vbox) 850 851 dialog.vbox.pack_start(hbox) 852 dialog.vbox.show_all() 853 854 response = dialog.run() 855 dialog.destroy() 856 857 if response == gtk.RESPONSE_CLOSE: 858 self.scan_interface.kill_all_scans() 859 elif response == gtk.RESPONSE_CANCEL: 860 return True 861 862 window = WindowConfig() 863 window.x, window.y = self.get_position() 864 window.width, window.height = self.get_size() 865 window.save_changes() 866 if config_parser.failed: 867 alert = HIGAlertDialog( 868 message_format=_("Can't save Zenmap configuration"), 869 # newline before path to help avoid weird line wrapping 870 secondary_text=_( 871 'An error occurred when saving to\n%s' 872 '\nThe error was: %s.' 873 ) % (Path.user_config_file, config_parser.failed)) 874 alert.run() 875 alert.destroy() 876 877 self.destroy() 878 879 return False 880 881 def _print_cb(self, *args): 882 """Show a print dialog.""" 883 entry = self.scan_interface.scan_result.scan_result_notebook.nmap_output.get_active_entry() # noqa 884 if entry is None: 885 return False 886 zenmapGUI.Print.run_print_operation( 887 self.scan_interface.inventory, entry) 888 889 def _quit_cb(self, *args): 890 """Close all open windows.""" 891 for window in zenmapGUI.App.open_windows[:]: 892 window.present() 893 if window._exit_cb(): 894 break 895 896 def _load_diff_compare_cb(self, widget=None, extra=None): 897 """Loads all active scans into a dictionary, passes it to the 898 DiffWindow constructor, and then displays the 'Compare Results' 899 window.""" 900 self.diff_window = DiffWindow( 901 self.scan_interface.inventory.get_scans()) 902 self.diff_window.show_all() 903 904 905def show_help(): 906 import urllib 907 import webbrowser 908 909 new = 0 910 if sys.hexversion >= 0x2050000: 911 new = 2 912 913 doc_path = abspath(join(Path.docs_dir, "help.html")) 914 url = "file:" + urllib.pathname2url(fs_enc(doc_path)) 915 try: 916 webbrowser.open(url, new=new) 917 except OSError, e: 918 d = HIGAlertDialog(parent=self, 919 message_format=_("Can't find documentation files"), 920 secondary_text=_("""\ 921There was an error loading the documentation file %s (%s). See the \ 922online documentation at %s.\ 923""") % (doc_path, unicode(e), APP_DOCUMENTATION_SITE)) 924 d.run() 925 d.destroy() 926 927if __name__ == '__main__': 928 w = ScanWindow() 929 w.show_all() 930 gtk.main() 931