root/network-scanner/branches/GSoC2010/umit/gui/qs/EntryField.py @ 5841

Revision 5841, 18.0 kB (checked in by diogo, 3 years ago)

some improvements in QuickScan

Line 
1# -*- coding: utf-8 -*-
2
3# Copyright (C) 2009 Adriano Monteiro Marques.
4#
5# Author: Daniel Mendes Cassiano <dcassiano@umitproject.org>
6#
7# This program is free software; you can redistribute it and/or modify
8# it under the terms of the GNU General Public License as published by
9# the Free Software Foundation; either version 2 of the License, or
10# (at your option) any later version.
11#
12# This program is distributed in the hope that it will be useful,
13# but WITHOUT ANY WARRANTY; without even the implied warranty of
14# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15# GNU General Public License for more details.
16#
17# You should have received a copy of the GNU General Public License
18# along with this program; if not, write to the Free Software
19# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
20
21import os
22import sys
23import re
24from datetime import datetime
25from subprocess import Popen, PIPE
26
27import gobject
28import gtk
29import shlex
30import traceback
31from gtk import gdk
32
33from higwidgets.higentries import HIGTextEntry
34from higwidgets.higtextviewers import HIGTextView
35from higwidgets import HIGAlertDialog
36
37from umit.core.Paths import Path
38from umit.core.NmapCommand import NmapCommand
39from umit.core.NmapParser import nmap_parser_sax
40from umit.core.qs.ImportData import QSData
41from umit.core.qs.Nmap import Nmap
42from umit.core.TargetList import TargetList
43from umit.core.UmitLogging import log
44from umit.gui.NmapOutputViewer import NmapOutputViewer
45
46#TODO: Import this of core, changing it from ScanNotebook to core
47
48class Status(object):
49    """
50    Class responsible to handle the status of actions during the QS processing.
51    """
52    def __init__(self, status=False):
53        if status:
54            self.status = status
55        else:
56            self.set_unknown()
57           
58    def set_empty(self):
59        self._status = "empty"
60       
61    def set_parsing_result(self):
62        self._status = "parsing_result"
63
64    def set_scanning(self):
65        self._status = "scanning"
66
67    def set_unknown(self):
68        self._status = "unknown"
69       
70    def set_scan_failed(self):
71        self._status = "scan_failed"
72       
73    def set_status(self, status):
74        if status in self.available_status:
75            self._status = status
76        else:
77            raise Exception("Unknown status!")
78       
79    def _verify_status(self, status):
80        if self._status == status:
81            return True
82        return False
83   
84    def get_status(self):
85        return self._status
86   
87    def get_parsing_result(self):
88        return self._verify_status("parsing_result")
89   
90    def get_parsing_result(self):
91        return self._verify_status("parsing_result")
92
93    def get_scanning(self):
94        return self._verify_status("scanning")
95
96    def get_empty(self):
97        return self._verify_status("empty")
98
99    def get_unknown(self):
100        return self._verify_status("unknown")
101   
102    def get_scan_failed(self):
103        return self._verify_status("scan_failed")
104       
105
106    status = property(get_status, set_status)
107    parsing_result = property(get_parsing_result)
108    scanning = property(get_scanning)
109    empty = property(get_empty)
110    unknown = property(get_unknown)
111    scan_failed = property(get_scan_failed)
112   
113   
114class EntryField(object):
115    """
116    This class is responsible by the main part of QS: the EntryField.
117    All logic of completion, data handling and interface is here.
118    """
119   
120    def __init__(self):
121        self.qs_data = QSData()
122        self.entry = HIGTextEntry()
123        self.entry.set_visibility(True)
124        self.status = Status()
125        self.status.set_empty()
126        self.scan_result = Result()
127        self.b_text = ""
128        self.rgx_is_domain = "^((ht|f)tp(s?)\:\/\/|~/|/)?([\w]+:\w+@)?([a-zA-Z]{1}"
129        self.rgx_is_domain += "([\w\-]+\.)+([\w]{2,5}))(:[\d]{1,5})?((/?\w+/)+|/?)"
130        self.rgx_is_domain += "(\w+\.[\w]{3,4})?((\?\w+=\w+)?(&\w+=\w+)*)?"
131       
132        self.completion = gtk.EntryCompletion()
133        self.entry.set_max_length(1000)
134       
135        # add button to launch result in umit
136        self.btn_umit = gtk.Button ("Open Result")
137       
138        self.results_opened = False
139        self.nmap_output = None
140       
141        self.load_data(None)
142         
143        self.btn_umit.connect("clicked", self._launch_umit, None)
144       
145        self.entry.show()
146
147       
148    def show_results(self):
149        """
150        Show scan output
151        """
152        if not self.results_opened:
153            self.nmap_output = NmapOutputViewer()
154            # remove some buttons
155            self.nmap_output.hbox_buttons.remove(self.nmap_output.btn_output_properties)
156            self.nmap_output.hbox_buttons.remove(self.nmap_output.btn_refresh)
157            self.nmap_output.hbox_buttons.pack_start(self.btn_umit)
158            self.vbox.pack_end(self.nmap_output)
159            self.vbox.show_all()
160            self.results_opened = True
161       
162    def hide_results(self):
163        """
164        Hide scan output
165        """
166        if self.results_opened:
167            self.nmap_output.hbox_buttons.remove(self.btn_umit)
168            self.vbox.remove(self.nmap_output)
169            self.results_opened = False
170       
171    def menu_to_umit(self, widget, button, time, data=None):
172        """
173        Here will be the small menu responsible to call Network Scanner.
174        """
175        if button == 3:
176            data.show_all()
177           
178    def _launch_umit(self, widget, event):
179        """
180        Here will go the call to NetWork Scanner to display results previously
181        loaded here.
182        """
183        nscanner_call = "umit -f %s" % self.command_execution.get_xml_output_file()
184        args = shlex.split(nscanner_call)
185        self.command_process = Popen(args, bufsize=1, stdin=PIPE,
186                                         stdout=PIPE, stderr=PIPE)
187
188    def load_data(self, option=None):
189        """
190        Load the data on gtk.ListStore, generate the model and set functions of
191        match and signals.
192        """
193        liststore = gtk.ListStore(str)
194           
195        if option == "host":
196            for _d in self.qs_data.get_target_list():
197                liststore.append([_d])
198        elif option == "profile":
199            for _d in self.qs_data.get_profiles("profile_name"):
200                liststore.append([_d])
201        elif option == "nmap_options":
202            for _d in self.qs_data.get_nmap_options():
203                liststore.append([_d])
204        else:
205            for _d in self.qs_data.get_all().values():
206                for _i in _d:
207                    liststore.append([_i])
208           
209        self.completion.set_model(liststore)
210        self.completion.set_match_func(self.match_func)
211        self.completion.connect('match-selected', self.on_completion_match)
212        self.entry.connect('activate', self.on_completion_not_match)
213        self.entry.connect('backspace', self.on_backspace)
214        self.entry.set_completion(self.completion)
215        self.completion.set_text_column(0)
216
217   
218    #def load_if_not_complete(self, widget, event):
219        #self.load_data("host")
220       
221 
222    def match_func(self, completion, key, iter):
223        """
224        This function have the job to match and compare the words that was entered
225        on the entry field.
226        """
227        model = self.completion.get_model()
228        modelstr = model[iter][0]
229       
230        if ' ' in key:
231            last_word = " ".join(key.split()[1:])
232            if last_word != "":
233                return modelstr.lower().startswith(last_word.lower()) or modelstr.lower().startswith(key.lower())
234       
235        return modelstr.lower().startswith(key.lower())
236   
237   
238    def on_completion_not_match(self, widget):
239        """
240        This method is called when the autocompletion don't match any result
241        or user press Enter.
242        """
243        target_list = TargetList()
244
245        entered_text = self.entry.get_text()
246
247        # If has a profile
248        if len(entered_text) > 1 and entered_text.find(" ") != -1:
249            possible_host = entered_text.split(" ")[0]
250            possible_profile = " ".join(entered_text.split(" ")[1:])
251        else:
252            # If is just a domain
253            self.b_text = entered_text + " "
254            self.entry.set_text(self.b_text)
255            possible_host = entered_text
256            possible_profile = None
257
258        # Saving the target
259        target_list.add_target(possible_host)
260           
261        self.entry.set_position(-1)
262                       
263#        if len(self.b_text.split(" ")) > 1:
264#            host = self.b_text.split(" ")[0]
265#            profile = " ".join(self.b_text.split(" ")[1:])
266#        else:
267#            host = entered_text
268#            profile = None
269
270           
271        #Launch the scan
272        self.run_scan(possible_host, possible_profile)
273       
274        #TODO: get the end of the scan here?
275        try:
276            alive = self.command_execution.scan_state()
277           
278            while alive:
279                alive = self.command_execution.scan_state()
280
281            file = self.command_execution.get_normal_output_file()
282            self.show_results()
283            self.nmap_output.show_nmap_output(file)
284            self.nmap_output.refresh_output()
285 
286           
287            #text_out = self.scan_result.set_nmap_output(possible_host,
288            #                        self.command_execution.get_normal_output(),
289            #                        possible_profile)
290                   
291            #self.buffer.set_text(text_out)
292            #self.vbox.pack_start(self.result_text, False, False, 0)
293            #self.result_text.show()
294       
295            self.save_scan(possible_host)
296            #del entered_text
297       
298            #self.load_data("profile")
299       
300            return True
301       
302        except:
303            pass
304
305       
306    def on_completion_match(self, completion, model, iter):
307        #get the text entered by the user
308        entered_text = self.entry.get_text()
309       
310        if model[iter][0]:
311           
312            current_text = model[iter][0]
313            # If has a profile
314            if len(entered_text) > 1 and entered_text.find(" ") != -1:
315                self.b_text = entered_text.split(" ")[0] + " " + current_text + " "
316                self.entry.set_text(self.b_text)
317                possible_host = entered_text.split(" ")[0]
318                possible_profile = current_text
319            #elif self.entry.get_text()
320            else:
321                # If is just a domain
322                self.b_text = current_text + " "
323                self.entry.set_text(self.b_text)
324                possible_host = current_text
325                possible_profile = None
326               
327            self.entry.set_position(-1)   
328            data_from_db = self.qs_data.get_from_db()
329           
330            if current_text in data_from_db.keys():
331                text_out = ""
332                for _t in data_from_db[current_text]:
333                    if _t[0] == "ports" or _t[0] == "stats":
334                        pass
335                    else:
336                        text_out += "%s: %s\n" % (_t[0], _t[1])
337                       
338                #self.buffer.set_text(text_out)
339                #self.vbox.pack_start(self.result_text, False, False, 0)
340                #self.result_text.show()
341               
342   
343            self.run_scan(possible_host, possible_profile)
344            try:
345                alive = self.command_execution.scan_state()
346               
347                while alive:
348                    alive = self.command_execution.scan_state()
349                    if not alive:
350                        break
351                   
352                self.show_results()
353                file = self.command_execution.get_normal_output_file()
354                print "scan finished - %s" % file
355                self.nmap_output.show_nmap_output(file)
356                self.nmap_output.refresh_output()
357               
358                #text_out = self.scan_result.set_nmap_output(possible_host,
359                #                self.command_execution.get_normal_output(),
360                #                possible_profile)
361       
362                #self.buffer.set_text(text_out)
363                #self.vbox.pack_start(self.result_text, False, False, 0)
364                #self.result_text.show()
365       
366                self.save_scan(possible_host)
367       
368                #self.load_data("profile")
369       
370                return True
371       
372            except:
373                print("exception")
374                traceback.print_exc(file=sys.stdout)
375           
376            del data_from_db
377                       
378            #self.load_data("profile")
379            return True
380           
381    def on_backspace(self, entry):
382        self.hide_results()
383        self.resize(500,30)
384        self.b_text = ""
385
386    def disable_widgets(self):
387        self.scan_result.set_sensitive(False)
388   
389    def enable_widgets(self):
390        self.scan_result.set_sensitive(True)
391   
392    def kill_scan(self):
393        try:
394            self.command_execution.kill()
395        except AttributeError:
396            pass
397
398        self.entry.set_text("")
399        self.status.set_empty()
400        self.disable_widgets()
401   
402    def run_scan(self, host, profile):
403        if re.match(self.rgx_is_domain, host):
404
405            commands = self.qs_data.get_profiles("profile_commands")
406            try:
407                nmap_option = commands[profile]
408            except:
409                nmap_option = "-T Aggressive -v -n"
410
411            print "QuickScan: running scan: %s on %s" % (nmap_option, host)
412           
413            self.command_execution = NmapCommand('%s %s %s' % (Path.nmap_command_path,
414                                                               nmap_option,
415                                                               host))
416           
417            try:
418                alive = self.command_execution.scan_state()
419                if alive:
420                    warn_dialog = HIGAlertDialog(
421                    message_format="Scan has not finished yet",
422                    secondary_text="Another scan is running in the background.",
423                    type=gtk.MESSAGE_QUESTION, buttons=gtk.BUTTONS_OK_CANCEL)
424                response = warn_dialog.run()
425                warn_dialog.destroy()
426               
427                if response == gtk.RESPONSE_OK:
428                    self.kill_scan()
429                else:
430                    return
431           
432            except:
433                pass
434                       
435            try:
436                self.command_execution.run_scan()
437                self.status.set_scanning()
438            except OSError, msg:
439                warn_dialog = HIGAlertDialog(
440                    message_format="Nmap couldn't be found",
441                    secondary_text="Umit Quick Scan couldn't find Nmap. " 
442                    "Please check your Nmap instalation.",
443                    type=gtk.MESSAGE_ERROR)
444                warn_dialog.run()
445                warn_dialog.destroy()
446            except Exception, msg:
447                warn_dialog = HIGAlertDialog(
448                    message_format="Command is missing!",
449                    secondary_text="Please check your profile's command.",
450                    type=gtk.MESSAGE_ERROR)
451                warn_dialog.run()
452                warn_dialog.destroy()
453               
454               
455            self.verify_thread_timeout_id = gobject.timeout_add(2000, 
456                self.verify_execution)
457           
458            return
459       
460        else:
461            self.command_execution = None
462            return False
463
464    def save_scan(self, host):
465        from umit.core.UmitDB import Scans
466        store = Scans(scan_name="Quick Scan on %s" % host,
467                      nmap_xml_output=self.command_execution.get_xml_output(),
468                      date=datetime.now())
469       
470    def verify_execution(self):
471        try:
472            alive = self.command_execution.scan_state()
473        except:
474            self.disable_widgets()
475            self.status.set_scan_failed()
476            #self.scan_result.set_nmap_output(self.command_execution.get_error())
477            #self.emit("scan-finished")
478            return False
479
480        # Maybe this automatic refresh should be eliminated
481        # to avoid processor burning
482        #self.scan_result.refresh_nmap_output()
483       
484        if alive:
485            return True
486        else:
487            #self.parse_result(self.command_execution.get_xml_output_file())
488            #self.emit("scan-finished")
489            return False
490   
491class Result(gtk.HPaned):
492
493    __gsignals__ = {
494        'scan-finished' : (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, ())
495    }
496   
497    def __init__(self):
498        gtk.HPaned.__init__(self)
499        self.status = Status()
500        self.status.set_parsing_result()
501        self.parsed = None
502       
503    def set_nmap_output(self, host, msg, command):
504       
505        # Nmap output in parameter msg
506     
507        text_out = "Running a %s on %s...\n" % (command, host)
508        text_out += "IP: "
509               
510        if re.compile("\d{1,3}(\.\d{1,3}){3}").search(msg):
511            text_out += re.compile("\d{1,3}(\.\d{1,3}){3}").search(msg).group()
512        else:
513            text_out += "not identified"
514               
515        text_out += "\tPorts:\t"
516        services = []
517               
518        for reg in msg.split("\n"):
519            if reg.find("open") != -1:
520                try:
521                    text_out += "%s " % re.search("[0-9]+", reg.replace("open", "").split()[0]).group()
522                    services.append(reg.replace("open", "").split()[1])
523                    flag_open = True
524                except AttributeError:
525                    pass
526                       
527            elif reg.find("closed") != -1:
528                try:
529                    text_out += "%s " % re.search("[0-9]+", reg.replace("closed", "").split()[0]).group()
530                    services.append(reg.replace("closed", "").split()[1])
531                except AttributeError:
532                    pass
533                       
534        text_out += "\n\t\t\t\tServices:\t"
535                       
536        for s in services:
537            text_out += "%s " % s
538
539           
540        return text_out
541       
542               
543if __name__ == "__main__":
544    a = EntryField()
Note: See TracBrowser for help on using the browser.