root/trunk/umit/core/SearchResult.py @ 4240

Revision 4240, 13.7 kB (checked in by gpolo, 4 years ago)

Restructuring umit, second step. See ticket #229.

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 os
24import os.path
25
26from glob import glob
27from fnmatch import fnmatch
28from tempfile import mktemp
29from types import StringTypes
30
31from umit.core.UmitDB import UmitDB
32from umit.core.NmapParser import NmapParser
33from umit.core.UmitLogging import log
34
35
36class SearchResult(object):   
37    def __init__(self):
38        """This method is called by classes that inherit this one at their
39        constructor methods. If in the future this method get some
40        functionallity, then, it will work fine for those classes that inherit
41        this one.
42        """
43        pass
44   
45    def search(self, **kargs):
46        log.debug(">>> Starting search process...")
47        parameters = ["keyword", "profile", "option", "target",
48                      "mac", "ipv4", "ipv6", "port", "service",
49                      "osclass", "osmatch", "product"]
50
51        # If nothing is passed, let's considerate that we want to search
52        # every port
53        self.port_closed = kargs.get("port_closed", True)
54        self.port_open = kargs.get("port_open", True)
55        self.port_filtered = kargs.get("port_filtered", True)
56
57
58        # Iterate over scan results searching for patterns
59        # Obs: This search looks for a result that matches each received
60        # parameters ("and" based search). If something fail, it
61        # desconsiderates the result
62       
63        keys = kargs.keys() # Catch given parameters names
64        log.debug(">>> Search parameters: %s" % keys)
65       
66        # Get parsed results, as NmapParser objects
67        for scan_result in self.get_scan_results():
68            self.parsed_scan = scan_result
69
70            # Test each given parameter against current parsed result
71            for parameter in parameters:
72                if parameter in keys:
73                    log.debug(">>> Searching '%s' at '%s'" % (parameter,
74                                                              kargs[parameter]))
75                   
76                    if not self.__getattribute__("match_%s" % parameter)\
77                       (kargs[parameter]):
78                        # A break here, means that there is no match for the
79                        # given pattern and, as it is an "and" based search, the
80                        # parsed result must be discarted
81                        log.debug(">>> Parsed result doesn't match patterns!")
82                        break
83            else:
84                log.debug(">>> Parsed result matches given patterns!")
85                yield self.parsed_scan
86
87        # If current scan result matches the pattern, yield the parsed object
88        # Else discart parsed result, and get another! ;-)
89
90    def get_scan_results(self):
91        # To be implemented by classes that are going to inherit this one
92        pass
93
94    def basic_match(self, keyword, property):
95        if keyword == "*" or keyword == "" or \
96           fnmatch(str(self.parsed_scan.__getattribute__(property)),
97                   "*%s*" % keyword):
98            return True # Pattern matches
99        return False # Pattern doesn't match
100
101    def match_keyword(self, keyword):
102        log.debug("Match keyword: %s" % keyword)
103        if self.match_profile(keyword) or \
104           self.match_option(keyword) or \
105           self.match_target(keyword) or \
106           self.match_mac(keyword) or \
107           self.match_ipv4(keyword) or \
108           self.match_ipv6(keyword) or \
109           self.match_service(keyword) or \
110           self.match_osmatch(keyword) or \
111           self.match_product(keyword) or \
112           self.basic_match(keyword, "nmap_output") or \
113           self.basic_match(keyword, "profile_name"):
114            return True
115
116    def match_profile(self, profile):
117        log.debug("Match profile: %s" % profile)
118        log.debug("Comparing: %s == %s ??" % \
119                  (str(self.parsed_scan.profile_name).lower(),
120                   "*%s*" % profile.lower()))
121        if profile == "*" or profile == "" or \
122           fnmatch(str(self.parsed_scan.profile_name).lower(),
123                   "*%s*" % profile.lower()):
124            return True # Pattern matches
125
126        return False # Pattern doesn't match
127
128        # FIXME: What is this?? I have commented this line, because it was
129        # useless Though, I don't have the time now to find out what is going
130        # on here but I'll do that later
131        #return self.basic_match(profile, "profile_name")
132   
133    def match_option(self, option):
134        log.debug("Match option: %s" % option)
135        return self.basic_match(option, "profile_options")
136
137    def match_target(self, target):
138        log.debug("Match target: %s" % target)
139        return self.basic_match(target, "target") or\
140               self.basic_match(target, "nmap_command")
141
142    def match_mac(self, mac):
143        log.debug("Match mac: %s" % mac)
144        return self.basic_match(mac, "mac")
145
146    def match_ipv4(self, ipv4):
147        log.debug("Match IPv4: %s" % ipv4)
148        return self.basic_match(ipv4, "ipv4")
149
150    def match_ipv6(self, ipv6):
151        log.debug("Match IPv6: %s" % ipv6)
152        return self.basic_match(ipv6, "ipv6")
153
154    def match_port(self, port):
155        log.debug("Match port:%s" % port)
156        if port == [""] or port == ["*"]:
157            return True
158       
159        ports = []
160       
161        for port in self.parsed_scan.ports:
162            if self.port_open and portid["state"] == "open":
163                ports.append(portid["portid"])
164            elif self.port_filtered and portid["state"] == "filtered":
165                ports.append(portid["portid"])
166            elif self.port_closed and portid["state"] == "closed":
167                ports.append(portid["portid"])
168            elif not self.port_open and \
169                    not self.port_filtered and \
170                    not self.port_closed:
171                # In case every port state is False, add every port
172                ports.append(portid["portid"])
173
174        for keyport in port:
175            if keyport not in ports:
176                return False # No match for asked port
177        else:
178            return True # Every given port matched current result
179
180    def match_service(self, service):
181        log.debug("Match service: %s" % service)
182        if service == "" or service == "*":
183            return True
184       
185        services = []
186        for port in self.parsed_scan.ports:
187            if 'name' in port and port['name'] not in services:
188                services.append(port["service_name"])
189
190        if service in services:
191            return True # Given service name matched current result
192        return False # Given service name didn't match current result
193
194    def match_osclass(self, osclass):
195        log.debug("Match osclass: %s" % osclass)
196        if osclass == "" or osclass == "*":
197            return True
198
199        class_info = self.split_osclass(osclass)
200        log.debug("Class info: %s" % class_info)
201       
202        for host in self.parsed_scan.hosts:
203            for oc in host.osclasses:
204                #log.debug("Vendor: %s" % oc.get("vendor", ""))
205                #log.debug("OS Family: %s" % oc.get("osfamily", ""))
206                #log.debug("OS Gen: %s" % oc.get("osgen", ""))
207                #log.debug("Type: %s" % oc.get("type", ""))
208               
209                if oc.get("vendor", "").lower() == class_info[0] and \
210                       oc.get("osfamily", "").lower() == class_info[1] and \
211                       oc.get("osgen", "").lower() == class_info[2] and \
212                       oc.get("type", "").lower() == class_info[3]:
213                    return True # Found a match
214        return False
215
216    def match_osmatch(self, osmatch):
217        # XXX only the last osmatch is being used
218        log.debug("Match osmatch: %s" % osmatch)
219        if osmatch == "" or osmatch == "*":
220            return True
221
222        for host in self.parsed_scan.hosts:
223            if not host.osmatch:
224                continue
225            match = host.osmatch[-1].get("name", False)
226            if match and fnmatch(match.lower(), "*%s*" % osmatch.lower()):
227                return True
228        return False
229
230    def match_product(self, product):
231        log.debug("Match product: %s" % product)
232        if product == "" or product == "*":
233            return True
234       
235        products = []
236        for first in self.parsed_scan.ports:
237            for ports in first:
238                for port in ports["port"]:
239                    if fnmatch(port.get("service_product", "").lower(),
240                               "*%s*" % product.lower()):
241
242                        # Given service product matched current result
243                        return True
244
245        # Given service product didn't match current result
246        return False
247
248    def split_osclass(self, osclass):
249        return [i.strip().lower() for i in osclass.split("|")]
250
251class SearchDB(SearchResult, object):
252    def __init__(self):
253        SearchResult.__init__(self)
254        log.debug(">>> SearchDB initialized")
255
256    def get_scan_results(self):
257        log.debug(">>> Getting scan results stored in data base")
258        u = UmitDB()
259
260        for scan in u.get_scans():
261            log.debug(">>> Retrieving result of scans_id %s" % scan.scans_id)
262            log.debug(">>> Nmap xml output: %s" % scan.nmap_xml_output)
263           
264            temp_file = mktemp(".usr", "umit_")
265           
266            tmp = open(temp_file, "w")
267            tmp.write(scan.nmap_xml_output)
268            tmp.close()
269
270            try:
271                parsed = NmapParser()
272                parsed.set_xml_file(temp_file)
273                parsed.parse()
274               
275                # Remove temporary file reference
276                parsed.nmap_xml_file = ""
277            except:
278                pass
279            else:
280                yield parsed
281
282class SearchDir(SearchResult, object):
283    def __init__(self, search_directory, file_extensions=["usr"]):
284        SearchResult.__init__(self)
285        log.debug(">>> SearchDir initialized")
286        self.search_directory = search_directory
287
288        if type(file_extensions) in StringTypes:
289            self.file_extensions = file_extensions.split(";")
290        elif type(file_extensions) == type([]):
291            self.file_extensions = file_extensions
292        else:
293            raise Exception("Wrong file extension format! '%s'" %
294                            file_extensions)
295
296    def get_scan_results(self):
297        log.debug(">>> Getting directory's scan results")
298        files = []
299        for ext in self.file_extensions:
300            files += glob(os.path.join(self.search_directory, "*.%s" % ext))
301
302        log.debug(">>> Scan results at selected directory: %s" % files)
303        for scan_file in files:
304            log.debug(">>> Retrieving scan result %s" % scan_file)
305            if os.access(scan_file, os.R_OK) and os.path.isfile(scan_file):
306
307                try:
308                    parsed = NmapParser()
309                    parsed.set_xml_file(scan_file)
310                    parsed.parse()
311                except:
312                    pass
313                else:
314                    yield parsed
315
316class SearchTabs(SearchResult, object):
317    def __init__(self, notebook):
318        self.scan_notebook = notebook
319
320    def get_scan_results(self):
321        scan_file = None
322        for i in range(self.scan_notebook.get_n_pages()):
323            sbook_page = self.scan_notebook.get_nth_page(i)
324
325            if not sbook_page.status.get_empty():
326                scan_file = sbook_page.parsed.nmap_xml_file
327                if hasattr(scan_file, "name"):
328                    # this scan was loaded from a file so nmap_xml_file is
329                    # actually a file object, but we are interested only in
330                    # the file name.
331                    scan_file = scan_file.name
332
333            if scan_file and os.access(scan_file, os.R_OK) and\
334               os.path.isfile(scan_file):
335                log.debug(">>> Retrieving unsaved scan result: %s" % scan_file)
336
337                try:
338                    parsed = NmapParser()
339                    parsed.set_xml_file(scan_file)
340                    parsed.parse()
341                    parsed.scan_name = "Unsaved " + sbook_page.get_tab_label()
342                    parsed.unsaved = True
343                except:
344                    pass
345                else:
346                    yield parsed
347
348if __name__ == "__main__":
349    s = SearchDir("/home/adriano/umit/test", ["usr", "xml"])
350    for result in s.search(\
351        keyword="",
352        #profile="",
353        #option="",
354        #started="1121737119",
355        #finished="1121737192",
356        #target="10.0.0.100-180",
357        #mac=":",
358        #ipv4="10.0.0.150",
359        #ipv6="",
360        #uptime=209980,
361        # lastboot="", MUST BE REMOVED FROM THE UI!
362        #port=["22", "80"],
363        #port_open="",
364        #port_filtered="",
365        #port_closed="",
366        #service="",
367        #osclass="Microsoft | Windows | 95/98/ME | General Purpose",
368        #osmatch="gentoo",
369        #product="Apache"\
370        ):
371
372        print "Ports:", result.hosts[-1].ports
373
Note: See TracBrowser for help on using the browser.