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

Revision 4449, 49.7 kB (checked in by nopper, 4 years ago)

Forget to emit scan-finished signal when there is an exception

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
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
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.