| 1 | #!/usr/bin/env python |
|---|
| 2 | # -*- coding: utf-8 -*- |
|---|
| 3 | # |
|---|
| 4 | # Copyright (C) 2005-2006 Insecure.Com LLC. |
|---|
| 5 | # Copyright (C) 2007-2008 Adriano Monteiro Marques |
|---|
| 6 | # |
|---|
| 7 | # Author: Adriano Monteiro Marques <adriano@umitproject.org> |
|---|
| 8 | # |
|---|
| 9 | # This program is free software; you can redistribute it and/or modify |
|---|
| 10 | # it under the terms of the GNU General Public License as published by |
|---|
| 11 | # the Free Software Foundation; either version 2 of the License, or |
|---|
| 12 | # (at your option) any later version. |
|---|
| 13 | # |
|---|
| 14 | # This program is distributed in the hope that it will be useful, |
|---|
| 15 | # but WITHOUT ANY WARRANTY; without even the implied warranty of |
|---|
| 16 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|---|
| 17 | # GNU General Public License for more details. |
|---|
| 18 | # |
|---|
| 19 | # You should have received a copy of the GNU General Public License |
|---|
| 20 | # along with this program; if not, write to the Free Software |
|---|
| 21 | # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA |
|---|
| 22 | |
|---|
| 23 | import re |
|---|
| 24 | import gtk |
|---|
| 25 | import gobject |
|---|
| 26 | import webbrowser |
|---|
| 27 | |
|---|
| 28 | from higwidgets.hignotebooks import HIGNotebook, HIGAnimatedTabLabel |
|---|
| 29 | from higwidgets.higboxes import HIGVBox |
|---|
| 30 | from higwidgets.higdialogs import HIGAlertDialog |
|---|
| 31 | from higwidgets.higscrollers import HIGScrolledWindow |
|---|
| 32 | |
|---|
| 33 | from umit.gui.ScanHostDetailsPage import ScanHostDetailsPage |
|---|
| 34 | from umit.gui.ScanToolbar import ScanCommandToolbar, ScanToolbar |
|---|
| 35 | from umit.gui.ScanHostsView import ScanHostsView, SCANNING |
|---|
| 36 | from umit.gui.ScanOpenPortsPage import ScanOpenPortsPage |
|---|
| 37 | from umit.gui.ScanRunDetailsPage import ScanRunDetailsPage |
|---|
| 38 | from umit.gui.ScanNmapOutputPage import ScanNmapOutputPage |
|---|
| 39 | from umit.gui.ScanMapperPage import ScanMapperPage |
|---|
| 40 | from umit.gui.Icons import get_os_icon, get_os_logo, get_vulnerability_logo |
|---|
| 41 | |
|---|
| 42 | from umit.core.NmapCommand import NmapCommand |
|---|
| 43 | from umit.core.UmitConf import CommandProfile, ProfileNotFound |
|---|
| 44 | from umit.core.NmapParser import NmapParser |
|---|
| 45 | from umit.core.Paths import Path |
|---|
| 46 | from umit.core.UmitLogging import log |
|---|
| 47 | from umit.core.I18N import _ |
|---|
| 48 | from umit.core.Utils import is_maemo |
|---|
| 49 | |
|---|
| 50 | from umit.plugin.Engine import PluginEngine |
|---|
| 51 | |
|---|
| 52 | from types import StringTypes |
|---|
| 53 | |
|---|
| 54 | icon_dir = Path.pixmaps_dir |
|---|
| 55 | |
|---|
| 56 | class PageStatus(object): |
|---|
| 57 | """ |
|---|
| 58 | Pages status: |
|---|
| 59 | The page status can be one of the following: |
|---|
| 60 | * saved: there is nothing to be saved in the current scan tab |
|---|
| 61 | * unsaved_unchanged: for recently scanned results that were parsed and |
|---|
| 62 | is unchanged. |
|---|
| 63 | * unsaved_changed: for recently scanned results that were parsed and |
|---|
| 64 | got some changes on its contents (like a comment) |
|---|
| 65 | * loaded_unchanged: for scan results that were loaded from a file and |
|---|
| 66 | got no modifications |
|---|
| 67 | * loaded_changed: for scan results that were loaded from a file and |
|---|
| 68 | got some modifications (like comments) |
|---|
| 69 | * parsing_result: the result is been parsed to be displayed at the tab |
|---|
| 70 | * scanning: there is no parsed result related to this tab, but there |
|---|
| 71 | is a related scan running to show results on that tab |
|---|
| 72 | * empty: there is nothing related to this tab. His widgets are |
|---|
| 73 | disabled and this is the initial state of a new tab |
|---|
| 74 | * unknown: the page status is unknown |
|---|
| 75 | * scan_failed: the scan has failed |
|---|
| 76 | * search_loaded: the scan was loaded from a search result |
|---|
| 77 | """ |
|---|
| 78 | def __init__(self, status=False): |
|---|
| 79 | if status: |
|---|
| 80 | self.status = status |
|---|
| 81 | else: |
|---|
| 82 | self.set_unknown() |
|---|
| 83 | |
|---|
| 84 | def set_empty(self): |
|---|
| 85 | self._status = "empty" |
|---|
| 86 | |
|---|
| 87 | def set_saved(self): |
|---|
| 88 | self._status = "saved" |
|---|
| 89 | |
|---|
| 90 | def set_unsaved_unchanged(self): |
|---|
| 91 | self._status = "unsaved_unchanged" |
|---|
| 92 | |
|---|
| 93 | def set_unsaved_changed(self): |
|---|
| 94 | self._status = "unsaved_changed" |
|---|
| 95 | |
|---|
| 96 | def set_loaded_unchanged(self): |
|---|
| 97 | self._status = "loaded_unchanged" |
|---|
| 98 | |
|---|
| 99 | def set_loaded_changed(self): |
|---|
| 100 | self._status = "loaded_changed" |
|---|
| 101 | |
|---|
| 102 | def set_parsing_result(self): |
|---|
| 103 | self._status = "parsing_result" |
|---|
| 104 | |
|---|
| 105 | def set_scanning(self): |
|---|
| 106 | self._status = "scanning" |
|---|
| 107 | |
|---|
| 108 | def set_unknown(self): |
|---|
| 109 | self._status = "unknown" |
|---|
| 110 | |
|---|
| 111 | def set_scan_failed(self): |
|---|
| 112 | self._status = "scan_failed" |
|---|
| 113 | |
|---|
| 114 | def set_search_loaded(self): |
|---|
| 115 | self._status = "search_loaded" |
|---|
| 116 | |
|---|
| 117 | def get_status(self): |
|---|
| 118 | return self._status |
|---|
| 119 | |
|---|
| 120 | def set_status(self, status): |
|---|
| 121 | if status in self.available_status: |
|---|
| 122 | self._status = status |
|---|
| 123 | else: |
|---|
| 124 | raise Exception("Unknown status!") |
|---|
| 125 | |
|---|
| 126 | def _verify_status(self, status): |
|---|
| 127 | if self._status == status: |
|---|
| 128 | return True |
|---|
| 129 | return False |
|---|
| 130 | |
|---|
| 131 | def get_unsaved_unchanged(self): |
|---|
| 132 | return self._verify_status("unsaved_unchanged") |
|---|
| 133 | |
|---|
| 134 | def get_unsaved_changed(self): |
|---|
| 135 | return self._verify_status("unsaved_changed") |
|---|
| 136 | |
|---|
| 137 | def get_loaded_unchanged(self): |
|---|
| 138 | return self._verify_status("loaded_unchanged") |
|---|
| 139 | |
|---|
| 140 | def get_loaded_changed(self): |
|---|
| 141 | return self._verify_status("loaded_changed") |
|---|
| 142 | |
|---|
| 143 | def get_parsing_result(self): |
|---|
| 144 | return self._verify_status("parsing_result") |
|---|
| 145 | |
|---|
| 146 | def get_scanning(self): |
|---|
| 147 | return self._verify_status("scanning") |
|---|
| 148 | |
|---|
| 149 | def get_empty(self): |
|---|
| 150 | return self._verify_status("empty") |
|---|
| 151 | |
|---|
| 152 | def get_unknown(self): |
|---|
| 153 | return self._verify_status("unknown") |
|---|
| 154 | |
|---|
| 155 | def get_saved(self): |
|---|
| 156 | return self._verify_status("saved") |
|---|
| 157 | |
|---|
| 158 | def get_scan_failed(self): |
|---|
| 159 | return self._verify_status("scan_failed") |
|---|
| 160 | |
|---|
| 161 | def get_search_loaded(self): |
|---|
| 162 | return self._verify_status("search_loaded") |
|---|
| 163 | |
|---|
| 164 | |
|---|
| 165 | status = property(get_status, set_status) |
|---|
| 166 | saved = property(get_saved) |
|---|
| 167 | unsaved_unchanged = property(get_unsaved_unchanged) |
|---|
| 168 | unsaved_changed = property(get_unsaved_changed) |
|---|
| 169 | loaded_unchanged = property(get_loaded_unchanged) |
|---|
| 170 | loaded_changed = property(get_loaded_changed) |
|---|
| 171 | parsing_result = property(get_parsing_result) |
|---|
| 172 | scanning = property(get_scanning) |
|---|
| 173 | empty = property(get_empty) |
|---|
| 174 | unknown = property(get_unknown) |
|---|
| 175 | scan_failed = property(get_scan_failed) |
|---|
| 176 | search_loaded = property(get_search_loaded) |
|---|
| 177 | |
|---|
| 178 | _status = "unknown" |
|---|
| 179 | available_status = ["saved", |
|---|
| 180 | "unsaved_unchanged", |
|---|
| 181 | "unsaved_changed", |
|---|
| 182 | "loaded_unchanged", |
|---|
| 183 | "loaded_changed", |
|---|
| 184 | "parsing_result", |
|---|
| 185 | "scanning", |
|---|
| 186 | "empty", |
|---|
| 187 | "unknown", |
|---|
| 188 | "search_loaded"] |
|---|
| 189 | |
|---|
| 190 | class ScanNotebook(HIGNotebook): |
|---|
| 191 | def __init__(self): |
|---|
| 192 | HIGNotebook.__init__(self) |
|---|
| 193 | self.set_scrollable(True) |
|---|
| 194 | self.tab_titles = [] |
|---|
| 195 | self.scan_num = 1 |
|---|
| 196 | self.close_scan_cb = None |
|---|
| 197 | self.title_edited_cb = None |
|---|
| 198 | |
|---|
| 199 | def remove_page(self, page_num): |
|---|
| 200 | page = self.get_nth_page(page_num) |
|---|
| 201 | self.remove_tab_title(self.get_tab_title(page)) |
|---|
| 202 | |
|---|
| 203 | HIGNotebook.remove_page(self, page_num) |
|---|
| 204 | |
|---|
| 205 | def add_scan_page(self, title): |
|---|
| 206 | page = ScanNotebookPage() |
|---|
| 207 | page.select_first_profile() |
|---|
| 208 | |
|---|
| 209 | self.append_page(page, self.close_scan_cb, tab_title=title) |
|---|
| 210 | page.show_all() |
|---|
| 211 | |
|---|
| 212 | self.set_current_page(-1) |
|---|
| 213 | |
|---|
| 214 | # Put focus at the target combo, so user can open umit and start |
|---|
| 215 | # writing the target |
|---|
| 216 | page.target_focus() |
|---|
| 217 | |
|---|
| 218 | return page |
|---|
| 219 | |
|---|
| 220 | def append_page(self, page, close_cb, tab_label=None, tab_title=None): |
|---|
| 221 | log.debug(">>> Appending Scan Tab.") |
|---|
| 222 | if tab_label: |
|---|
| 223 | tab_label.set_text(self.sanitize_tab_title(tab_label.get_text())) |
|---|
| 224 | elif tab_title: |
|---|
| 225 | tab_label = HIGAnimatedTabLabel(self.sanitize_tab_title(tab_title)) |
|---|
| 226 | else: |
|---|
| 227 | tab_label = HIGAnimatedTabLabel(self.get_new_tab_title()) |
|---|
| 228 | |
|---|
| 229 | tab_label.get_animated_label().connect("title-edited", |
|---|
| 230 | self.title_edited_cb, page) |
|---|
| 231 | tab_label.connect("close-clicked", close_cb, page) |
|---|
| 232 | HIGNotebook.append_page(self, page, tab_label) |
|---|
| 233 | |
|---|
| 234 | def sanitize_tab_title(self, title): |
|---|
| 235 | #log.debug(">>> Sanitize this title: %s" % title) |
|---|
| 236 | scan_id = 1 |
|---|
| 237 | title2 = title |
|---|
| 238 | while title2 in self.tab_titles: |
|---|
| 239 | title2 = "%s (%s)" % (title, scan_id) |
|---|
| 240 | scan_id += 1 |
|---|
| 241 | |
|---|
| 242 | self.add_tab_title(title2) |
|---|
| 243 | #log.debug(">>> Title sanitized: %s" % title2) |
|---|
| 244 | return title2 |
|---|
| 245 | |
|---|
| 246 | def remove_tab_title(self, title): |
|---|
| 247 | log.debug(">>> Remove tab title: %s" % title) |
|---|
| 248 | try: self.tab_titles.remove(title) |
|---|
| 249 | except: pass |
|---|
| 250 | |
|---|
| 251 | def get_new_tab_title(self, parsed_result=None): |
|---|
| 252 | log.debug(">>> Get new tab title") |
|---|
| 253 | if parsed_result: |
|---|
| 254 | if parsed_result.scan_name: |
|---|
| 255 | return self.sanitize_tab_title(parsed_result.scan_name) |
|---|
| 256 | try: |
|---|
| 257 | filename = parsed_result.nmap_xml_file |
|---|
| 258 | if filename and type(filename) in StringTypes: |
|---|
| 259 | return self.sanitize_tab_title(filename) |
|---|
| 260 | except: |
|---|
| 261 | pass |
|---|
| 262 | |
|---|
| 263 | index = self.scan_num |
|---|
| 264 | self.scan_num += 1 |
|---|
| 265 | return self.sanitize_tab_title(_('untitled_scan%s') % index) |
|---|
| 266 | |
|---|
| 267 | def add_tab_title(self, title): |
|---|
| 268 | log.debug(">>> Add tab title: %s" % title) |
|---|
| 269 | self.tab_titles.append(title) |
|---|
| 270 | |
|---|
| 271 | def get_tab_title(self, page): |
|---|
| 272 | log.debug(">>> Get tab title") |
|---|
| 273 | return self.get_tab_label(page).get_text() |
|---|
| 274 | |
|---|
| 275 | def set_tab_title(self, page, title): |
|---|
| 276 | log.debug(">>> Set tab title: %s" % title) |
|---|
| 277 | |
|---|
| 278 | old_title = self.get_tab_title(page) |
|---|
| 279 | if old_title: |
|---|
| 280 | self.remove_tab_title(old_title) |
|---|
| 281 | |
|---|
| 282 | if not title: |
|---|
| 283 | title = self.get_new_tab_title(page.parsed) |
|---|
| 284 | else: |
|---|
| 285 | title = self.sanitize_tab_title(title) |
|---|
| 286 | |
|---|
| 287 | self.get_tab_label(page).set_text(title) |
|---|
| 288 | |
|---|
| 289 | |
|---|
| 290 | def get_port_info(port): |
|---|
| 291 | """Return port info.""" |
|---|
| 292 | return ( |
|---|
| 293 | int(port.get('portid', '0')), |
|---|
| 294 | port.get('protocol', ''), |
|---|
| 295 | port.get('state', ''), |
|---|
| 296 | port.get('name', ''), |
|---|
| 297 | port.get('product', '')) |
|---|
| 298 | |
|---|
| 299 | def get_service_info(service): |
|---|
| 300 | """Return service info.""" |
|---|
| 301 | return ( |
|---|
| 302 | service.get('hostname', ''), |
|---|
| 303 | int(service.get('portid', '0')), |
|---|
| 304 | service.get('protocol', ''), |
|---|
| 305 | service.get('port_state', ''), |
|---|
| 306 | service.get('service_product', ''), |
|---|
| 307 | service.get('service_version', '')) |
|---|
| 308 | |
|---|
| 309 | class ScanNotebookPage(HIGVBox): |
|---|
| 310 | __gsignals__ = { |
|---|
| 311 | 'scan-finished' : (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, ()) |
|---|
| 312 | } |
|---|
| 313 | |
|---|
| 314 | def __init__(self): |
|---|
| 315 | HIGVBox.__init__(self) |
|---|
| 316 | |
|---|
| 317 | # The borders are consuming too much space on Maemo. Setting it to |
|---|
| 318 | # 0 pixels while on Maemo |
|---|
| 319 | if is_maemo(): |
|---|
| 320 | self.set_border_width(0) |
|---|
| 321 | |
|---|
| 322 | self.set_spacing(0) |
|---|
| 323 | self.status = PageStatus() |
|---|
| 324 | self.status.set_empty() |
|---|
| 325 | |
|---|
| 326 | self.changes = False |
|---|
| 327 | self.comments = {} |
|---|
| 328 | |
|---|
| 329 | self.parsed = NmapParser() |
|---|
| 330 | self.top_box = HIGVBox() |
|---|
| 331 | |
|---|
| 332 | self.__create_toolbar() |
|---|
| 333 | self.__create_command_toolbar() |
|---|
| 334 | self.__create_scan_result() |
|---|
| 335 | self.disable_widgets() |
|---|
| 336 | |
|---|
| 337 | self.saved = False |
|---|
| 338 | self.saved_filename = '' |
|---|
| 339 | |
|---|
| 340 | self.top_box.set_border_width(6) |
|---|
| 341 | self.top_box.set_spacing(5) |
|---|
| 342 | |
|---|
| 343 | self.top_box._pack_noexpand_nofill(self.toolbar) |
|---|
| 344 | self.top_box._pack_noexpand_nofill(self.command_toolbar) |
|---|
| 345 | |
|---|
| 346 | self._pack_noexpand_nofill(self.top_box) |
|---|
| 347 | self._pack_expand_fill(self.scan_result) |
|---|
| 348 | |
|---|
| 349 | PluginEngine().core.emit('ScanNotebookPage-created', self) |
|---|
| 350 | |
|---|
| 351 | def target_focus(self): |
|---|
| 352 | self.toolbar.target_entry.child.grab_focus() |
|---|
| 353 | |
|---|
| 354 | def select_first_profile(self): |
|---|
| 355 | model = self.toolbar.profile_entry.get_model() |
|---|
| 356 | if not len(model): # no profiles |
|---|
| 357 | return |
|---|
| 358 | self.toolbar.profile_entry.child.set_text(model[0][0]) |
|---|
| 359 | |
|---|
| 360 | def verify_changes(self): |
|---|
| 361 | return self.__verify_comments_changes() |
|---|
| 362 | |
|---|
| 363 | def go_to_host(self, host): |
|---|
| 364 | """Go to host line on nmap output result""" |
|---|
| 365 | result_nb = self.scan_result.scan_result_notebook |
|---|
| 366 | result_nb.nmap_output.nmap_output.go_to_host(host) |
|---|
| 367 | |
|---|
| 368 | def __create_scan_result(self): |
|---|
| 369 | self.scan_result = ScanResult() |
|---|
| 370 | self.scan_result.set_parse(self.parsed) |
|---|
| 371 | |
|---|
| 372 | self.host_view_selection = self.scan_result.get_host_selection() |
|---|
| 373 | self.service_view_selection = self.scan_result.get_service_selection() |
|---|
| 374 | |
|---|
| 375 | self.host_view_selection.connect('changed', self.update_host_info) |
|---|
| 376 | self.service_view_selection.connect('changed', self.update_service_info) |
|---|
| 377 | |
|---|
| 378 | def __create_toolbar(self): |
|---|
| 379 | self.toolbar = ScanToolbar() |
|---|
| 380 | self.toolbar.scan_button.set_sensitive(False) |
|---|
| 381 | self.empty_target = _("<target>") |
|---|
| 382 | |
|---|
| 383 | self.toolbar.target_entry.changed_handler = self.toolbar.target_entry.\ |
|---|
| 384 | connect('changed', self.refresh_command_target) |
|---|
| 385 | self.toolbar.profile_entry.connect('changed', self.refresh_command) |
|---|
| 386 | |
|---|
| 387 | self.toolbar.scan_button.connect('clicked', self.start_scan_cb) |
|---|
| 388 | |
|---|
| 389 | def __create_command_toolbar(self): |
|---|
| 390 | self.command_toolbar = ScanCommandToolbar() |
|---|
| 391 | self.command_toolbar.command_entry.connect('activate', |
|---|
| 392 | lambda x: self.toolbar.scan_button.clicked()) |
|---|
| 393 | |
|---|
| 394 | # This variable says if the command at command entry was edited by user |
|---|
| 395 | self.command_edited = False |
|---|
| 396 | |
|---|
| 397 | |
|---|
| 398 | # When user clicks insite the command entry for edition |
|---|
| 399 | self.command_toolbar.command_entry.connect("focus-in-event", |
|---|
| 400 | self.remember_command) |
|---|
| 401 | |
|---|
| 402 | # When user gets out of the command entry after edition |
|---|
| 403 | self.command_toolbar.command_entry.connect("focus-out-event", |
|---|
| 404 | self.check_command) |
|---|
| 405 | |
|---|
| 406 | def remember_command(self, widget, extra=None): |
|---|
| 407 | # User is inside command entry, probably editing it... |
|---|
| 408 | |
|---|
| 409 | # Target may be empty |
|---|
| 410 | self.old_target = self.toolbar.target_entry.selected_target |
|---|
| 411 | |
|---|
| 412 | if not self.old_target: |
|---|
| 413 | self.old_target = self.empty_target |
|---|
| 414 | |
|---|
| 415 | self.old_full_command = self.command_toolbar.command_entry.get_text() |
|---|
| 416 | self.old_command = self.old_full_command.split(self.old_target)[0] |
|---|
| 417 | |
|---|
| 418 | |
|---|
| 419 | def check_command(self, widget, extra=None): |
|---|
| 420 | # User has left command entry. Verify if something has changed! |
|---|
| 421 | new_command = self.command_toolbar.command |
|---|
| 422 | |
|---|
| 423 | def disable_widgets(self): |
|---|
| 424 | self.scan_result.set_sensitive(False) |
|---|
| 425 | |
|---|
| 426 | def enable_widgets(self): |
|---|
| 427 | self.scan_result.set_sensitive(True) |
|---|
| 428 | |
|---|
| 429 | def refresh_command_target(self, widget): |
|---|
| 430 | #log.debug(">>> Refresh Command Target") |
|---|
| 431 | |
|---|
| 432 | profile = self.toolbar.selected_profile |
|---|
| 433 | #log.debug(">>> Profile: %s" % profile) |
|---|
| 434 | |
|---|
| 435 | if profile != '': |
|---|
| 436 | target = self.toolbar.selected_target |
|---|
| 437 | if target == '': |
|---|
| 438 | self.toolbar.scan_button.set_sensitive(False) |
|---|
| 439 | else: |
|---|
| 440 | self.toolbar.scan_button.set_sensitive(True) |
|---|
| 441 | |
|---|
| 442 | try: |
|---|
| 443 | cmd_profile = CommandProfile() |
|---|
| 444 | command = cmd_profile.get_command(profile) % target |
|---|
| 445 | del(cmd_profile) |
|---|
| 446 | |
|---|
| 447 | self.command_toolbar.command = command |
|---|
| 448 | except ProfileNotFound: |
|---|
| 449 | pass # Go without a profile |
|---|
| 450 | except TypeError: |
|---|
| 451 | pass # The target is empty... |
|---|
| 452 | #self.profile_not_found_dialog() |
|---|
| 453 | |
|---|
| 454 | def refresh_command(self, widget): |
|---|
| 455 | #log.debug(">>> Refresh Command") |
|---|
| 456 | profile = self.toolbar.selected_profile |
|---|
| 457 | target = self.toolbar.selected_target |
|---|
| 458 | |
|---|
| 459 | #log.debug(">>> Profile: %s" % profile) |
|---|
| 460 | #log.debug(">>> Target: %s" % target) |
|---|
| 461 | |
|---|
| 462 | if target == '': |
|---|
| 463 | target = self.empty_target |
|---|
| 464 | |
|---|
| 465 | try: |
|---|
| 466 | cmd_profile = CommandProfile() |
|---|
| 467 | command = cmd_profile.get_command(profile) % target |
|---|
| 468 | del(cmd_profile) |
|---|
| 469 | |
|---|
| 470 | # scan button must be enable if -iR or -iL options are passed |
|---|
| 471 | if command.find('-iR') != -1 or command.find('-iL') != -1: |
|---|
| 472 | self.toolbar.scan_button.set_sensitive(True) |
|---|
| 473 | |
|---|
| 474 | # For these nmap options, target is unecessary. |
|---|
| 475 | # Removes unnecessary target from the command |
|---|
| 476 | command = command.replace(target,'').strip() |
|---|
| 477 | elif target != self.empty_target: |
|---|
| 478 | self.toolbar.scan_button.set_sensitive(True) |
|---|
| 479 | else: |
|---|
| 480 | self.toolbar.scan_button.set_sensitive(False) |
|---|
| 481 | |
|---|
| 482 | self.command_toolbar.command = command |
|---|
| 483 | except ProfileNotFound: |
|---|
| 484 | pass |
|---|
| 485 | #self.profile_not_found_dialog() |
|---|
| 486 | except TypeError: |
|---|
| 487 | pass # That means that the command string convertion "%" didn't work |
|---|
| 488 | |
|---|
| 489 | def profile_not_found_dialog(self): |
|---|
| 490 | warn_dialog = HIGAlertDialog(message_format=_("Profile not found!"), |
|---|
| 491 | secondary_text=_("The profile name you selected/typed " |
|---|
| 492 | "couldn't be found, and probably doesn't exist. " |
|---|
| 493 | "Please, check the profile name and try again."), |
|---|
| 494 | type=gtk.MESSAGE_QUESTION) |
|---|
| 495 | warn_dialog.run() |
|---|
| 496 | warn_dialog.destroy() |
|---|
| 497 | |
|---|
| 498 | def get_tab_label(self): |
|---|
| 499 | return self.get_parent().get_tab_title(self) |
|---|
| 500 | |
|---|
| 501 | def set_tab_label(self, label): |
|---|
| 502 | self.get_parent().set_tab_title(self, label) |
|---|
| 503 | |
|---|
| 504 | def start_scan_cb(self, widget=None): |
|---|
| 505 | if not self.toolbar.scan_button.get_property("sensitive"): |
|---|
| 506 | return |
|---|
| 507 | |
|---|
| 508 | target = self.toolbar.selected_target |
|---|
| 509 | command = self.command_toolbar.command |
|---|
| 510 | profile = self.toolbar.selected_profile |
|---|
| 511 | |
|---|
| 512 | log.debug(">>> Start Scan:") |
|---|
| 513 | log.debug(">>> Target: '%s'" % target) |
|---|
| 514 | log.debug(">>> Profile: '%s'" % profile) |
|---|
| 515 | log.debug(">>> Command: '%s'" % command) |
|---|
| 516 | |
|---|
| 517 | if target and profile: |
|---|
| 518 | self.set_tab_label("%s on %s" %(profile, target)) |
|---|
| 519 | elif target: |
|---|
| 520 | self.set_tab_label("Scan on %s" % target) |
|---|
| 521 | elif profile: |
|---|
| 522 | self.set_tab_label(profile) |
|---|
| 523 | |
|---|
| 524 | if target != '': |
|---|
| 525 | self.toolbar.add_new_target(target) |
|---|
| 526 | |
|---|
| 527 | # TODO: Fix this workarround. The following line will set back the |
|---|
| 528 | # correct command to be executed after the refresh_command_target |
|---|
| 529 | # method that will be called by the targetcombo update method. |
|---|
| 530 | self.command_toolbar.command = command |
|---|
| 531 | |
|---|
| 532 | if (command.find("-iR") == -1 and command.find("-iL") == -1): |
|---|
| 533 | if command.find("<target>") > 0: |
|---|
| 534 | warn_dialog = HIGAlertDialog( |
|---|
| 535 | message_format=_("No Target Host!"), |
|---|
| 536 | secondary_text=_("Target specification is mandatory. " |
|---|
| 537 | "Either by an address in the target input box or " |
|---|
| 538 | "through the '-iR' and '-iL' nmap options. " |
|---|
| 539 | "Aborting scan."), type=gtk.MESSAGE_ERROR) |
|---|
| 540 | warn_dialog.run() |
|---|
| 541 | warn_dialog.destroy() |
|---|
| 542 | return |
|---|
| 543 | |
|---|
| 544 | if command != '': |
|---|
| 545 | # Setting status to scanning |
|---|
| 546 | self.status.set_scanning() |
|---|
| 547 | self.execute_command(command) |
|---|
| 548 | else: |
|---|
| 549 | warn_dialog = HIGAlertDialog( |
|---|
| 550 | message_format=_("Empty Nmap Command!"), |
|---|
| 551 | secondary_text=_("There is no command to execute! " |
|---|
| 552 | "Maybe the selected/typed profile doesn't exist. " |
|---|
| 553 | "Please, check the profile name or type the nmap " |
|---|
| 554 | "command you would like to execute."), |
|---|
| 555 | type=gtk.MESSAGE_ERROR) |
|---|
| 556 | warn_dialog.run() |
|---|
| 557 | warn_dialog.destroy() |
|---|
| 558 | |
|---|
| 559 | def close_tab(self): |
|---|
| 560 | try: |
|---|
| 561 | gobject.source_remove(self.verify_thread_timeout_id) |
|---|
| 562 | except: |
|---|
| 563 | pass |
|---|
| 564 | |
|---|
| 565 | def collect_umit_info(self): |
|---|
| 566 | profile = CommandProfile() |
|---|
| 567 | profile_name = self.toolbar.selected_profile |
|---|
| 568 | |
|---|
| 569 | self.parsed.target = self.toolbar.get_target() |
|---|
| 570 | self.parsed.profile_name = profile_name |
|---|
| 571 | self.parsed.nmap_command = self.command_toolbar.get_command() |
|---|
| 572 | self.parsed.profile = profile.get_command(profile_name) |
|---|
| 573 | self.parsed.profile_hint = profile.get_hint(profile_name) |
|---|
| 574 | self.parsed.profile_description = profile.get_description(profile_name) |
|---|
| 575 | self.parsed.profile_annotation = profile.get_annotation(profile_name) |
|---|
| 576 | self.parsed.profile_options = profile.get_options(profile_name) |
|---|
| 577 | |
|---|
| 578 | if hasattr(self, "command_execution"): |
|---|
| 579 | self.parsed.nmap_output = self.command_execution.get_raw_output() |
|---|
| 580 | elif not self.parsed.nmap_output: |
|---|
| 581 | self.parsed.nmap_output = self.scan_result.get_nmap_output() |
|---|
| 582 | |
|---|
| 583 | def kill_scan(self): |
|---|
| 584 | try: |
|---|
| 585 | self.command_execution.kill() |
|---|
| 586 | except AttributeError: |
|---|
| 587 | pass |
|---|
| 588 | |
|---|
| 589 | self.scan_result.clear_nmap_output() |
|---|
| 590 | self.scan_result.clear_host_view() |
|---|
| 591 | self.status.set_empty() |
|---|
| 592 | self.disable_widgets() |
|---|
| 593 | |
|---|
| 594 | def execute_command(self, command): |
|---|
| 595 | log.debug("execute_command %s" % command) |
|---|
| 596 | try: |
|---|
| 597 | alive = self.command_execution.scan_state() |
|---|
| 598 | if alive: |
|---|
| 599 | warn_dialog = HIGAlertDialog( |
|---|
| 600 | message_format=_("Scan has not finished yet"), |
|---|
| 601 | secondary_text=_("Another scan is running in " |
|---|
| 602 | "the background. To start another scan and kill " |
|---|
| 603 | "the old one, click Ok. To wait for the " |
|---|
| 604 | "conclusion of the old scan, choose Cancel."), |
|---|
| 605 | type=gtk.MESSAGE_QUESTION, buttons=gtk.BUTTONS_OK_CANCEL) |
|---|
| 606 | response = warn_dialog.run() |
|---|
| 607 | warn_dialog.destroy() |
|---|
| 608 | |
|---|
| 609 | if response == gtk.RESPONSE_OK: |
|---|
| 610 | # Kill current scan, and let the another one to be created |
|---|
| 611 | self.kill_scan() |
|---|
| 612 | else: |
|---|
| 613 | return |
|---|
| 614 | except: |
|---|
| 615 | pass |
|---|
| 616 | |
|---|
| 617 | self.command_execution = NmapCommand(command) |
|---|
| 618 | |
|---|
| 619 | try: |
|---|
| 620 | self.command_execution.run_scan() |
|---|
| 621 | except OSError, msg: |
|---|
| 622 | warn_dialog = HIGAlertDialog( |
|---|
| 623 | message_format=_("Nmap couldn't be found"), |
|---|
| 624 | secondary_text=_("Umit couldn't find Nmap. Maybe you don't " |
|---|
| 625 | "have it installed, or you need to set your " |
|---|
| 626 | "PATH environment variable."), |
|---|
| 627 | type=gtk.MESSAGE_ERROR) |
|---|
| 628 | warn_dialog.run() |
|---|
| 629 | warn_dialog.destroy() |
|---|
| 630 | except Exception, msg: |
|---|
| 631 | warn_dialog = HIGAlertDialog( |
|---|
| 632 | message_format=_("Command is missing!"), |
|---|
| 633 | secondary_text=_("It seems that your profile's command " |
|---|
| 634 | "is missing or something else went wrong. Please, " |
|---|
| 635 | "try to remove and recreate your profile."), |
|---|
| 636 | type=gtk.MESSAGE_ERROR) |
|---|
| 637 | warn_dialog.run() |
|---|
| 638 | warn_dialog.destroy() |
|---|
| 639 | |
|---|
| 640 | # Ask NmapOutputViewer to show/refresh nmap output from given file |
|---|
| 641 | self.scan_result.show_nmap_output(\ |
|---|
| 642 | self.command_execution.get_output_file()) |
|---|
| 643 | |
|---|
| 644 | # Set a "EXECUTING" icon to host list |
|---|
| 645 | self.scan_result.set_hosts({SCANNING: {'stock': gtk.STOCK_EXECUTE, |
|---|
| 646 | 'action': None}}) |
|---|
| 647 | self.scan_result.set_services({SCANNING:{'action': None}}) |
|---|
| 648 | |
|---|
| 649 | # Clear port list, to remove old information |
|---|
| 650 | self.scan_result.clear_port_list() |
|---|
| 651 | |
|---|
| 652 | # When scan starts, change to nmap output view tab and refresh output |
|---|
| 653 | self.scan_result.change_to_nmap_output_tab() |
|---|
| 654 | self.scan_result.refresh_nmap_output() |
|---|
| 655 | |
|---|
| 656 | self.enable_widgets() |
|---|
| 657 | |
|---|
| 658 | # Add a timeout function |
|---|
| 659 | self.verify_thread_timeout_id = gobject.timeout_add(2000, |
|---|
| 660 | self.verify_execution) |
|---|
| 661 | |
|---|
| 662 | def verify_execution(self): |
|---|
| 663 | # Using new subprocess style |
|---|
| 664 | try: |
|---|
| 665 | alive = self.command_execution.scan_state() |
|---|
| 666 | except: |
|---|
| 667 | self.disable_widgets() |
|---|
| 668 | self.status.set_scan_failed() |
|---|
| 669 | self.scan_result.set_nmap_output(self.command_execution.get_error()) |
|---|
| 670 | self.emit("scan-finished") |
|---|
| 671 | return False |
|---|
| 672 | |
|---|
| 673 | # Maybe this automatic refresh should be eliminated |
|---|
| 674 | # to avoid processor burning |
|---|
| 675 | self.scan_result.refresh_nmap_output() |
|---|
| 676 | |
|---|
| 677 | if alive: |
|---|
| 678 | return True |
|---|
| 679 | else: |
|---|
| 680 | self.parse_result(self.command_execution.get_xml_output_file()) |
|---|
| 681 | self.emit("scan-finished") |
|---|
| 682 | return False |
|---|
| 683 | |
|---|
| 684 | def load_result(self, file_to_parse): |
|---|
| 685 | #### |
|---|
| 686 | # Setting status to parsing_result |
|---|
| 687 | self.status.set_parsing_result() |
|---|
| 688 | #### |
|---|
| 689 | self._parse(file_to_parse=file_to_parse) |
|---|
| 690 | |
|---|
| 691 | #### |
|---|
| 692 | # Setting status to loaded_unchanged |
|---|
| 693 | self.status.set_loaded_unchanged() |
|---|
| 694 | #### |
|---|
| 695 | |
|---|
| 696 | def parse_result(self, file_to_parse): |
|---|
| 697 | #### |
|---|
| 698 | # Setting status to parsing_result |
|---|
| 699 | self.status.set_parsing_result() |
|---|
| 700 | #### |
|---|
| 701 | self._parse(file_to_parse=file_to_parse) |
|---|
| 702 | |
|---|
| 703 | ### |
|---|
| 704 | # Updating Topology |
|---|
| 705 | self.scan_result.set_parse(self.parsed) |
|---|
| 706 | ### |
|---|
| 707 | |
|---|
| 708 | #### |
|---|
| 709 | # Setting status to unsaved_unchanged |
|---|
| 710 | self.status.set_unsaved_unchanged() |
|---|
| 711 | #### |
|---|
| 712 | |
|---|
| 713 | def load_from_parsed_result(self, parsed_result): |
|---|
| 714 | #### |
|---|
| 715 | # Setting status to parsing_result |
|---|
| 716 | self.status.set_parsing_result() |
|---|
| 717 | #### |
|---|
| 718 | self._parse(parsed_result=parsed_result) |
|---|
| 719 | |
|---|
| 720 | #### |
|---|
| 721 | # Setting status to unsaved_unchanged |
|---|
| 722 | self.status.set_unsaved_unchanged() |
|---|
| 723 | #### |
|---|
| 724 | |
|---|
| 725 | def _parse(self, file_to_parse=None, parsed_result=None): |
|---|
| 726 | """Called when scan is done. Verify if any host were found.""" |
|---|
| 727 | log.debug(">>> XML output file that is going to be " |
|---|
| 728 | "parsed: %s" % file_to_parse) |
|---|
| 729 | |
|---|
| 730 | # All hosts details pages |
|---|
| 731 | self.host_pages = [] |
|---|
| 732 | self.changes = True |
|---|
| 733 | |
|---|
| 734 | self.scan_result.scan_host_view.clear_host_list() |
|---|
| 735 | self.hosts = {} |
|---|
| 736 | self.services = {} |
|---|
| 737 | |
|---|
| 738 | # Removed and created again to avoid host duplication problems when |
|---|
| 739 | # making multiple scans inside the same scan tab |
|---|
| 740 | try: del(self.parsed) |
|---|
| 741 | except: pass |
|---|
| 742 | |
|---|
| 743 | if file_to_parse: |
|---|
| 744 | self.parsed = NmapParser() |
|---|
| 745 | self.parsed.set_xml_file(file_to_parse) |
|---|
| 746 | try: |
|---|
| 747 | log.debug(">>> Start parsing...") |
|---|
| 748 | self.parsed.parse() |
|---|
| 749 | log.debug(">>> Successfully parsed!") |
|---|
| 750 | except: |
|---|
| 751 | log.debug(">>> An exception occourried during xml ouput " |
|---|
| 752 | "parsing") |
|---|
| 753 | try: |
|---|
| 754 | error = self.command_execution.get_error() |
|---|
| 755 | except AttributeError: |
|---|
| 756 | error = _("Couldn't retrieve the error raised by " |
|---|
| 757 | "the command!") |
|---|
| 758 | except: |
|---|
| 759 | error = _("Unknown error!") |
|---|
| 760 | |
|---|
| 761 | log.debug(">>> Error: '%s'" % error) |
|---|
| 762 | |
|---|
| 763 | # Treat root exceptions more carefully! |
|---|
| 764 | if re.findall('[rR][oO0]{2}[tT]', error): |
|---|
| 765 | need_root = HIGAlertDialog( |
|---|
| 766 | message_format=_('Root privileges are needed!'), |
|---|
| 767 | secondary_text=error) |
|---|
| 768 | need_root.run() |
|---|
| 769 | need_root.destroy() |
|---|
| 770 | else: |
|---|
| 771 | unknown_problem = HIGAlertDialog( |
|---|
| 772 | message_format=_('An unexpected error occourried!'), |
|---|
| 773 | secondary_text=error) |
|---|
| 774 | unknown_problem.run() |
|---|
| 775 | unknown_problem.destroy() |
|---|
| 776 | return |
|---|
| 777 | elif parsed_result: |
|---|
| 778 | self.parsed = parsed_result |
|---|
| 779 | |
|---|
| 780 | if int(self.parsed.hosts_up): |
|---|
| 781 | for host in self.parsed.hosts: |
|---|
| 782 | hostname = host.get_hostname() |
|---|
| 783 | host_page = self.set_host_details(host) |
|---|
| 784 | list_states = ["open", "filtered", "open|filtered"] |
|---|
| 785 | |
|---|
| 786 | for service in host.services: |
|---|
| 787 | name = service["service_name"] |
|---|
| 788 | state = service["port_state"] |
|---|
| 789 | |
|---|
| 790 | if state not in list_states: |
|---|
| 791 | continue |
|---|
| 792 | |
|---|
| 793 | if name not in self.services.keys(): |
|---|
| 794 | self.services[name] = {"hosts":[]} |
|---|
| 795 | |
|---|
| 796 | hs = {"host": host, "page": host_page, "hostname": hostname} |
|---|
| 797 | hs.update(service) |
|---|
| 798 | |
|---|
| 799 | self.services[name]["hosts"].append(hs) |
|---|
| 800 | |
|---|
| 801 | self.hosts[hostname] = {'host': host, 'page': host_page} |
|---|
| 802 | |
|---|
| 803 | host_details = self.hosts[hostname]['page'].host_details |
|---|
| 804 | host_info = self.hosts[hostname]['host'] |
|---|
| 805 | |
|---|
| 806 | try: |
|---|
| 807 | host_details.set_os_image( |
|---|
| 808 | get_os_logo(host.get_osmatch()['name'])) |
|---|
| 809 | except: |
|---|
| 810 | host_details.set_os_image(get_os_logo('')) |
|---|
| 811 | |
|---|
| 812 | host_details.set_vulnerability_image(get_vulnerability_logo( |
|---|
| 813 | host_info.get_open_ports())) |
|---|
| 814 | |
|---|
| 815 | |
|---|
| 816 | icon = None |
|---|
| 817 | try:icon = get_os_icon(host.get_osmatch()['name']) |
|---|
| 818 | except:icon = get_os_icon('') |
|---|
| 819 | |
|---|
| 820 | self.scan_result.scan_host_view.add_host( |
|---|
| 821 | {hostname: {'stock': icon, 'action':None}}) |
|---|
| 822 | |
|---|
| 823 | # Select the first host found |
|---|
| 824 | self.host_view_selection.select_iter( |
|---|
| 825 | self.scan_result.scan_host_view.host_list.get_iter_root()) |
|---|
| 826 | |
|---|
| 827 | self.scan_result.scan_host_view.set_services(self.services.keys()) |
|---|
| 828 | |
|---|
| 829 | saved_output = self.parsed.get_nmap_output() |
|---|
| 830 | if saved_output == "": |
|---|
| 831 | # And them, we update the nmap output! ;) |
|---|
| 832 | self.scan_result.scan_result_notebook.nmap_output.\ |
|---|
| 833 | nmap_output.refresh_output() |
|---|
| 834 | else: |
|---|
| 835 | # Put saved nmap output |
|---|
| 836 | self.scan_result.scan_result_notebook.nmap_output.\ |
|---|
| 837 | nmap_output.text_buffer.\ |
|---|
| 838 | set_text('\n'.join(saved_output.split('\\n'))) |
|---|
| 839 | self.scan_result.scan_result_notebook.nmap_output.nmap_output.\ |
|---|
| 840 | update_output_colors() |
|---|
| 841 | |
|---|
| 842 | target = self.parsed.get_target() |
|---|
| 843 | |
|---|
| 844 | if target != '': |
|---|
| 845 | self.toolbar.target_entry.child.set_text(target) |
|---|
| 846 | |
|---|
| 847 | profile_name = self.parsed.profile_name |
|---|
| 848 | |
|---|
| 849 | if profile_name != '': |
|---|
| 850 | profile = CommandProfile() |
|---|
| 851 | profile.add_profile(self.parsed.profile_name, |
|---|
| 852 | command=self.parsed.profile, |
|---|
| 853 | hint=self.parsed.profile_hint, |
|---|
| 854 | options=self.parsed.profile_options, |
|---|
| 855 | description=self.parsed.profile_description, |
|---|
| 856 | annotation=self.parsed.profile_annotation) |
|---|
| 857 | del(profile) |
|---|
| 858 | |
|---|
| 859 | self.toolbar.profile_entry.update() |
|---|
| 860 | |
|---|
| 861 | self.toolbar.selected_profile = profile_name |
|---|
| 862 | else: |
|---|
| 863 | pass |
|---|
| 864 | # The line bellow seens to be useless |
|---|
| 865 | #self.command_toolbar.command = self.parsed.get_nmap_command() |
|---|
| 866 | |
|---|
| 867 | self.collect_umit_info() |
|---|
| 868 | self.switch_scan_details(self.__set_scan_info()) |
|---|
| 869 | self.check_fingerprints() |
|---|
| 870 | |
|---|
| 871 | def check_fingerprints(self): |
|---|
| 872 | re_host = re.compile(r"(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})") |
|---|
| 873 | re_os_fp = re.compile(r"(SInfo.*\);)") |
|---|
| 874 | re_service_fp = re.compile(r"(SF-Port.*\);)") |
|---|
| 875 | re_service_number = re.compile(r"SF-Port(\d+)") |
|---|
| 876 | |
|---|
| 877 | nmap_output = self.parsed.nmap_output.split("\\n\\n") |
|---|
| 878 | fingerprints = {} |
|---|
| 879 | current_ip = None |
|---|
| 880 | |
|---|
| 881 | for line in nmap_output: |
|---|
| 882 | match_host = re_host.search(line) |
|---|
| 883 | match_os = re_os_fp.search(line) |
|---|
| 884 | match_service = re_service_fp.search(line) |
|---|
| 885 | |
|---|
| 886 | if match_host: |
|---|
| 887 | current_ip = match_host.groups()[0] |
|---|
| 888 | if match_os: |
|---|
| 889 | if current_ip not in fingerprints.keys(): |
|---|
| 890 | fingerprints[current_ip] = {} |
|---|
| 891 | |
|---|
| 892 | fingerprints[current_ip]["os"] = match_os.groups()[0] |
|---|
| 893 | if match_service: |
|---|
| 894 | if current_ip not in fingerprints.keys(): |
|---|
| 895 | fingerprints[current_ip] = {} |
|---|
| 896 | |
|---|
| 897 | fp = match_service.groups()[0] |
|---|
| 898 | |
|---|
| 899 | fingerprints[current_ip]["service"] = fp |
|---|
| 900 | |
|---|
| 901 | #port = re_service_port.search(fp).groups()[0] |
|---|
| 902 | #fingerprints[current_ip]["service_port"] = port |
|---|
| 903 | #fingerprints[current_ip]["service_name"] |
|---|
| 904 | |
|---|
| 905 | |
|---|
| 906 | # In the future, umit is going to catch the fingerprint informations |
|---|
| 907 | # and load a umit.gui.OSFingerprintReport or |
|---|
| 908 | # umit.gui.ServiceFingerprintReport window that will let user |
|---|
| 909 | # to directely register a new fingerprint from the interface |
|---|
| 910 | # without worrying about moving his mouse off the interface. |
|---|
| 911 | |
|---|
| 912 | """ |
|---|
| 913 | for fp in fingerprints: |
|---|
| 914 | # We've found a new fp! Please contribute dialog |
|---|
| 915 | # If ok, show the form, sending the ip and fingerprint. |
|---|
| 916 | """ |
|---|
| 917 | |
|---|
| 918 | key_num = len(fingerprints.keys()) |
|---|
| 919 | dialog_text = ("Umit has found that %s. The submission and " |
|---|
| 920 | "registration of fingerprints are very important for you " |
|---|
| 921 | "and the Nmap project! If you would like to contribute " |
|---|
| 922 | "to see your favorite network mapper recognizing those " |
|---|
| 923 | "fingerprints in the future, choose the Ok button, and a " |
|---|
| 924 | "submission page will be open in your default web browser " |
|---|
| 925 | "with instructions about how to proceed on this registration.") |
|---|
| 926 | |
|---|
| 927 | if key_num == 1: |
|---|
| 928 | msg = _("you network scan discovered an unknown fingerprint " |
|---|
| 929 | "sent by the host %s") % fingerprints.keys()[0] |
|---|
| 930 | |
|---|
| 931 | self.show_contribute_dialog(dialog_text % msg) |
|---|
| 932 | |
|---|
| 933 | elif key_num > 1: |
|---|
| 934 | msg = _("your network scan discovered several unknown " |
|---|
| 935 | "fingerprints sent by the follwoing hosts: ") |
|---|
| 936 | for i in fingerprints: |
|---|
| 937 | msg += "%s, " % i |
|---|
| 938 | msg = msg[:-2] |
|---|
| 939 | |
|---|
| 940 | self.show_contribute_dialog(dialog_text % msg) |
|---|
| 941 | |
|---|
| 942 | |
|---|
| 943 | def show_contribute_dialog(self, dialog_text): |
|---|
| 944 | contribute_dialog = HIGAlertDialog( |
|---|
| 945 | message_format=_("Unrecognized Services/OS Fingerprints Found!"), |
|---|
| 946 | secondary_text=dialog_text, type=gtk.MESSAGE_QUESTION, |
|---|
| 947 | buttons=gtk.BUTTONS_OK_CANCEL) |
|---|
| 948 | response = contribute_dialog.run() |
|---|
| 949 | contribute_dialog.destroy() |
|---|
| 950 | |
|---|
| 951 | if response == gtk.RESPONSE_OK: |
|---|
| 952 | webbrowser.open("http://www.insecure.org/nmap/submit/") |
|---|
| 953 | |
|---|
| 954 | |
|---|
| 955 | def __verify_comments_changes(self): |
|---|
| 956 | try: |
|---|
| 957 | for hostname in self.hosts: |
|---|
| 958 | host_details = self.hosts[hostname]['page'].host_details |
|---|
| 959 | if host_details.get_comment() != self.comments[hostname]: |
|---|
| 960 | log.debug("Changes on comments") |
|---|
| 961 | self.changes = True |
|---|
| 962 | return True |
|---|
| 963 | except: |
|---|
| 964 | return False |
|---|
| 965 | |
|---|
| 966 | def __set_scan_info(self): |
|---|
| 967 | self.clean_scan_details() |
|---|
| 968 | run_details = ScanRunDetailsPage() |
|---|
| 969 | |
|---|
| 970 | run_details.set_command_info( |
|---|
| 971 | {'command': self.parsed.get_nmap_command(), |
|---|
| 972 | 'version': self.parsed.get_scanner_version(), |
|---|
| 973 | 'verbose': self.parsed.get_verbose_level(), |
|---|
| 974 | 'debug': self.parsed.get_debugging_level() |
|---|
| 975 | }) |
|---|
| 976 | |
|---|
| 977 | run_details.set_general_info( |
|---|
| 978 | {'start': self.parsed.formated_date, |
|---|
| 979 | 'finish': self.parsed.formated_finish_date, |
|---|
| 980 | 'hosts_up': str(self.parsed.hosts_up), |
|---|
| 981 | 'hosts_down': str(self.parsed.hosts_down), |
|---|
| 982 | 'hosts_scanned': str(self.parsed.hosts_total), |
|---|
| 983 | 'open_ports': str(self.parsed.open_ports), |
|---|
| 984 | 'filtered_ports': str(self.parsed.filtered_ports), |
|---|
| 985 | 'closed_ports': str(self.parsed.closed_ports) |
|---|
| 986 | }) |
|---|
| 987 | |
|---|
| 988 | run_details.set_scan_infos(self.parsed.scaninfo) |
|---|
| 989 | |
|---|
| 990 | return run_details |
|---|
| 991 | |
|---|
| 992 | def update_host_info(self, widget): |
|---|
| 993 | self.scan_result.scan_result_notebook.port_mode() |
|---|
| 994 | |
|---|
| 995 | model_host_list, selection = widget.get_selected_rows() |
|---|
| 996 | #host_objs = [self.hosts[model_host_list[i[0]][1]] for i in selection] |
|---|
| 997 | host_objs = [] |
|---|
| 998 | for i in selection: |
|---|
| 999 | key = model_host_list[i[0]][1] |
|---|
| 1000 | if self.hosts.has_key(key): |
|---|
| 1001 | host_objs.append(self.hosts[key]) |
|---|
| 1002 | |
|---|
| 1003 | self.clean_host_details() |
|---|
| 1004 | |
|---|
| 1005 | if len(host_objs) == 1: |
|---|
| 1006 | self.set_single_host_port(host_objs[0]['host']) |
|---|
| 1007 | self.switch_host_details(host_objs[0]['page']) |
|---|
| 1008 | else: |
|---|
| 1009 | self.set_multiple_host_port(host_objs) |
|---|
| 1010 | self.switch_host_details(self.set_multiple_host_details(host_objs)) |
|---|
| 1011 | |
|---|
| 1012 | # Switch nmap output to show first host occourrence |
|---|
| 1013 | try: |
|---|
| 1014 | self.go_to_host(host_objs[0]['host'].get_hostname()) |
|---|
| 1015 | except IndexError: |
|---|
| 1016 | pass |
|---|
| 1017 | |
|---|
| 1018 | def update_service_info(self, widget): |
|---|
| 1019 | self.scan_result.scan_result_notebook.host_mode() |
|---|
| 1020 | |
|---|
| 1021 | model_service_list, selection = widget.get_selected_rows() |
|---|
| 1022 | serv_objs = [] |
|---|
| 1023 | for i in selection: |
|---|
| 1024 | key = model_service_list[i[0]][0] |
|---|
| 1025 | if self.services.has_key(key): |
|---|
| 1026 | serv_objs.append(self.services[key]) |
|---|
| 1027 | |
|---|
| 1028 | # Removing current widgets from the host details page |
|---|
| 1029 | self.clean_host_details() |
|---|
| 1030 | |
|---|
| 1031 | if len(serv_objs) == 1: |
|---|
| 1032 | self.set_single_service_host(serv_objs[0]['hosts']) |
|---|
| 1033 | self.switch_host_details([page["page"] \ |
|---|
| 1034 | for page in serv_objs[0]['hosts']]) |
|---|
| 1035 | else: |
|---|
| 1036 | servs = [] |
|---|
| 1037 | for s in serv_objs: |
|---|
| 1038 | servs.append({"service_name": s["hosts"][0]["service_name"], |
|---|
| 1039 | "hosts": s["hosts"]}) |
|---|
| 1040 | |
|---|
| 1041 | self.set_multiple_service_host(servs) |
|---|
| 1042 | |
|---|
| 1043 | pages = [] |
|---|
| 1044 | for serv in [serv["hosts"] for serv in serv_objs]: |
|---|
| 1045 | for h in serv: |
|---|
| 1046 | # Prevent from adding a host more then once |
|---|
| 1047 | if h["page"] not in pages: |
|---|
| 1048 | pages.append(h["page"]) |
|---|
| 1049 | |
|---|
| 1050 | self.switch_host_details(pages) |
|---|
| 1051 | |
|---|
| 1052 | # Change scan tab to "Ports/Hosts" |
|---|
| 1053 | self.scan_result.scan_result_notebook.set_current_page(0) |
|---|
| 1054 | |
|---|
| 1055 | def clean_host_details(self): |
|---|
| 1056 | parent = self.scan_result.scan_result_notebook.host_details_vbox |
|---|
| 1057 | children = parent.get_children() |
|---|
| 1058 | |
|---|
| 1059 | for child in children: |
|---|
| 1060 | parent.remove(child) |
|---|
| 1061 | |
|---|
| 1062 | def clean_scan_details(self): |
|---|
| 1063 | parent = self.scan_result.scan_result_notebook.scan_details_vbox |
|---|
| 1064 | children = parent.get_children() |
|---|
| 1065 | |
|---|
| 1066 | for child in children: |
|---|
| 1067 | parent.remove(child) |
|---|
| 1068 | |
|---|
| 1069 | def switch_host_details(self, page): |
|---|
| 1070 | if type(page) == type([]): |
|---|
| 1071 | if len(page) > 1: |
|---|
| 1072 | for p in page: |
|---|
| 1073 | p.hide() |
|---|
| 1074 | p.set_expanded(False) |
|---|
| 1075 | result_nb = self.scan_result.scan_result_notebook |
|---|
| 1076 | result_nb.host_details_vbox._pack_noexpand_nofill(p) |
|---|
| 1077 | |
|---|
| 1078 | result_nb = self.scan_result.scan_result_notebook |
|---|
| 1079 | result_nb.host_details_vbox.show_all() |
|---|
| 1080 | return |
|---|
| 1081 | elif len(page) == 1: |
|---|
| 1082 | page = page[0] |
|---|
| 1083 | |
|---|
| 1084 | try: |
|---|
| 1085 | page.hide() |
|---|
| 1086 | except: # XXX except what ? |
|---|
| 1087 | pass |
|---|
| 1088 | else: |
|---|
| 1089 | result_nb = self.scan_result.scan_result_notebook |
|---|
| 1090 | result_nb.host_details_vbox._pack_noexpand_nofill(page) |
|---|
| 1091 | page.set_expanded(True) |
|---|
| 1092 | page.show_all() |
|---|
| 1093 | |
|---|
| 1094 | def switch_scan_details(self, page): |
|---|
| 1095 | # Removing current widget from the host details page |
|---|
| 1096 | result_nb = self.scan_result.scan_result_notebook |
|---|
| 1097 | result_nb.scan_details_vbox._pack_noexpand_nofill(page) |
|---|
| 1098 | page.show_all() |
|---|
| 1099 | |
|---|
| 1100 | def set_multiple_host_details(self, host_list): |
|---|
| 1101 | hosts = [] |
|---|
| 1102 | for h in host_list: |
|---|
| 1103 | hosts.append(h['page']) |
|---|
| 1104 | |
|---|
| 1105 | return hosts |
|---|
| 1106 | |
|---|
| 1107 | def _save_comment(self, widget, extra, host_id): |
|---|
| 1108 | if self.status.unsaved_unchanged: |
|---|
| 1109 | self.status.set_unsaved_changed() |
|---|
| 1110 | elif self.status.loaded_unchanged or self.status.saved: |
|---|
| 1111 | self.status.set_loaded_changed() |
|---|
| 1112 | |
|---|
| 1113 | # Catch a comment and record it to be saved posteriorly |
|---|
| 1114 | log.debug(">>> Catching edited comment to be saved posteriorly.") |
|---|
| 1115 | buff = widget.get_buffer() |
|---|
| 1116 | self.parsed.set_host_comment(host_id, |
|---|
| 1117 | buff.get_text(buff.get_start_iter(), buff.get_end_iter())) |
|---|
| 1118 | |
|---|
| 1119 | def set_host_details(self, host): |
|---|
| 1120 | # Start connecting event to automatically update comments, target |
|---|
| 1121 | # and profile infos |
|---|
| 1122 | host_page = ScanHostDetailsPage(host.get_hostname()) |
|---|
| 1123 | host_details = host_page.host_details |
|---|
| 1124 | |
|---|
| 1125 | log.debug(">>> Setting host details") |
|---|
| 1126 | log.debug(">>> Hostname: %s" % host.get_hostname()) |
|---|
| 1127 | log.debug(">>> Comment: %s" % self.parsed.get_host_comment(host.id)) |
|---|
| 1128 | host_details.set_comment(self.parsed.get_host_comment(host.id)) |
|---|
| 1129 | |
|---|
| 1130 | # Setting events to automatically record the commentary to be saved |
|---|
| 1131 | host_page.host_details.comment_txt_vw.connect("insert-at-cursor", |
|---|
| 1132 | self._save_comment, host.id) |
|---|
| 1133 | host_page.host_details.comment_txt_vw.connect("focus-out-event", |
|---|
| 1134 | self._save_comment, host.id) |
|---|
| 1135 | |
|---|
| 1136 | |
|---|
| 1137 | self.comments[host.get_hostname()] = host.comment |
|---|
| 1138 | |
|---|
| 1139 | uptime = host.uptime |
|---|
| 1140 | |
|---|
| 1141 | host_details.set_host_status({'state':host.status['state'], |
|---|
| 1142 | 'open':str(host.get_open_ports()), |
|---|
| 1143 | 'filtered':str(host.get_filtered_ports()), |
|---|
| 1144 | 'closed':str(host.get_closed_ports()), |
|---|
| 1145 | 'scanned':str(host.get_scanned_ports()), |
|---|
| 1146 | 'uptime':uptime['seconds'], |
|---|
| 1147 | 'lastboot':uptime['lastboot']}) |
|---|
| 1148 | |
|---|
| 1149 | |
|---|
| 1150 | ipv4, ipv6, mac = '', '', '' |
|---|
| 1151 | for addr in host.address: |
|---|
| 1152 | addrtype, addr = addr['addrtype'], addr['addr'] |
|---|
| 1153 | if addrtype == 'ipv4': |
|---|
| 1154 | ipv4 = addr |
|---|
| 1155 | elif addrtype == 'ipv6': |
|---|
| 1156 | ipv6 = addr |
|---|
| 1157 | elif addrtype == 'mac': |
|---|
| 1158 | mac = addr |
|---|
| 1159 | |
|---|
| 1160 | host_details.set_addresses({'ipv4': ipv4, 'ipv6': ipv6, 'mac': mac}) |
|---|
| 1161 | host_details.set_hostnames(host.hostnames) |
|---|
| 1162 | |
|---|
| 1163 | # XXX |
|---|
| 1164 | os = {} |
|---|
| 1165 | if host.osmatch: |
|---|
| 1166 | os = host.osmatch[0] |
|---|
| 1167 | os['portsused'] = host.portused |
|---|
| 1168 | os['osclass'] = host.osclass |
|---|
| 1169 | |
|---|
| 1170 | host_details.set_os_list(host.osmatch, os) |
|---|
| 1171 | host_details.set_tcpseq(host.tcpsequence) |
|---|
| 1172 | host_details.set_ipseq(host.ipidsequence) |
|---|
| 1173 | host_details.set_tcptsseq(host.tcptssequence) |
|---|
| 1174 | |
|---|
| 1175 | |
|---|
| 1176 | return host_page |
|---|
| 1177 | |
|---|
| 1178 | def set_single_host_port(self, host): |
|---|
| 1179 | host_page = self.scan_result.scan_result_notebook.open_ports.host |
|---|
| 1180 | host_page.switch_port_to_list_store() |
|---|
| 1181 | |
|---|
| 1182 | host_page.clear_port_list() |
|---|
| 1183 | for port in host.ports: |
|---|
| 1184 | host_page.add_port( |
|---|
| 1185 | (self.findout_service_icon(port), ) + |
|---|
| 1186 | get_port_info(port)) |
|---|
| 1187 | |
|---|
| 1188 | def set_single_service_host(self, service): |
|---|
| 1189 | host_page = self.scan_result.scan_result_notebook.open_ports.host |
|---|
| 1190 | host_page.switch_host_to_list_store() |
|---|
| 1191 | host_page.clear_host_list() |
|---|
| 1192 | |
|---|
| 1193 | for h in service: |
|---|
| 1194 | host_page.add_host( |
|---|
| 1195 | (self.findout_service_icon(h), ) + |
|---|
| 1196 | get_service_info(h)) |
|---|
| 1197 | |
|---|
| 1198 | def set_multiple_host_port(self, host_list): |
|---|
| 1199 | host_page = self.scan_result.scan_result_notebook.open_ports.host |
|---|
| 1200 | host_page.switch_port_to_tree_store() |
|---|
| 1201 | host_page.clear_port_tree() |
|---|
| 1202 | |
|---|
| 1203 | for host in host_list: |
|---|
| 1204 | host = host['host'] |
|---|
| 1205 | parent = host_page.port_tree.append(None, |
|---|
| 1206 | [host.get_hostname(), '', 0, '', '', '', '']) |
|---|
| 1207 | for port in host.ports: |
|---|
| 1208 | host_page.port_tree.append(parent, |
|---|
| 1209 | ('', self.findout_service_icon(port)) + |
|---|
| 1210 | get_port_info(port)) |
|---|
| 1211 | |
|---|
| 1212 | def set_multiple_service_host(self, service_list): |
|---|
| 1213 | host_page = self.scan_result.scan_result_notebook.open_ports.host |
|---|
| 1214 | host_page.switch_host_to_tree_store() |
|---|
| 1215 | host_page.clear_host_tree() |
|---|
| 1216 | |
|---|
| 1217 | for host in service_list: |
|---|
| 1218 | parent = host_page.host_tree.append(None, [host['service_name'], |
|---|
| 1219 | '','',0,'','', '', '']) |
|---|
| 1220 | for h in host['hosts']: |
|---|
| 1221 | host_page.host_tree.append(parent, |
|---|
| 1222 | ('', self.findout_service_icon(h)) + |
|---|
| 1223 | get_service_info(h)) |
|---|
| 1224 | |
|---|
| 1225 | def findout_service_icon(self, port_info): |
|---|
| 1226 | return gtk.STOCK_YES |
|---|
| 1227 | |
|---|
| 1228 | class ScanResult(gtk.HPaned): |
|---|
| 1229 | def __init__(self): |
|---|
| 1230 | gtk.HPaned.__init__(self) |
|---|
| 1231 | self.parsed = None |
|---|
| 1232 | self.scan_host_view = ScanHostsView() |
|---|
| 1233 | self.scan_result_notebook = ScanResultNotebook() |
|---|
| 1234 | |
|---|
| 1235 | self.pack1(self.scan_host_view, True, False) |
|---|
| 1236 | self.pack2(self.scan_result_notebook, True, False) |
|---|
| 1237 | def set_parse(self, parse): |
|---|
| 1238 | self.parsed = parse |
|---|
| 1239 | self.scan_result_notebook.set_parse(self.parsed) |
|---|
| 1240 | def set_nmap_output(self, msg): |
|---|
| 1241 | nmap_output = self.scan_result_notebook.nmap_output.nmap_output |
|---|
| 1242 | nmap_output.text_view.get_buffer().set_text(msg) |
|---|
| 1243 | nmap_output.update_output_colors() |
|---|
| 1244 | |
|---|
| 1245 | def clear_nmap_output(self): |
|---|
| 1246 | nmap_output = self.scan_result_notebook.nmap_output.nmap_output |
|---|
| 1247 | nmap_output.text_view.get_buffer().set_text("") |
|---|
| 1248 | |
|---|
| 1249 | def clear_host_view(self): |
|---|
| 1250 | self.set_hosts({}) |
|---|
| 1251 | |
|---|
| 1252 | def clear_service_view(self): |
|---|
| 1253 | self.set_services({}) |
|---|
| 1254 | |
|---|
| 1255 | def get_host_selection(self): |
|---|
| 1256 | return self.scan_host_view.host_view.get_selection() |
|---|
| 1257 | |
|---|
| 1258 | def get_service_selection(self): |
|---|
| 1259 | return self.scan_host_view.service_view.get_selection() |
|---|
| 1260 | |
|---|
| 1261 | def get_nmap_output(self): |
|---|
| 1262 | return self.scan_result_notebook.nmap_output.get_nmap_output() |
|---|
| 1263 | |
|---|
| 1264 | def show_nmap_output(self, file): |
|---|
| 1265 | """Ask NmapOutputViewer to show/refresh nmap output from given file.""" |
|---|
| 1266 | self.scan_result_notebook.nmap_output.nmap_output.show_nmap_output(file) |
|---|
| 1267 | |
|---|
| 1268 | def set_hosts(self, hosts_dic): |
|---|
| 1269 | """Set hosts at host list.""" |
|---|
| 1270 | self.scan_host_view.set_hosts(hosts_dic) |
|---|
| 1271 | |
|---|
| 1272 | def set_services(self, services_dic): |
|---|
| 1273 | self.scan_host_view.set_services(services_dic) |
|---|
| 1274 | |
|---|
| 1275 | def clear_port_list(self): |
|---|
| 1276 | """Clear Umit's scan result ports list.""" |
|---|
| 1277 | self.scan_result_notebook.open_ports.host.clear_port_list() |
|---|
| 1278 | |
|---|
| 1279 | def change_to_nmap_output_tab(self): |
|---|
| 1280 | """Show the nmap output tab.""" |
|---|
| 1281 | self.scan_result_notebook.set_current_page(1) |
|---|
| 1282 | |
|---|
| 1283 | def refresh_nmap_output(self): |
|---|
| 1284 | """Refresh Nmap output in nmap output tab.""" |
|---|
| 1285 | self.scan_result_notebook.nmap_output.nmap_output.refresh_output() |
|---|
| 1286 | |
|---|
| 1287 | |
|---|
| 1288 | class ScanResultNotebook(HIGNotebook): |
|---|
| 1289 | def __init__(self): |
|---|
| 1290 | HIGNotebook.__init__(self) |
|---|
| 1291 | self.set_scrollable(True) |
|---|
| 1292 | self.set_border_width(5) |
|---|
| 1293 | |
|---|
| 1294 | self.__create_widgets() |
|---|
| 1295 | self.__nmap_output_refreshing() |
|---|
| 1296 | |
|---|
| 1297 | self.append_page(self.open_ports_page, gtk.Label(_('Ports / Hosts'))) |
|---|
| 1298 | self.append_page(self.nmap_output_page, gtk.Label(_('Nmap Output'))) |
|---|
| 1299 | self.append_page(self.host_details_page, gtk.Label(_('Host Details'))) |
|---|
| 1300 | self.append_page(self.scan_details_page, gtk.Label(_('Scan Details'))) |
|---|
| 1301 | self.append_page(self.scan_mapper, gtk.Label(_('Topology'))) |
|---|
| 1302 | |
|---|
| 1303 | self.parsed = None |
|---|
| 1304 | |
|---|
| 1305 | def set_parse(self, parse): |
|---|
| 1306 | self.parsed = parse |
|---|
| 1307 | self.scan_mapper.set_parse(self.parsed) |
|---|
| 1308 | self.scan_mapper.create_widgets() |
|---|
| 1309 | |
|---|
| 1310 | PluginEngine().core.emit('ScanResultNotebook-created', self) |
|---|
| 1311 | |
|---|
| 1312 | def get_nmap_output(self): |
|---|
| 1313 | return self.nmap_output.get_map_output() |
|---|
| 1314 | |
|---|
| 1315 | def host_mode(self): |
|---|
| 1316 | self.open_ports.host.host_mode() |
|---|
| 1317 | |
|---|
| 1318 | def port_mode(self): |
|---|
| 1319 | self.open_ports.host.port_mode() |
|---|
| 1320 | |
|---|
| 1321 | def __create_widgets(self): |
|---|
| 1322 | self.open_ports_page = HIGVBox() |
|---|
| 1323 | self.nmap_output_page = HIGVBox() |
|---|
| 1324 | self.host_details_page = HIGScrolledWindow() |
|---|
| 1325 | self.scan_details_page = HIGScrolledWindow() |
|---|
| 1326 | self.scan_details_vbox = HIGVBox() |
|---|
| 1327 | self.host_details_vbox = HIGVBox() |
|---|
| 1328 | self.scan_mapper = self.__create_mapper() |
|---|
| 1329 | |
|---|
| 1330 | self.open_ports = ScanOpenPortsPage() |
|---|
| 1331 | self.nmap_output = ScanNmapOutputPage() |
|---|
| 1332 | |
|---|
| 1333 | self.no_selected = gtk.Label(_('No host selected!')) |
|---|
| 1334 | self.host_details = self.no_selected |
|---|
| 1335 | |
|---|
| 1336 | self.no_details = gtk.Label(_('Scan is not finished yet!')) |
|---|
| 1337 | self.scan_details = self.no_details |
|---|
| 1338 | |
|---|
| 1339 | self.open_ports_page.add(self.open_ports) |
|---|
| 1340 | self.nmap_output_page.add(self.nmap_output) |
|---|
| 1341 | |
|---|
| 1342 | self.host_details_page.add_with_viewport(self.host_details_vbox) |
|---|
| 1343 | self.host_details_vbox._pack_expand_fill(self.host_details) |
|---|
| 1344 | |
|---|
| 1345 | self.scan_details_page.add_with_viewport(self.scan_details_vbox) |
|---|
| 1346 | self.scan_details_vbox._pack_expand_fill(self.scan_details) |
|---|
| 1347 | def __create_mapper(self): |
|---|
| 1348 | page = ScanMapperPage() |
|---|
| 1349 | |
|---|
| 1350 | return page |
|---|
| 1351 | def __nmap_output_refreshing(self): |
|---|
| 1352 | self.connect('switch-page', self.refresh_cb) |
|---|
| 1353 | |
|---|
| 1354 | def refresh_cb(self, widget, page=None, page_num=None): |
|---|
| 1355 | if self.nmap_output.nmap_output.thread.isAlive(): |
|---|
| 1356 | if page_num == 2: |
|---|
| 1357 | self.nmap_output.nmap_output.refresh_output(None) |
|---|
| 1358 | |
|---|
| 1359 | |
|---|
| 1360 | if __name__ == "__main__": |
|---|
| 1361 | status = PageStatus("empty") |
|---|
| 1362 | status.set_saved() |
|---|
| 1363 | status.set_unsaved_unchanged() |
|---|
| 1364 | status.set_unsaved_changed() |
|---|
| 1365 | status.set_loaded_unchanged() |
|---|
| 1366 | status.set_loaded_changed() |
|---|
| 1367 | status.set_empty() |
|---|
| 1368 | status.set_scanning() |
|---|
| 1369 | status.set_parsing_result() |
|---|
| 1370 | status.set_unknown() |
|---|
| 1371 | |
|---|
| 1372 | print "Saved:", status.saved |
|---|
| 1373 | print "Unsaved unchanged:", status.unsaved_unchanged |
|---|
| 1374 | print "Unsaved changed:", status.unsaved_changed |
|---|
| 1375 | print "Loaded unchanged:", status.loaded_unchanged |
|---|
| 1376 | print "Loaded changed:", status.loaded_changed |
|---|
| 1377 | print "Empty:", status.empty |
|---|
| 1378 | print "Scanning:", status.scanning |
|---|
| 1379 | print "Parsing result:", status.parsing_result |
|---|
| 1380 | print "Unknown:", status.unknown |
|---|