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

Revision 5839, 18.3 kB (checked in by diogo, 3 years ago)

changed quickscan to display nmap output

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        self.nmap_output = NmapOutputViewer()
136        # remove some buttons
137        self.nmap_output.hbox_buttons.remove(self.nmap_output.btn_output_properties)
138        self.nmap_output.hbox_buttons.remove(self.nmap_output.btn_refresh)
139        # add button to launch result in umit
140        self.btn_umit = gtk.Button ("Open Result")
141        self.nmap_output.hbox_buttons.pack_start(self.btn_umit)
142       
143        self.results_opened = False
144       
145        self.load_data(None)
146         
147        self.btn_umit.connect("clicked", self._launch_umit, None)
148       
149        self.entry.show()
150       
151    def show_results(self):
152        """
153        Show scan output
154        """
155        if not self.results_opened:
156            self.vbox.pack_end(self.nmap_output)
157            self.vbox.show_all()
158            self.results_opened = True
159       
160    def hide_results(self):
161        """
162        Hide scan output
163        """
164        if self.results_opened:
165            self.vbox.remove(self.nmap_output)
166            self.results_opened = False
167       
168    def menu_to_umit(self, widget, button, time, data=None):
169        """
170        Here will be the small menu responsible to call Network Scanner.
171        """
172        if button == 3:
173            data.show_all()
174           
175    def _launch_umit(self, widget, event):
176        """
177        Here will go the call to NetWork Scanner to display results previously
178        loaded here.
179        """
180        nscanner_call = "umit -f %s" % self.command_execution.get_xml_output_file()
181        args = shlex.split(nscanner_call)
182        print args
183        self.command_process = Popen(args, bufsize=1, stdin=PIPE,
184                                         stdout=PIPE, stderr=PIPE)
185
186    def load_data(self, option=None):
187        """
188        Load the data on gtk.ListStore, generate the model and set functions of
189        match and signals.
190        """
191        liststore = gtk.ListStore(str)
192           
193        if option == "host":
194            for _d in self.qs_data.get_target_list():
195                liststore.append([_d])
196        elif option == "profile":
197            for _d in self.qs_data.get_profiles("profile_name"):
198                liststore.append([_d])
199        elif option == "nmap_options":
200            for _d in self.qs_data.get_nmap_options():
201                liststore.append([_d])
202        else:
203            for _d in self.qs_data.get_all().values():
204                for _i in _d:
205                    liststore.append([_i])
206           
207        self.completion.set_model(liststore)
208        self.completion.set_match_func(self.match_func)
209        self.completion.connect('match-selected', self.on_completion_match)
210        self.entry.connect('activate', self.on_completion_not_match)
211        self.entry.connect('backspace', self.on_backspace)
212        self.entry.set_completion(self.completion)
213        self.completion.set_text_column(0)
214
215   
216    #def load_if_not_complete(self, widget, event):
217        #self.load_data("host")
218       
219 
220    def match_func(self, completion, key, iter):
221        """
222        This function have the job to match and compare the words that was entered
223        on the entry field.
224        """
225        model = self.completion.get_model()
226        modelstr = model[iter][0]
227       
228        if " " in key:
229            last_word = key.split()[-1]
230            #self.load_data("nmap_options")
231            return modelstr.lower().startswith(last_word.lower())
232       
233        return modelstr.lower().startswith(key.lower())
234   
235   
236    def on_completion_not_match(self, widget):
237        """
238        This method is called when the autocompletion don't match any result
239        or user press Enter.
240        """
241        target_list = TargetList()
242
243        entered_text = self.entry.get_text()
244
245
246        # TODO: Something wrong here, this function is called when enter key pressed, not an item of the list selected
247        try:
248            current_text = self.completion.get_model()[iter][0]       
249        except TypeError:
250            pass
251
252       
253
254        # If has a profile
255        if len(entered_text) > 1 and entered_text.find(" ") != -1:
256            self.b_text = entered_text.split(" ")[0] + " " + current_text + " "
257            self.entry.set_text(self.b_text)
258            possible_host = entered_text.split(" ")[0]
259            possible_profile = current_text
260            #elif self.entry.get_text()
261        else:
262            # If is just a domain
263            self.b_text = entered_text + " "
264            self.entry.set_text(self.b_text)
265            possible_host = entered_text
266            possible_profile = None
267
268        # Saving the target
269        # CHANGED: only hostname should be saved, right?
270        target_list.add_target(possible_host)
271           
272        self.entry.set_position(-1)
273               
274        if self.buffer.get_char_count() > 1:
275            self.buffer.set_text("")
276            self.vbox.pack_start(self.result_text, False, False, 0)
277            self.result_text.show()
278       
279#        if len(self.b_text.split(" ")) > 1:
280#            host = self.b_text.split(" ")[0]
281#            profile = " ".join(self.b_text.split(" ")[1:])
282#        else:
283#            host = entered_text
284#            profile = None
285
286           
287        #Launch the scan
288        self.run_scan(possible_host, possible_profile)
289       
290        #TODO: get the end of the scan here?
291        try:
292            alive = self.command_execution.scan_state()
293           
294            while alive:
295                alive = self.command_execution.scan_state()
296
297            file = self.command_execution.get_normal_output_file()
298            self.nmap_output.show_nmap_output(file)
299            self.show_results()
300 
301           
302            #text_out = self.scan_result.set_nmap_output(possible_host,
303            #                        self.command_execution.get_normal_output(),
304            #                        possible_profile)
305                   
306            #self.buffer.set_text(text_out)
307            #self.vbox.pack_start(self.result_text, False, False, 0)
308            #self.result_text.show()
309       
310            self.save_scan(possible_host)
311            #del entered_text
312       
313            #self.load_data("profile")
314       
315            return True
316       
317        except:
318            pass
319
320       
321    def on_completion_match(self, completion, model, iter):
322        #get the text entered by the user
323        entered_text = self.entry.get_text()
324       
325        if model[iter][0]:
326           
327            current_text = model[iter][0]
328            # If has a profile
329            if len(entered_text) > 1 and entered_text.find(" ") != -1:
330                self.b_text = entered_text.split(" ")[0] + " " + current_text + " "
331                self.entry.set_text(self.b_text)
332                possible_host = entered_text.split(" ")[0]
333                possible_profile = current_text
334            #elif self.entry.get_text()
335            else:
336                # If is just a domain
337                self.b_text = current_text + " "
338                self.entry.set_text(self.b_text)
339                possible_host = current_text
340                possible_profile = None
341               
342            self.entry.set_position(-1)   
343            data_from_db = self.qs_data.get_from_db()
344           
345            if current_text in data_from_db.keys():
346                text_out = ""
347                for _t in data_from_db[current_text]:
348                    if _t[0] == "ports" or _t[0] == "stats":
349                        pass
350                    else:
351                        text_out += "%s: %s\n" % (_t[0], _t[1])
352                       
353                #self.buffer.set_text(text_out)
354                #self.vbox.pack_start(self.result_text, False, False, 0)
355                #self.result_text.show()
356               
357   
358            self.run_scan(possible_host, possible_profile)
359            try:
360                alive = self.command_execution.scan_state()
361               
362                while alive:
363                    alive = self.command_execution.scan_state()
364                    if not alive:
365                        break
366                   
367                file = self.command_execution.get_normal_output_file()
368                self.nmap_output.show_nmap_output(file)
369                self.show_results()
370               
371                #text_out = self.scan_result.set_nmap_output(possible_host,
372                #                self.command_execution.get_normal_output(),
373                #                possible_profile)
374       
375                #self.buffer.set_text(text_out)
376                #self.vbox.pack_start(self.result_text, False, False, 0)
377                #self.result_text.show()
378       
379                self.save_scan(possible_host)
380       
381                #self.load_data("profile")
382       
383                return True
384       
385            except:
386                print("exception")
387                traceback.print_exc(file=sys.stdout)
388           
389            del data_from_db
390                       
391            #self.load_data("profile")
392            return True
393           
394    def on_backspace(self, entry):
395        self.hide_results()
396        self.resize(500,30)
397        self.b_text = ""
398
399    def disable_widgets(self):
400        self.scan_result.set_sensitive(False)
401   
402    def enable_widgets(self):
403        self.scan_result.set_sensitive(True)
404   
405    def kill_scan(self):
406        try:
407            self.command_execution.kill()
408        except AttributeError:
409            pass
410
411        self.entry.set_text("")
412        self.status.set_empty()
413        self.disable_widgets()
414   
415    def run_scan(self, host, profile):
416        if re.match(self.rgx_is_domain, host):
417
418            commands = self.qs_data.get_profiles("nmap_commands")
419            try:
420                nmap_option = commands[profile]
421            except:
422                nmap_option = "-T Aggressive -v -n"
423
424           
425            self.command_execution = NmapCommand('%s %s %s' % (Path.nmap_command_path,
426                                                               nmap_option,
427                                                               host))
428           
429            try:
430                alive = self.command_execution.scan_state()
431                if alive:
432                    warn_dialog = HIGAlertDialog(
433                    message_format="Scan has not finished yet",
434                    secondary_text="Another scan is running in the background.",
435                    type=gtk.MESSAGE_QUESTION, buttons=gtk.BUTTONS_OK_CANCEL)
436                response = warn_dialog.run()
437                warn_dialog.destroy()
438               
439                if response == gtk.RESPONSE_OK:
440                    self.kill_scan()
441                else:
442                    return
443           
444            except:
445                pass
446                       
447            try:
448                self.command_execution.run_scan()
449                self.status.set_scanning()
450            except OSError, msg:
451                warn_dialog = HIGAlertDialog(
452                    message_format="Nmap couldn't be found",
453                    secondary_text="Umit Quick Scan couldn't find Nmap. " 
454                    "Please check your Nmap instalation.",
455                    type=gtk.MESSAGE_ERROR)
456                warn_dialog.run()
457                warn_dialog.destroy()
458            except Exception, msg:
459                warn_dialog = HIGAlertDialog(
460                    message_format="Command is missing!",
461                    secondary_text="Please check your profile's command.",
462                    type=gtk.MESSAGE_ERROR)
463                warn_dialog.run()
464                warn_dialog.destroy()
465               
466               
467            self.verify_thread_timeout_id = gobject.timeout_add(2000, 
468                self.verify_execution)
469           
470            return
471       
472        else:
473            self.command_execution = None
474            return False
475
476    def save_scan(self, host):
477        from umit.core.UmitDB import Scans
478        store = Scans(scan_name="Quick Scan on %s" % host,
479                      nmap_xml_output=self.command_execution.get_xml_output(),
480                      date=datetime.now())
481       
482    def verify_execution(self):
483        try:
484            alive = self.command_execution.scan_state()
485        except:
486            self.disable_widgets()
487            self.status.set_scan_failed()
488            #self.scan_result.set_nmap_output(self.command_execution.get_error())
489            #self.emit("scan-finished")
490            return False
491
492        # Maybe this automatic refresh should be eliminated
493        # to avoid processor burning
494        #self.scan_result.refresh_nmap_output()
495       
496        if alive:
497            return True
498        else:
499            #self.parse_result(self.command_execution.get_xml_output_file())
500            #self.emit("scan-finished")
501            return False
502   
503class Result(gtk.HPaned):
504
505    __gsignals__ = {
506        'scan-finished' : (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, ())
507    }
508   
509    def __init__(self):
510        gtk.HPaned.__init__(self)
511        self.status = Status()
512        self.status.set_parsing_result()
513        self.parsed = None
514       
515    def set_nmap_output(self, host, msg, command):
516       
517        # Nmap output in parameter msg
518     
519        text_out = "Running a %s on %s...\n" % (command, host)
520        text_out += "IP: "
521               
522        if re.compile("\d{1,3}(\.\d{1,3}){3}").search(msg):
523            text_out += re.compile("\d{1,3}(\.\d{1,3}){3}").search(msg).group()
524        else:
525            text_out += "not identified"
526               
527        text_out += "\tPorts:\t"
528        services = []
529               
530        for reg in msg.split("\n"):
531            if reg.find("open") != -1:
532                try:
533                    text_out += "%s " % re.search("[0-9]+", reg.replace("open", "").split()[0]).group()
534                    services.append(reg.replace("open", "").split()[1])
535                    flag_open = True
536                except AttributeError:
537                    pass
538                       
539            elif reg.find("closed") != -1:
540                try:
541                    text_out += "%s " % re.search("[0-9]+", reg.replace("closed", "").split()[0]).group()
542                    services.append(reg.replace("closed", "").split()[1])
543                except AttributeError:
544                    pass
545                       
546        text_out += "\n\t\t\t\tServices:\t"
547                       
548        for s in services:
549            text_out += "%s " % s
550
551           
552        return text_out
553       
554               
555if __name__ == "__main__":
556    a = EntryField()
Note: See TracBrowser for help on using the browser.