root/branch/hildon/umitCore/NmapCommand.py @ 648

Revision 648, 12.1 kB (checked in by boltrix, 6 years ago)

Work on having branch/hildon version working on windows.

Line 
1# Copyright (C) 2005 Insecure.Com LLC.
2#
3# Author: Adriano Monteiro Marques   <py.adriano@gmail.com>
4#
5# This program is free software; you can redistribute it and/or modify
6# it under the terms of the GNU General Public License as published by
7# the Free Software Foundation; either version 2 of the License, or
8# (at your option) any later version.
9#
10# This program is distributed in the hope that it will be useful,
11# but WITHOUT ANY WARRANTY; without even the implied warranty of
12# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13# GNU General Public License for more details.
14#
15# You should have received a copy of the GNU General Public License
16# along with this program; if not, write to the Free Software
17# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
18
19import sys
20import os
21import re
22import threading
23
24from tempfile import mktemp
25from types import StringTypes
26from subprocess import Popen, PIPE
27
28from umitCore.NmapOptions import NmapOptions
29from umitCore.Paths import Path
30from umitCore.Logging import log
31
32option_xml = Path.options
33
34# shell_state = True avoids python to open a terminal to execute nmap.exe
35# shell_state = False is needed to run correctly at Linux
36shell_state = (sys.platform == "win32")
37
38nmap_command_path = "nmap"
39# Don't need the line below anymore
40#if sys.platform == "win32":
41#   nmap_command_path = os.path.join(os.path.split(os.path.abspath(sys.executable))[0], "Nmap", "nmap.exe")
42
43log.debug(">>> Platform: %s" % sys.platform)
44log.debug(">>> Nmap command path: %s" % nmap_command_path)
45
46class NmapCommand(object):
47    def __init__(self, command=None):
48        self.xml_output = mktemp()
49        self.normal_output = mktemp()
50        self.stdout_output = mktemp()
51        self.stderr_output = mktemp()
52       
53        # Creating files. Avoid troubles while running at Windows
54        open(self.xml_output,'w').close()
55        open(self.normal_output,'w').close()
56        open(self.stdout_output,'w').close()
57        open(self.stderr_output,'w').close()
58
59        self.command_process = None
60        self.command_buffer = ""
61        self.command_stderr = ""
62
63        if command:
64            self.command = command
65
66    def get_command(self):
67        if type(self._command) == type(""):
68            return self._command.split()
69        return self._command
70
71    def set_command(self, command):
72        self._command = self._verify(command)
73
74    def _verify(self, command):
75        command = self._remove_double_space(command)
76        command = self._verify_output_options(command)
77        command[0] = nmap_command_path
78
79        return command
80
81    def _verify_output_options(self, command):
82        if type(command) == type([]):
83            command = " ".join(command)
84
85        # Removing comments from command
86        for comment in re.findall('(#.*)', command):
87            command = command.replace(comment, '')
88
89        # Removing output options that user may have set away from command
90        found = re.findall('(-o[XGASN]{1}) {0,1}', command)
91
92        # Adequating double quoted options
93        splited = [x.replace("\"", "") for x in re.findall('([\w\-\,\./]+|".*")', command)]
94        #splited = command.split(' ')
95
96        if found:
97            for option in found:
98                pos = splited.index(option)
99                del(splited[pos+1])
100                del(splited[pos])
101
102        # Saving the XML output to a temporary file
103        splited.append('-oX')
104        splited.append('%s' % self.xml_output)
105
106        # Saving the Normal output to a temporary file
107        splited.append('-oN')
108        splited.append('%s' % self.normal_output)
109
110        # Disable runtime interaction feature
111        #splited.append("--noninteractive")
112
113
114        # Redirecting output
115        #splited.append('>')
116        #splited.append('%s' % self.stdout_output)
117
118        return splited
119
120    def _remove_double_space(self, command):
121        if type(command) == type([]):
122            command = " ".join(command)
123
124        ## Found a better solution for this problem
125        #while re.findall('(  )', command):
126        #    command = command.replace('  ', ' ')
127
128
129        # The first join + split ensures to remove double spaces on lists like this:
130        # ["nmap    ", "-T4", ...]
131        # And them, we must return a list of the command, that's why we have the second split
132        return " ".join(command.split()).split()
133
134    def close(self):
135        # Remove temporary files created
136        self._stdout_handler.close()
137        self._stderr_handler.close()
138
139        os.remove(self.xml_output)
140        os.remove(self.normal_output)
141        os.remove(self.stdout_output)
142
143    def kill(self):
144        log.debug(">>> Killing scan process %s" % self.command_process.pid)
145
146        if sys.platform != "win32":
147            try:
148                from signal import SIGKILL
149                os.kill(self.command_process.pid, SIGKILL)
150            except:
151                pass
152        else:
153            # Try to find a function at Windows that kills the process
154            pass
155
156    def run_scan(self):
157        if self.command:
158            #self.command_process = Popen(self.command, bufsize=1, stdin=PIPE,
159            #                             stdout=PIPE, stderr=PIPE)
160           
161            # Because of problems with Windows, I passed only the file descriptors to \
162            # Popen and set stdin to PIPE
163            # Python problems... Cross-platform execution of process should be improved
164           
165            self._stdout_handler = open(self.stdout_output, "w+")
166            self._stderr_handler = open(self.stderr_output, "w+")
167           
168            self.command_process = Popen(self.command, bufsize=1,
169                                         stdin=PIPE,
170                                         stdout=self._stdout_handler.fileno(),
171                                         stderr=self._stderr_handler.fileno(),
172                                         shell=shell_state)
173        else:
174            raise Exception("You have no command to run! Please, set the command \
175before trying to start scan!")
176
177    def scan_state(self):
178        if self.command_process == None:
179            raise Exception("Scan is not running yet!")
180
181        state = self.command_process.poll()
182
183        ### Buffer is not been used anymore
184        ## This line blocks the GUI execution, once the read method waits until a
185        ## new content come to be buffered
186        #self.command_buffer += self.command_process.stdout.read()
187        ###
188       
189        if state == None:
190            return True # True means that the process is still running
191        elif state == 0:
192            return False # False means that the process had a successful exit
193        else:
194            self.command_stderr = self.get_error()
195           
196            log.critical("An error occourried during the scan execution!")
197            log.critical('%s' % self.command_stderr)
198            log.critical("Command that raised the exception: '%s'" % " ".join(self.command))
199           
200            raise Exception("An error occourried during the scan execution!\n'%s'" % \
201                            self.command_stderr)
202
203    def scan_progress(self):
204        """Should return a tuple with the stage and status of the scan execution progress.
205        Will work only when the runtime interaction problem is solved.
206        """
207        pass
208
209    def get_raw_output(self):
210        raw_desc = open(self.stdout_output, "r")
211        raw_output = raw_desc.readlines()
212       
213        raw_desc.close()
214        return "\\n".join(raw_output)
215
216    def get_output(self):
217        output_desc = open(self.stdout_output, "r")
218        output = output_desc.read()
219
220        output_desc.close()
221        return output
222
223    def get_output_file(self):
224        return self.stdout_output
225
226    def get_normal_output(self):
227        normal_desc = open(self.normal_output, "r")
228        normal = normal_desc.read()
229
230        normal_desc.close()
231        return normal
232
233    def get_xml_output(self):
234        xml_desc = open(self.xml_output, "r")
235        xml = xml_desc.read()
236
237        xml_desc.close()
238        return xml
239
240    def get_xml_output_file(self):
241        return self.xml_output
242
243    def get_normal_output_file(self):
244        return self.normal_output
245
246    def get_error(self):
247        error_desc = open(self.stderr_output, "r")
248        error = error_desc.read()
249
250        error_desc.close()
251        return error
252
253    command = property(get_command, set_command)
254    _command = None
255
256
257class CommandConstructor:
258    def __init__(self, profile=None):
259        self.option_profile = NmapOptions(option_xml)
260        self.options = {}
261
262    def add_option(self, option_name, args=[], level=False):
263        option = self.option_profile.get_option(option_name)
264
265        if type(args) in StringTypes:
266            args = [args]
267
268        if level:
269            self.options[option_name] = (option['option']+' ')*level
270        elif args:
271            args = tuple (args)
272            self.options[option_name] = option['option'] % args[0]
273        else:
274            self.options[option_name] = option['option']
275
276    def remove_option(self, option_name):
277        if option_name in self.options.keys():
278            del(self.options[option_name])
279
280    def get_command(self, target):
281        splited = ['%s' % nmap_command_path]
282        for option in self.options.values():
283            splited.append(option)
284
285        splited.append(target)
286        return ' '.join(splited)
287
288    def __remove_double_space(self, command):
289        while re.findall('(  )', command):
290            command = command.replace('  ', ' ')
291        return command
292
293class CommandThread(threading.Thread):
294    def __init__(self, command):
295        self._stop_event = threading.Event()
296        self._sleep = 1.0
297        threading.Thread.__init__(self)
298        self.command = command
299
300    def run(self):
301        #self.command_result = os.popen3(self.command)
302        self.command_result = os.system(self.command)
303
304    def join(self, timeout=None):
305        self._stop_event.set()
306        threading.Thread.join(self, timeout)
307
308
309##############
310# Exceptions #
311##############
312
313class WrongCommandType(Exception):
314    def __init__(self, command):
315        self.command = command
316
317    def __str__(self):
318        print "Command must be of type string! Got %s instead." % str(type(self.command))
319
320class OptionDependency(Exception):
321    def __init__(self, option, dependency):
322        self.option = option
323        self.dependency = dependency
324   
325    def __str__(self):
326        return "The given option '%s' has a dependency not commited: %s" %\
327               (self.option, self.dependency)
328
329class OptionConflict(Exception):
330    def __init__(self, option, option_conflict):
331        self.option = option
332        self.option_conflict = option_conflict
333   
334    def __str__(self):
335        return "The given option '%s' is conflicting with '%s'" %\
336               (self.option, self.option_conflict)
337
338class NmapCommandError(Exception):
339    def __init__(self, command, error):
340        self.error = error
341        self.command = command
342   
343    def __str__(self):
344        return """An error occouried while trying to execute nmap command.
345
346ERROR: %s
347COMMAND: %s
348""" % (self.error, self.command)
349
350
351
352
353# Testing module functionality! ;-)
354if __name__ == '__main__':
355    #command = CommandConstructor ('option_profile.uop')
356    #print 'Aggressive options:', command.add_option ('Aggressive Options')
357    #print 'UDP Scan:', command.add_option ('Version Detection')
358    #print 'UDP Scan:', command.add_option ('UDP Scan')
359    #command.add_option ('Idle Scan', ['10.0.0.138'])
360    #command.add_option ('UDP Scan')
361    #command.add_option ('ACK scan')
362    #command.remove_option ('Idle Scannn')
363   
364    #print command.get_command ('localhost')
365    #print command.get_command ('localhost')
366    #print command.get_command ('localhost')
367   
368    #from time import sleep
369   
370    #nmap = NmapCommand (command)
371    #executando = nmap.execute_nmap_command ()
372    #print nmap.command
373    #while executando[0].isAlive ():
374    #    print open(executando[3]).read()
375    #    sleep (1)
376    #print open(executando[3]).read()
377
378    scan = NmapCommand('%s -T4 -iL "/home/adriano/umit/test/targets\ teste"' % nmap_command_path)
379    scan.run_scan()
380
381    while scan.scan_state():
382        print ">>>", scan.get_normal_output()
383    print "Scan is finished!"
Note: See TracBrowser for help on using the browser.