root/trunk/umit/gui/ScanNotebook.py @ 4469

Revision 4469, 49.7 kB (checked in by gpolo, 4 years ago)

Fixed ticket #276: Clicking on "Scanning" (inside hosts treeview, or inside services treeview) before scans finishes results in an error.

Line 
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
23import re
24import gtk
25import gobject
26import webbrowser
27
28from higwidgets.hignotebooks import HIGNotebook, HIGAnimatedTabLabel
29from higwidgets.higboxes import HIGVBox
30from higwidgets.higdialogs import HIGAlertDialog
31from higwidgets.higscrollers import HIGScrolledWindow
32
33from umit.gui.ScanHostDetailsPage import ScanHostDetailsPage
34from umit.gui.ScanToolbar import ScanCommandToolbar, ScanToolbar
35from umit.gui.ScanHostsView import ScanHostsView, SCANNING
36from umit.gui.ScanOpenPortsPage import ScanOpenPortsPage
37from umit.gui.ScanRunDetailsPage import ScanRunDetailsPage
38from umit.gui.ScanNmapOutputPage import ScanNmapOutputPage
39from umit.gui.ScanMapperPage import ScanMapperPage
40from umit.gui.Icons import get_os_icon, get_os_logo, get_vulnerability_logo
41
42from umit.core.NmapCommand import NmapCommand
43from umit.core.UmitConf import CommandProfile, ProfileNotFound
44from umit.core.NmapParser import NmapParser
45from umit.core.Paths import Path
46from umit.core.UmitLogging import log
47from umit.core.I18N import _
48from umit.core.Utils import is_maemo
49
50from umit.plugin.Engine import PluginEngine
51
52from types import StringTypes
53
54icon_dir = Path.pixmaps_dir
55
56class 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
190class 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
290def 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
299def 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
309class 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        self.hosts = {}
329        self.services = {}
330
331        self.parsed = NmapParser()
332        self.top_box = HIGVBox()
333       
334        self.__create_toolbar()
335        self.__create_command_toolbar()
336        self.__create_scan_result()
337        self.disable_widgets()
338       
339        self.saved = False
340        self.saved_filename = ''
341
342        self.top_box.set_border_width(6)
343        self.top_box.set_spacing(5)
344       
345        self.top_box._pack_noexpand_nofill(self.toolbar)
346        self.top_box._pack_noexpand_nofill(self.command_toolbar)
347       
348        self._pack_noexpand_nofill(self.top_box)
349        self._pack_expand_fill(self.scan_result)
350
351        PluginEngine().core.emit('ScanNotebookPage-created', self)
352
353    def target_focus(self):
354        self.toolbar.target_entry.child.grab_focus()
355
356    def select_first_profile(self):
357        model = self.toolbar.profile_entry.get_model()
358        if not len(model): # no profiles
359            return
360        self.toolbar.profile_entry.child.set_text(model[0][0])
361
362    def verify_changes(self):
363        return self.__verify_comments_changes()
364
365    def go_to_host(self, host):
366        """Go to host line on nmap output result"""
367        result_nb = self.scan_result.scan_result_notebook
368        result_nb.nmap_output.nmap_output.go_to_host(host)
369
370    def __create_scan_result(self):
371        self.scan_result = ScanResult()
372        self.scan_result.set_parse(self.parsed)
373
374        self.host_view_selection = self.scan_result.get_host_selection()
375        self.service_view_selection = self.scan_result.get_service_selection()
376
377        self.host_view_selection.connect('changed', self.update_host_info)
378        self.service_view_selection.connect('changed', self.update_service_info)
379
380    def __create_toolbar(self):
381        self.toolbar = ScanToolbar()
382        self.toolbar.scan_button.set_sensitive(False)
383        self.empty_target = _("<target>")
384       
385        self.toolbar.target_entry.changed_handler = self.toolbar.target_entry.\
386            connect('changed', self.refresh_command_target)
387        self.toolbar.profile_entry.connect('changed', self.refresh_command)
388
389        self.toolbar.scan_button.connect('clicked', self.start_scan_cb)
390   
391    def __create_command_toolbar(self):
392        self.command_toolbar = ScanCommandToolbar()
393        self.command_toolbar.command_entry.connect('activate',
394            lambda x: self.toolbar.scan_button.clicked())
395
396        # This variable says if the command at command entry was edited by user
397        self.command_edited = False
398
399
400        # When user clicks insite the command entry for edition
401        self.command_toolbar.command_entry.connect("focus-in-event", 
402            self.remember_command)
403
404        # When user gets out of the command entry after edition
405        self.command_toolbar.command_entry.connect("focus-out-event", 
406            self.check_command)
407
408    def remember_command(self, widget, extra=None):
409        # User is inside command entry, probably editing it...
410       
411        # Target may be empty
412        self.old_target = self.toolbar.target_entry.selected_target
413
414        if not self.old_target:
415            self.old_target = self.empty_target
416
417        self.old_full_command = self.command_toolbar.command_entry.get_text()
418        self.old_command = self.old_full_command.split(self.old_target)[0]
419       
420
421    def check_command(self, widget, extra=None):
422        # User has left command entry. Verify if something has changed!
423        new_command = self.command_toolbar.command
424
425    def disable_widgets(self):
426        self.scan_result.set_sensitive(False)
427   
428    def enable_widgets(self):
429        self.scan_result.set_sensitive(True)
430   
431    def refresh_command_target(self, widget):
432        #log.debug(">>> Refresh Command Target")
433
434        profile = self.toolbar.selected_profile
435        #log.debug(">>> Profile: %s" % profile)
436       
437        if profile != '':
438            target = self.toolbar.selected_target
439            if target == '':
440                self.toolbar.scan_button.set_sensitive(False)
441            else:
442                self.toolbar.scan_button.set_sensitive(True)
443
444            try:
445                cmd_profile = CommandProfile()
446                command = cmd_profile.get_command(profile) % target
447                del(cmd_profile)
448               
449                self.command_toolbar.command = command
450            except ProfileNotFound:
451                pass # Go without a profile
452            except TypeError:
453                pass # The target is empty...
454                #self.profile_not_found_dialog()
455   
456    def refresh_command(self, widget):
457        #log.debug(">>> Refresh Command")
458        profile = self.toolbar.selected_profile
459        target = self.toolbar.selected_target
460
461        #log.debug(">>> Profile: %s" % profile)
462        #log.debug(">>> Target: %s" % target)
463       
464        if target == '':
465            target = self.empty_target
466
467        try:
468            cmd_profile = CommandProfile()
469            command = cmd_profile.get_command(profile) % target
470            del(cmd_profile)
471           
472            # scan button must be enable if -iR or -iL options are passed
473            if command.find('-iR') != -1 or command.find('-iL') != -1:
474                self.toolbar.scan_button.set_sensitive(True)
475
476                # For these nmap options, target is unecessary.
477                # Removes unnecessary target from the command
478                command = command.replace(target,'').strip()
479            elif target != self.empty_target:
480                self.toolbar.scan_button.set_sensitive(True)
481            else:
482                self.toolbar.scan_button.set_sensitive(False)
483
484            self.command_toolbar.command = command
485        except ProfileNotFound:
486            pass
487            #self.profile_not_found_dialog()
488        except TypeError:
489            pass # That means that the command string convertion "%" didn't work
490
491    def profile_not_found_dialog(self):
492        warn_dialog = HIGAlertDialog(message_format=_("Profile not found!"),
493            secondary_text=_("The profile name you selected/typed "
494                "couldn't be found, and probably doesn't exist. "
495                "Please, check the profile name and try again."),
496                type=gtk.MESSAGE_QUESTION)
497        warn_dialog.run()
498        warn_dialog.destroy()
499
500    def get_tab_label(self):
501        return self.get_parent().get_tab_title(self)
502
503    def set_tab_label(self, label):
504        self.get_parent().set_tab_title(self, label)
505   
506    def start_scan_cb(self, widget=None):
507        if not self.toolbar.scan_button.get_property("sensitive"):
508            return
509
510        target = self.toolbar.selected_target
511        command = self.command_toolbar.command
512        profile = self.toolbar.selected_profile
513
514        log.debug(">>> Start Scan:")
515        log.debug(">>> Target: '%s'" % target)
516        log.debug(">>> Profile: '%s'" % profile)
517        log.debug(">>> Command: '%s'" % command)
518
519        if target and profile:
520            self.set_tab_label("%s on %s" %(profile, target))
521        elif target:
522            self.set_tab_label("Scan on %s" % target)
523        elif profile:
524            self.set_tab_label(profile)
525
526        if target != '':
527            self.toolbar.add_new_target(target)
528           
529            # TODO: Fix this workarround. The following line will set back the
530            # correct command to be executed after the refresh_command_target
531            # method that will be called by the targetcombo update method.
532            self.command_toolbar.command = command
533
534        if (command.find("-iR") == -1 and command.find("-iL") == -1):
535            if command.find("<target>") > 0:
536                warn_dialog = HIGAlertDialog(
537                    message_format=_("No Target Host!"), 
538                    secondary_text=_("Target specification is mandatory. "
539                        "Either by an address in the target input box or "
540                        "through the '-iR' and '-iL' nmap options. "
541                        "Aborting scan."), type=gtk.MESSAGE_ERROR)
542                warn_dialog.run()
543                warn_dialog.destroy()
544                return
545
546        if command != '':
547            # Setting status to scanning
548            self.status.set_scanning()
549            self.execute_command(command)
550        else:
551            warn_dialog = HIGAlertDialog(
552                message_format=_("Empty Nmap Command!"),
553                secondary_text=_("There is no command to execute! "
554                    "Maybe the selected/typed profile doesn't exist. "
555                    "Please, check the profile name or type the nmap "
556                    "command you would like to execute."),
557                type=gtk.MESSAGE_ERROR)
558            warn_dialog.run()
559            warn_dialog.destroy()
560
561    def close_tab(self):
562        try:
563            gobject.source_remove(self.verify_thread_timeout_id)
564        except:
565            pass
566
567    def collect_umit_info(self):
568        profile = CommandProfile()
569        profile_name = self.toolbar.selected_profile
570       
571        self.parsed.target = self.toolbar.get_target()
572        self.parsed.profile_name = profile_name
573        self.parsed.nmap_command = self.command_toolbar.get_command()
574        self.parsed.profile = profile.get_command(profile_name)
575        self.parsed.profile_hint = profile.get_hint(profile_name)
576        self.parsed.profile_description = profile.get_description(profile_name)
577        self.parsed.profile_annotation = profile.get_annotation(profile_name)
578        self.parsed.profile_options = profile.get_options(profile_name)
579
580        if hasattr(self, "command_execution"):
581            self.parsed.nmap_output = self.command_execution.get_raw_output()
582        elif not self.parsed.nmap_output:
583            self.parsed.nmap_output = self.scan_result.get_nmap_output()
584
585    def kill_scan(self):
586        try:
587            self.command_execution.kill()
588        except AttributeError:
589            pass
590
591        self.scan_result.clear_nmap_output()
592        self.scan_result.clear_host_view()
593        self.status.set_empty()
594        self.disable_widgets()
595   
596    def execute_command(self, command):
597        log.debug("execute_command %s" % command)
598        try:
599            alive = self.command_execution.scan_state()
600            if alive:
601                warn_dialog = HIGAlertDialog(
602                    message_format=_("Scan has not finished yet"),
603                    secondary_text=_("Another scan is running in "
604                        "the background. To start another scan and kill "
605                        "the old one, click Ok. To wait for the "
606                        "conclusion of the old scan, choose Cancel."),
607                    type=gtk.MESSAGE_QUESTION, buttons=gtk.BUTTONS_OK_CANCEL)
608                response = warn_dialog.run()
609                warn_dialog.destroy()
610
611                if response == gtk.RESPONSE_OK:
612                    # Kill current scan, and let the another one to be created
613                    self.kill_scan()
614                else:
615                    return
616        except:
617            pass
618
619        self.command_execution = NmapCommand(command)
620       
621        try:
622            self.command_execution.run_scan()
623        except OSError, msg:
624            warn_dialog = HIGAlertDialog(
625                message_format=_("Nmap couldn't be found"),
626                secondary_text=_("Umit couldn't find Nmap. Maybe you don't "
627                                 "have it installed, or you need to set your "
628                                 "PATH environment variable."),
629                type=gtk.MESSAGE_ERROR)
630            warn_dialog.run()
631            warn_dialog.destroy()
632        except Exception, msg:
633            warn_dialog = HIGAlertDialog(
634                message_format=_("Command is missing!"),
635                secondary_text=_("It seems that your profile's command "
636                    "is missing or something else went wrong. Please, "
637                    "try to remove and recreate your profile."),
638                type=gtk.MESSAGE_ERROR)
639            warn_dialog.run()
640            warn_dialog.destroy()
641
642        # Ask NmapOutputViewer to show/refresh nmap output from given file
643        self.scan_result.show_nmap_output(\
644            self.command_execution.get_output_file())
645
646        # Set a "EXECUTING" icon to host list
647        self.scan_result.set_hosts({SCANNING: {'stock': gtk.STOCK_EXECUTE,
648            'action': None}})
649        self.scan_result.set_services({SCANNING:{'action': None}})
650
651        # Clear port list, to remove old information
652        self.scan_result.clear_port_list()
653
654        # When scan starts, change to nmap output view tab and refresh output
655        self.scan_result.change_to_nmap_output_tab()
656        self.scan_result.refresh_nmap_output()
657       
658        self.enable_widgets()
659
660        # Add a timeout function
661        self.verify_thread_timeout_id = gobject.timeout_add(2000, 
662            self.verify_execution)
663
664    def verify_execution(self):
665        # Using new subprocess style
666        try:
667            alive = self.command_execution.scan_state()
668        except:
669            self.disable_widgets()
670            self.status.set_scan_failed()
671            self.scan_result.set_nmap_output(self.command_execution.get_error())
672            self.emit("scan-finished")
673            return False
674
675        # Maybe this automatic refresh should be eliminated
676        # to avoid processor burning
677        self.scan_result.refresh_nmap_output()
678       
679        if alive:
680            return True
681        else:
682            self.parse_result(self.command_execution.get_xml_output_file())
683            self.emit("scan-finished")
684            return False
685
686    def load_result(self, file_to_parse):
687        ####
688        # Setting status to parsing_result
689        self.status.set_parsing_result()
690        ####
691        self._parse(file_to_parse=file_to_parse)
692
693        ####
694        # Setting status to loaded_unchanged
695        self.status.set_loaded_unchanged()
696        ####
697
698    def parse_result(self, file_to_parse):
699        ####
700        # Setting status to parsing_result
701        self.status.set_parsing_result()
702        ####
703        self._parse(file_to_parse=file_to_parse)
704       
705        ###
706        # Updating Topology
707        self.scan_result.set_parse(self.parsed)
708        ###
709       
710        ####
711        # Setting status to unsaved_unchanged
712        self.status.set_unsaved_unchanged()
713        ####
714
715    def load_from_parsed_result(self, parsed_result):
716        ####
717        # Setting status to parsing_result
718        self.status.set_parsing_result()
719        ####
720        self._parse(parsed_result=parsed_result)
721
722        ####
723        # Setting status to unsaved_unchanged
724        self.status.set_unsaved_unchanged()
725        ####
726
727    def _parse(self, file_to_parse=None, parsed_result=None):
728        """Called when scan is done. Verify if any host were found."""
729        log.debug(">>> XML output file that is going to be "
730            "parsed: %s" % file_to_parse)
731       
732        # All hosts details pages
733        self.host_pages = []
734        self.changes = True
735
736        self.scan_result.scan_host_view.clear_host_list()
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
1228class 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
1288class 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
1360if __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
Note: See TracBrowser for help on using the browser.