Add : check_hpjd rule with printer discovery.
[shinken.git] / libexec / discovery.py
blobe5c42c0b1dd27db9d5e9dad18ac6a668436c7846
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 cPickle
27 import os
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] -x nmap.xml -o directory_output",
40 version="%prog " + VERSION)
41 parser.add_option('-x', '--xml-input',
42 dest="xml_input", help=('Output of nmap'))
43 parser.add_option('-o', '--dir-output', dest="output_dir",
44 help="Directory output for results")
45 parser.add_option('-d', '--cfg-dir-output', dest="cfg_output_dir",
46 help="Directory output for host/services generated configurations")
47 parser.add_option('-c', '--criticity', dest="criticity",
48 help="Criticity level of theses elements in the range (min)[0..5](max).")
50 opts, args = parser.parse_args()
52 if not opts.xml_input:
53 parser.error("Requires one nmap xml output file (option -x/--xml-input")
54 if not opts.output_dir:
55 parser.error("Requires one output directory (option -o/--dir-output")
56 if not opts.cfg_output_dir:
57 parser.error("Requires one configuration output directory (option -d/--cfg-dir-output")
58 #If no criticity is given, use the default one, 3
59 if not opts.criticity:
60 criticity = 3
61 else:
62 criticity = int(opts.criticity)
63 if args:
64 parser.error("Does not accept any argument.")
67 # Says if a host is up or not
68 def is_up(h):
69 status = h.find('status')
70 state = status.attrib['state']
71 return state == 'up'
74 class ConfigurationManager:
75 def __init__(self, h, path, criticity):
76 self.h = h
77 self.hosts_path = os.path.join(path, 'hosts')
78 self.srvs_path = os.path.join(path, 'services')
79 self.templates = ['generic-host']
80 self.services = []
81 self.criticity = criticity
82 self.parents = []
85 # We search if our potential parent is present in the
86 # other detected hosts. If so, set it as my parent
87 def look_for_parent(self, all_hosts):
88 parent = self.h.parent
89 print "Look for my parent", self.h.get_name(), "->", parent
90 # Ok, we didn't find any parent
91 # we bail out
92 if parent == '':
93 return
94 for h in all_hosts:
95 print "Is it you?", h.get_name()
96 if h.get_name() == parent:
97 print "Houray, we find our parent", self.h.get_name(), "->", h.get_name()
98 self.parents.append(h.get_name())
102 def fill_system_conf(self):
103 ios = self.h.os
105 #Ok, unknown os... not good
106 if ios == ('', ''):
107 return
109 map = {('Windows', '2000') : 'windows',
110 ('Windows', '2003') : 'windows',
111 ('Windows', '7') : 'windows',
112 ('Windows', 'XP') : 'windows',
113 # ME? you are a stupid moron!
114 ('Windows', 'Me') : 'windows',
115 ('Windows', '2008') : 'windows',
116 # that's a good boy :)
117 ('Linux', '2.6.X') : 'linux',
118 ('Linux', '2.4.X') : 'linux',
119 # HPUX? I think you didn't choose...
120 ('HP-UX', '11.X') : 'hpux',
121 ('HP-UX', '10.X') : 'hpux',
124 if ios not in map:
125 print "Unknown OS:", ios
126 return
128 t = map[ios]
129 self.templates.append(t)
131 # Look for VMWare VM or hosts
132 if self.h.is_vmware_vm():
133 self.templates.append('vmware-vm')
134 # Now is an host?
135 if self.h.is_vmware_esx():
136 self.templates.append('vmware-host')
139 def get_cfg_for_host(self):
140 props = {}
141 props['host_name'] = self.h.get_name()
142 props['criticity'] = self.criticity
144 # Parents if we got some
145 if self.parents != []:
146 props['parents'] = ','.join(self.parents)
148 # Now template
149 props['use'] = ','.join(self.templates)
151 print "Want to write", props
152 s = 'define host {\n'
153 for k in props:
154 v = props[k]
155 s += ' %s %s\n' % (k, v)
156 s += '}\n'
157 print s
158 return s
161 def get_cfg_for_services(self):
162 r = {}
163 print "And now services:"
164 for srv in self.services:
165 desc = srv['service_description']
166 s = 'define service {\n'
167 for k in srv:
168 v = srv[k]
169 s += ' %s %s\n' % (k, v)
170 s += '}\n'
171 print s
172 r[desc] = s
173 return r
177 def fill_ports_services(self):
178 for p in self.h.open_ports:
179 print "The port", p, " is open"
180 f = getattr(self, 'gen_srv_'+str(p), None)
181 if f:
184 def fill_system_services(self):
185 for t in self.templates:
186 print "Registering services for the template", t
187 # Python functions cannot be -, so we change it by _
188 t = t.replace('-','_')
189 print "Search for", 'gen_srv_'+str(t)
190 f = getattr(self, 'gen_srv_'+str(t), None)
191 if f:
195 def generate_service(self, desc, check):
196 srv = {'use' : 'generic-service', 'service_description' : desc, 'check_command' : check, 'host_name' : self.h.get_name()}
197 self.services.append(srv)
200 ######### For network ones
202 # HTTP
203 def gen_srv_80(self):
204 self.generate_service('HTTP', 'check_http')
206 # SSH
207 def gen_srv_22(self):
208 self.generate_service('SSH', 'check_ssh')
210 # HTTPS + certificate
211 def gen_srv_443(self):
212 self.generate_service('HTTPS', 'check_https')
213 self.generate_service('HTTPS-CERT', 'check_https_certificate')
215 # FTP
216 def gen_srv_21(self):
217 self.generate_service('FTP', 'check_ftp')
219 # DNS
220 def gen_srv_53(self):
221 self.generate_service('DNS', 'check_dig!$HOSTADDRESS$')
223 # Oracle Listener
224 def gen_srv_1521(self):
225 self.generate_service('Oracle-Listener', 'check_oracle_listener')
227 # Now for MSSQL
228 def gen_srv_1433(self):
229 self.generate_service('MSSQL-Connexion', 'check_mssql_connexion')
230 print "I will need check_mssql_health from http://labs.consol.de/nagios/check_mssql_health/"
232 # SMTP
233 def gen_srv_25(self):
234 self.generate_service('SMTP', 'check_smtp')
236 # And the SMTPS too
237 def gen_srv_465(self):
238 self.generate_service('SMTPS', 'check_smtps')
240 # LDAP
241 def gen_srv_389(self):
242 self.generate_service('Ldap', 'check_ldap')
244 # LDAPS
245 def gen_srv_636(self):
246 self.generate_service('Ldaps', 'check_ldaps')
248 #Mysql
249 def gen_srv_3306(self):
250 self.generate_service('Mysql', 'check_mysql_connexion')
253 ####
254 # For system ones
255 ####
256 def gen_srv_linux(self):
257 print "You want a Linux check, but I don't know what to propose, sorry..."
259 def gen_srv_windows(self):
260 print "You want a Windows check, but I don't know what to propose, sorry..."
262 #For a VM we can add cpu, io, mem and net
263 def gen_srv_vmware_vm(self):
264 self.generate_service('VM-Cpu', "check_esx_vm!cpu")
265 self.generate_service('VM-IO', "check_esx_vm!io")
266 self.generate_service('VM-Memory', "check_esx_vm!mem")
267 self.generate_service('VM-Network', "check_esx_vm!net")
268 print "I will need the check_esx3.pl from http://www.op5.org/community/plugin-inventory/op5-projects/op5-plugins"
270 # Quite the same for the host
271 def gen_srv_vmware_host(self):
272 self.generate_service('ESX-host-Cpu', "check_esx_host!cpu")
273 self.generate_service('ESX-host-IO', "check_esx_host!io")
274 self.generate_service('ESX-host-Memory', "check_esx_host!mem")
275 self.generate_service('ESX-host-Network', "check_esx_host!net")
276 print "I will need the check_esx3.pl from http://www.op5.org/community/plugin-inventory/op5-projects/op5-plugins"
280 # Write the host cfg file
281 def write_host_configuration(self):
282 name = self.h.get_name()
283 # If the host is bad, get out
284 if not name:
285 return
287 # Write the directory with the host config
288 p = os.path.join(self.hosts_path, name)
289 print "I want to create", p
290 try:
291 os.mkdir(p)
292 except OSError, exp:
293 # If directory already exists, it's not a problem
294 if not exp.errno != '17':
295 print "Cannot create the directory '%s' : '%s'" % (p, exp)
296 return
297 cfg_p = os.path.join(p, name+'.cfg')
298 print "I want to write", cfg_p
299 s = self.get_cfg_for_host()
300 try:
301 fd = open(cfg_p, 'w')
302 except OSError, exp:
303 print "Cannot create the file '%s' : '%s'" % (cfg_p, exp)
304 return
305 fd.write(s)
306 fd.close()
309 def write_services_configuration(self):
310 name = self.h.get_name()
311 # If the host is bad, get out
312 if not name:
313 return
315 # Write the directory with the host config
316 p = os.path.join(self.srvs_path, name)
317 print "I want to create", p
318 try:
319 os.mkdir(p)
320 except OSError, exp:
321 # If directory already exist, it's not a problem
322 if not exp.errno != '17':
323 print "Cannot create the directory '%s' : '%s'" % (p, exp)
324 return
325 # Ok now we get the services to create
326 r = c.get_cfg_for_services()
327 for s in r:
328 cfg_p = os.path.join(p, name+'-'+s+'.cfg')
329 print "I want to write", cfg_p
330 buf = r[s]
331 try:
332 fd = open(cfg_p, 'w')
333 except OSError, exp:
334 print "Cannot create the file '%s' : '%s'" % (cfg_p, exp)
335 return
336 fd.write(buf)
337 fd.close()
342 class DetectedHost:
343 def __init__(self):
344 self.ip = ''
345 self.mac_vendor = ''
346 self.host_name = ''
348 self.os_possibilities = []
349 self.os = ('', '')
350 self.open_ports = []
352 self.parent = ''
355 # Keep the first name we've got
356 def set_host_name(self, name):
357 if self.host_name == '':
358 self.host_name = name
361 # Get a identifier for this host
362 def get_name(self):
363 if self.host_name != '':
364 return self.host_name
365 if self.ip != '':
366 return self.ip
367 return None
369 # We look for the host VMWare
370 def is_vmware_esx(self):
371 # If it's not a virtual machine bail out
372 if self.mac_vendor != 'VMware':
373 return False
374 # If we got all theses ports, we are quite ok for
375 # a VMWare host
376 needed_ports = [22, 80, 443, 902, 903, 5989]
377 for p in needed_ports:
378 if p not in self.open_ports:
379 # find one missing port, not a VMWare host
380 return False
381 # Ok all ports are found, we are a ESX :)
382 return True
384 # Says if we are a virtual machine or not
385 def is_vmware_vm(self):
386 # special case : the esx host itself
387 if self.is_vmware_esx():
388 return False
389 # Else, look at the mac vendor
390 return self.mac_vendor == 'VMware'
393 # Fill the different os possibilities
394 def add_os_possibility(self, os, osgen, accuracy):
395 self.os_possibilities.append( (os, osgen, accuracy) )
397 # Look at ours oses and see which one is the better
398 def compute_os(self):
399 # bailout if we got no os :(
400 if len(self.os_possibilities) == 0:
401 return
403 max_accuracy = 0
404 for (os, osgen, accuracy) in self.os_possibilities:
405 if accuracy > max_accuracy:
406 max_accuracy = accuracy
408 # now get the entry with the max value
409 for (os, osgen, accuracy) in self.os_possibilities:
410 if accuracy == max_accuracy:
411 self.os = (os, osgen)
414 xml_input = opts.xml_input
415 output_dir = opts.output_dir
416 cfg_output_dir = opts.cfg_output_dir
418 tree = ElementTree()
419 try:
420 tree.parse(xml_input)
421 except IOError, exp:
422 print "Error opening file '%s' : %s" % (xml_input, exp)
423 sys.exit(2)
425 hosts = tree.findall('host')
426 print "Number of hosts", len(hosts)
429 all_hosts = []
431 for h in hosts:
432 # Bypass non up hosts
433 if not is_up(h):
434 continue
436 dh = DetectedHost()
438 # Now we get the ipaddr and the mac vendor
439 # for future VMWare matching
440 #print h.__dict__
441 addrs = h.findall('address')
442 for addr in addrs:
443 #print "Address", addr.__dict__
444 addrtype = addr.attrib['addrtype']
445 if addrtype == 'ipv4':
446 dh.ip = addr.attrib['addr']
447 if addrtype == "mac":
448 if 'vendor' in addr.attrib:
449 dh.mac_vendor = addr.attrib['vendor']
452 # Now we've got the hostnames
453 host_names = h.findall('hostnames')
454 for h_name in host_names:
455 h_names = h_name.findall('hostname')
456 for h_n in h_names:
457 #print 'hname', h_n.__dict__
458 #print 'Host name', h_n.attrib['name']
459 dh.set_host_name(h_n.attrib['name'])
462 # Now print the traceroute
463 traces = h.findall('trace')
464 for trace in traces:
465 #print trace.__dict__
466 hops = trace.findall('hop')
467 #print "Number of hops", len(hops)
468 distance = len(hops)
469 if distance >= 2:
470 for hop in hops:
471 ttl = int(hop.attrib['ttl'])
472 #We search for the direct father
473 if ttl == distance-1:
474 print ttl
475 print "Super hop", hop.__dict__
476 # Get the host name if possible, if not
477 # take the IP
478 if 'host' in hop.attrib:
479 dh.parent = hop.attrib['host']
480 else:
481 dh.parent = hop.attrib['ipaddr']
484 # Now the OS detection
485 ios = h.find('os')
486 #print os.__dict__
487 cls = ios.findall('osclass')
488 for c in cls:
489 #print "Class", c.__dict__
490 family = c.attrib['osfamily']
491 accuracy = c.attrib['accuracy']
492 if 'osgen' in c.attrib:
493 osgen = c.attrib['osgen']
494 else:
495 osgen = None
496 #print "Type:", family, osgen, accuracy
497 dh.add_os_possibility(family, osgen, accuracy)
498 # Ok we can compute our OS now :)
499 dh.compute_os()
502 # Now the ports :)
503 allports = h.findall('ports')
504 for ap in allports:
505 ports = ap.findall('port')
506 for p in ports:
507 #print "Port", p.__dict__
508 p_id = p.attrib['portid']
509 s = p.find('state')
510 #print s.__dict__
511 state = s.attrib['state']
512 if state == 'open':
513 dh.open_ports.append(int(p_id))
515 print dh.__dict__
516 all_hosts.append(dh)
517 print "\n\n"
521 for h in all_hosts:
522 name = h.get_name()
523 if not name:
524 continue
526 print "Doing name", name
527 path = os.path.join(output_dir, name+'.discover')
528 print "Want path", path
529 f = open(path, 'wb')
530 cPickle.dump(h, f)
531 f.close()
533 # And generate the configuration too
534 c = ConfigurationManager(h, cfg_output_dir, criticity)
535 c.look_for_parent(all_hosts)
536 c.fill_system_conf()
537 c.fill_ports_services()
538 c.fill_system_services()
539 c.write_host_configuration()
540 c.write_services_configuration()
541 print c.__dict__