Add : service without host will be just droped, like Nagios.
[shinken.git] / libexec / nmap_discovery_runner.py
blob377c12ae5edea3cbdf7d4e915d58f33101bd0421
1 #!/usr/bin/env python
2 #Copyright (C) 2009-2010 :
3 # Gabes Jean, naparuba@gmail.com
4 # Gerhard Lausser, Gerhard.Lausser@consol.de
5 # Gregory Starck, g.starck@gmail.com
7 #This file is part of Shinken.
9 #Shinken is free software: you can redistribute it and/or modify
10 #it under the terms of the GNU Affero General Public License as published by
11 #the Free Software Foundation, either version 3 of the License, or
12 #(at your option) any later version.
14 #Shinken 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 Affero General Public License for more details.
19 #You should have received a copy of the GNU Affero General Public License
20 #along with Shinken. If not, see <http://www.gnu.org/licenses/>.
22 #sudo nmap 192.168.0.1 -T4 -O --traceroute -oX toto.xml
24 import optparse
25 import sys
26 import os
27 import tempfile
28 import subprocess
30 try:
31 # xml.etree.ElementTree is new in Python 2.5
32 from xml.etree.ElementTree import ElementTree
33 except ImportError:
34 sys.exit("This script needs the Python ElementTree module. Please install it")
36 VERSION = '0.1'
38 parser = optparse.OptionParser(
39 "%prog [options] -t nmap scanning targets",
40 version="%prog " + VERSION)
42 parser.add_option('-t', '--targets', dest="targets",
43 help="NMap scanning targets.")
44 parser.add_option('-v', '--verbose', dest="verbose", action='store_true',
45 help="Verbose output.")
47 targets = []
48 opts, args = parser.parse_args()
49 if not opts.targets:
50 parser.error("Requires at least one nmap target for scanning (option -t/--targets")
51 else:
52 targets.append(opts.targets)
54 if not opts.verbose:
55 verbose = False
56 else:
57 verbose = True
59 if args:
60 targets.extend(args)
62 print "Got our target", targets
64 def debug(txt):
65 if verbose:
66 print txt
68 # Says if a host is up or not
69 def is_up(h):
70 status = h.find('status')
71 state = status.attrib['state']
72 return state == 'up'
76 class DetectedHost:
77 def __init__(self):
78 self.ip = ''
79 self.mac_vendor = ''
80 self.host_name = ''
82 self.os_possibilities = []
83 self.os = ('', '')
84 self.open_ports = []
86 self.parent = ''
89 # Keep the first name we've got
90 def set_host_name(self, name):
91 if self.host_name == '':
92 self.host_name = name
95 # Get a identifier for this host
96 def get_name(self):
97 if self.host_name != '':
98 return self.host_name
99 if self.ip != '':
100 return self.ip
101 return None
103 # We look for the host VMWare
104 def is_vmware_esx(self):
105 # If it's not a virtual machine bail out
106 if self.mac_vendor != 'VMware':
107 return False
108 # If we got all theses ports, we are quite ok for
109 # a VMWare host
110 needed_ports = [22, 80, 443, 902, 903, 5989]
111 for p in needed_ports:
112 if p not in self.open_ports:
113 # find one missing port, not a VMWare host
114 return False
115 # Ok all ports are found, we are a ESX :)
116 return True
118 # Says if we are a virtual machine or not
119 def is_vmware_vm(self):
120 # special case : the esx host itself
121 if self.is_vmware_esx():
122 return False
123 # Else, look at the mac vendor
124 return self.mac_vendor == 'VMware'
127 # Fill the different os possibilities
128 def add_os_possibility(self, os, osgen, accuracy):
129 self.os_possibilities.append( (os, osgen, accuracy) )
132 # We search if our potential parent is present in the
133 # other detected hosts. If so, set it as my parent
134 def look_for_parent(self, all_hosts):
135 self.parents = []
136 parent = self.parent
137 debug("Look for my parent %s -> %s" % (self.get_name(), parent))
138 # Ok, we didn't find any parent
139 # we bail out
140 if parent == '':
141 return
142 for h in all_hosts:
143 debug("Is it you? %s" % h.get_name())
144 if h.get_name() == parent:
145 debug("Houray, we find our parent %s -> %s" % (self.get_name(), h.get_name()))
146 self.parents.append(h.get_name())
151 # Look at ours oses and see which one is the better
152 def compute_os(self):
153 self.os_name = 'Unknown OS'
154 self.os_version = 'Unknown Version'
156 # bailout if we got no os :(
157 if len(self.os_possibilities) == 0:
158 return
160 max_accuracy = 0
161 for (os, osgen, accuracy) in self.os_possibilities:
162 if accuracy > max_accuracy:
163 max_accuracy = accuracy
165 # now get the entry with the max value
166 for (os, osgen, accuracy) in self.os_possibilities:
167 if accuracy == max_accuracy:
168 self.os = (os, osgen)
172 #Ok, unknown os... not good
173 if self.os == ('', ''):
174 return
176 map = {('Windows', '2000') : 'windows',
177 ('Windows', '2003') : 'windows',
178 ('Windows', '7') : 'windows',
179 ('Windows', 'XP') : 'windows',
180 # ME? you are a stupid moron!
181 ('Windows', 'Me') : 'windows',
182 ('Windows', '2008') : 'windows',
183 # that's a good boy :)
184 ('Linux', '2.6.X') : 'linux',
185 ('Linux', '2.4.X') : 'linux',
186 # HPUX? I think you didn't choose...
187 ('HP-UX', '11.X') : 'hpux',
188 ('HP-UX', '10.X') : 'hpux',
191 if self.os not in map:
192 return
194 self.os_name = map[self.os]
195 self.os_version = self.os[1]
196 # self.templates.append(t)
198 # # Look for VMWare VM or hosts
199 # if self.h.is_vmware_vm():
200 # self.templates.append('vmware-vm')
201 # # Now is an host?
202 # if self.h.is_vmware_esx():
203 # self.templates.append('vmware-host')
206 # Return the string of the 'discovery' items
207 def get_discovery_output(self):
208 r = []
209 r.append('%s::isup=1' % self.get_name())
210 r.append(self.get_discovery_system())
211 r.append(self.get_discovery_macvendor())
212 op = self.get_discovery_ports()
213 if op != '':
214 r.append(op)
215 par = self.get_discovery_parents()
216 if par != '':
217 r.append(par)
218 fqdn = self.get_dicovery_fqdn()
219 if fqdn != '':
220 r.append(fqdn)
221 ip = self.get_discovery_ip()
222 if ip != '':
223 r.append(ip)
224 return r
227 # for system output
228 def get_discovery_system(self):
229 r = '%s::os=%s' % (self.get_name(), self.os_name)+'\n'
230 r += '%s::osversion=%s' % (self.get_name(), self.os_version)
231 return r
233 def get_discovery_macvendor(self):
234 return '%s::macvendor=%s' % (self.get_name(), self.mac_vendor)
236 def get_discovery_ports(self):
237 if self.open_ports == []:
238 return ''
239 return '%s::openports=%s' % (self.get_name(), ','.join([str(p) for p in self.open_ports]))
241 def get_discovery_parents(self):
242 if self.parents == []:
243 return ''
244 return '%s::parents=%s' % (self.get_name(), ','.join(self.parents))
246 def get_dicovery_fqdn(self):
247 if self.host_name == '':
248 return ''
249 return '%s::fqdn=%s' % (self.get_name(), self.host_name)
251 def get_discovery_ip(self):
252 if self.ip == '':
253 return ''
254 return '%s::ip=%s' % (self.get_name(), self.ip)
257 (_, tmppath) = tempfile.mkstemp()
259 print "propose a tmppath", tmppath
261 cmd = "sudo nmap %s -T4 -O --traceroute -oX %s" % (' '.join(targets) , tmppath)
262 print "Launching command,", cmd
263 try:
264 nmap_process = subprocess.Popen(
265 cmd,
266 stdout=subprocess.PIPE, stderr=subprocess.PIPE,
267 close_fds=True, shell=True)
268 except OSError , exp:
269 print "Debug : Error in launching command:", cmd, exp
270 sys.exit(2)
272 print "Try to communicate"
273 (stdoutdata, stderrdata) = nmap_process.communicate()
275 if nmap_process.returncode != 0:
276 print "Error : the nmap return an error : '%s'" % stderrdata
277 sys.exit(2)
279 print "Got it", (stdoutdata, stderrdata)
281 xml_input = tmppath
283 tree = ElementTree()
284 try:
285 tree.parse(xml_input)
286 except IOError, exp:
287 print "Error opening file '%s' : %s" % (xml_input, exp)
288 sys.exit(2)
290 hosts = tree.findall('host')
291 debug("Number of hosts : %d" % len(hosts))
294 all_hosts = []
296 for h in hosts:
297 # Bypass non up hosts
298 if not is_up(h):
299 continue
301 dh = DetectedHost()
303 # Now we get the ipaddr and the mac vendor
304 # for future VMWare matching
305 #print h.__dict__
306 addrs = h.findall('address')
307 for addr in addrs:
308 #print "Address", addr.__dict__
309 addrtype = addr.attrib['addrtype']
310 if addrtype == 'ipv4':
311 dh.ip = addr.attrib['addr']
312 if addrtype == "mac":
313 if 'vendor' in addr.attrib:
314 dh.mac_vendor = addr.attrib['vendor']
317 # Now we've got the hostnames
318 host_names = h.findall('hostnames')
319 for h_name in host_names:
320 h_names = h_name.findall('hostname')
321 for h_n in h_names:
322 #print 'hname', h_n.__dict__
323 #print 'Host name', h_n.attrib['name']
324 dh.set_host_name(h_n.attrib['name'])
327 # Now print the traceroute
328 traces = h.findall('trace')
329 for trace in traces:
330 #print trace.__dict__
331 hops = trace.findall('hop')
332 #print "Number of hops", len(hops)
333 distance = len(hops)
334 if distance >= 2:
335 for hop in hops:
336 ttl = int(hop.attrib['ttl'])
337 #We search for the direct father
338 if ttl == distance-1:
339 #print ttl
340 #print "Super hop", hop.__dict__
341 # Get the host name if possible, if not
342 # take the IP
343 if 'host' in hop.attrib:
344 dh.parent = hop.attrib['host']
345 else:
346 dh.parent = hop.attrib['ipaddr']
349 # Now the OS detection
350 ios = h.find('os')
351 #print os.__dict__
352 cls = ios.findall('osclass')
353 for c in cls:
354 #print "Class", c.__dict__
355 family = c.attrib['osfamily']
356 accuracy = c.attrib['accuracy']
357 if 'osgen' in c.attrib:
358 osgen = c.attrib['osgen']
359 else:
360 osgen = None
361 #print "Type:", family, osgen, accuracy
362 dh.add_os_possibility(family, osgen, accuracy)
363 # Ok we can compute our OS now :)
364 dh.compute_os()
367 # Now the ports :)
368 allports = h.findall('ports')
369 for ap in allports:
370 ports = ap.findall('port')
371 for p in ports:
372 #print "Port", p.__dict__
373 p_id = p.attrib['portid']
374 s = p.find('state')
375 #print s.__dict__
376 state = s.attrib['state']
377 if state == 'open':
378 dh.open_ports.append(int(p_id))
380 #print dh.__dict__
381 all_hosts.append(dh)
382 #print "\n\n"
386 for h in all_hosts:
387 name = h.get_name()
388 if not name:
389 continue
391 debug("Doing name %s" % name)
392 #path = os.path.join(output_dir, name+'.discover')
393 #print "Want path", path
394 #f = open(path, 'wb')
395 #cPickle.dump(h, f)
396 #f.close()
397 debug(str(h.__dict__))
398 # And generate the configuration too
399 h.look_for_parent(all_hosts)
400 #c.fill_system_conf()
401 #c.fill_ports_services()
402 #c.fill_system_services()
403 # c.write_host_configuration()
404 #print "Host config", c.get_cfg_for_host()
405 # c.write_services_configuration()
406 #print "Service config"
407 #print c.get_cfg_for_services()
408 #print c.__dict__
409 print '\n'.join(h.get_discovery_output())
410 #print "\n\n\n"
413 # Try to remove the temppath
414 try:
415 os.unlink(tmppath)
416 except Exception:
417 pass