1 import BaseHTTPServer
2 import ConfigParser
3 import SocketServer
4 import base64
5 import datetime
6 import libxml2
7 import mimetools
8 import os
9 import random
10 import shlex
11 import signal
12 import socket
13 import string
14 import subprocess
15 import sys
17 def debug(level, verb, string):
18 if level <= verb:
19 print "%s: %s" % (level, string)
22 ##############################################################################
24 class ExecTimeoutError(Exception):
25 pass
27 ##############################################################################
29 def available_encodings():
30 return ['base64', 'plain',]
33 def check_encoding(enc):
34 if enc in available_encodings():
35 return True
36 else:
37 return False
40 def decode(data, encoding):
41 if encoding == 'plain':
42 return data
43 else:
44 return base64.b64decode(data)
47 def encode(data, encoding=None):
48 if encoding == 'plain':
49 return data
50 else:
51 return base64.b64encode(data)
54 ##############################################################################
56 def read_inifile(inifile):
57 config = ConfigParser.RawConfigParser()
58 config.optionxform = str # We need case-sensitive options
59 ini_list = config.read(inifile)
61 if ini_list:
62 return config
63 else:
64 return False
67 ##############################################################################
69 def exec_timeout_handler(signum, frame):
70 raise ExecTimeoutError
72 def exec_check(host_name, service_descr, cmdline, timeout=None, timeout_returncode=2):
73 cmdarray = shlex.split(cmdline)
75 check = {}
76 check['host_name'] = host_name
77 check['service_description'] = service_descr
79 if len(cmdarray) == 0:
80 check['output'] = 'No command line specified!'
81 check['returncode'] = 127
82 return check
84 if timeout:
85 signal.signal(signal.SIGALRM, exec_timeout_handler)
86 signal.alarm(timeout)
88 try:
89 cmd = subprocess.Popen(cmdarray, stdout=subprocess.PIPE)
90 check['output'] = cmd.communicate()[0].rstrip()
91 check['returncode'] = cmd.returncode
92 except OSError:
93 check['output'] = 'Could not execute "%s"' % cmdline
94 check['returncode'] = 127
95 except ExecTimeoutError:
96 check['output'] = 'Plugin timed out after %s seconds' % timeout
97 check['returncode'] = timeout_returncode
99 if timeout:
100 signal.alarm(0)
101 try:
102 if sys.version_info >= (2, 6):
103 cmd.terminate()
104 else:
105 os.kill(cmd.pid, 15)
106 except OSError:
107 pass
109 check['timestamp'] = datetime.datetime.now().strftime('%s')
110 return check
113 ##############################################################################
115 def conf2dict(config, opt_host=None, opt_service=None):
116 checks = []
118 # Read "plugin_timeout" from "[nagixsc]", default "None" (no timeout)
119 try:
120 timeout = config.getint('nagixsc','plugin_timeout')
121 except (ConfigParser.NoSectionError, ConfigParser.NoOptionError):
122 timeout = None
124 # Read "plugin_timeout_returncode" from "[nagixsc]", default "2" (CRITICAL)
125 try:
126 timeout_returncode = config.getint('nagixsc','plugin_timeout_returncode')
127 except (ConfigParser.NoSectionError, ConfigParser.NoOptionError):
128 timeout_returncode = 2
130 # Sections are Hosts (not 'nagixsc'), options in sections are Services
131 hosts = config.sections()
132 if 'nagixsc' in hosts:
133 hosts.remove('nagixsc')
135 # Filter out host/section if it exists
136 if opt_host:
137 if opt_host in hosts:
138 hosts = [opt_host,]
139 else:
140 hosts = []
142 for host in hosts:
143 # Overwrite section/host name with '_host_name'
144 if config.has_option(host,'_host_name'):
145 host_name = config.get(host,'_host_name')
146 else:
147 host_name = host
150 services = config.options(host)
151 # Look for host check
152 if '_host_check' in services and not opt_service:
153 cmdline = config.get(host, '_host_check')
154 check = exec_check(host_name, None, cmdline, timeout, timeout_returncode)
155 checks.append(check)
158 # Filter out service if given in cmd line options
159 if opt_service:
160 if opt_service in services:
161 services = [opt_service,]
162 else:
163 services = []
165 for service in services:
166 # If option starts with '_' it may be a NagixSC option in the future
167 if service[0] != '_':
168 cmdline = config.get(host, service)
170 check = exec_check(host_name, service, cmdline, timeout, timeout_returncode)
171 checks.append(check)
173 return checks
176 ##############################################################################
178 def dict2out_passive(checks, xmltimestamp, opt_pipe, opt_verb=0):
179 FORMAT_HOST = '[%s] PROCESS_HOST_CHECK_RESULT;%s;%s;%s'
180 FORMAT_SERVICE = '[%s] PROCESS_SERVICE_CHECK_RESULT;%s;%s;%s;%s'
181 count_services = 0
182 now = datetime.datetime.now().strftime('%s')
184 # Prepare
185 if opt_verb <= 2:
186 pipe = open(opt_pipe, "w")
187 else:
188 pipe = None
190 # Output
191 for check in checks:
192 count_services += 1
193 if check.has_key('timestamp'):
194 timestamp = check['timestamp']
195 else:
196 timestamp = xmltimestamp
197 count_services += 1
199 if check['service_description'] == None or check['service_description'] == '':
200 # Host check
201 line = FORMAT_HOST % (now, check['host_name'], check['returncode'], check['output'].replace('\n', '\\n'))
202 else:
203 # Service check
204 line = FORMAT_SERVICE % (now, check['host_name'], check['service_description'], check['returncode'], check['output'].replace('\n', '\\n'))
206 if pipe:
207 pipe.write(line + '\n')
208 debug(2, opt_verb, line)
210 # Close
211 if pipe:
212 pipe.close()
213 else:
214 print "Passive check results NOT written to Nagios pipe due to -vvv!"
216 return count_services
219 def dict2out_checkresult(checks, xmltimestamp, opt_checkresultdir, opt_verb):
220 count_services = 0
221 count_failed = 0
222 list_failed = []
223 chars = string.letters + string.digits
224 ctimestamp = datetime.datetime.now().ctime()
226 for check in checks:
227 count_services += 1
228 if check.has_key('timestamp'):
229 timestamp = check['timestamp']
230 else:
231 timestamp = xmltimestamp
233 filename = os.path.join(opt_checkresultdir, 'c' + ''.join([random.choice(chars) for i in range(6)]))
234 try:
235 crfile = open(filename, "w")
236 if check['service_description'] == None or check['service_description'] == '':
237 # Host check
238 crfile.write('### Active Check Result File ###\nfile_time=%s\n\n### Nagios Service Check Result ###\n# Time: %s\nhost_name=%s\ncheck_type=0\ncheck_options=0\nscheduled_check=1\nreschedule_check=1\nlatency=0.0\nstart_time=%s.00\nfinish_time=%s.05\nearly_timeout=0\nexited_ok=1\nreturn_code=%s\noutput=%s\n' % (timestamp, ctimestamp, check['host_name'], timestamp, timestamp, check['returncode'], check['output'].replace('\n', '\\n') ) )
239 else:
240 # Service check
241 crfile.write('### Active Check Result File ###\nfile_time=%s\n\n### Nagios Service Check Result ###\n# Time: %s\nhost_name=%s\nservice_description=%s\ncheck_type=0\ncheck_options=0\nscheduled_check=1\nreschedule_check=1\nlatency=0.0\nstart_time=%s.00\nfinish_time=%s.05\nearly_timeout=0\nexited_ok=1\nreturn_code=%s\noutput=%s\n' % (timestamp, ctimestamp, check['host_name'], check['service_description'], timestamp, timestamp, check['returncode'], check['output'].replace('\n', '\\n') ) )
242 crfile.close()
244 # Create OK file
245 open(filename + '.ok', 'w').close()
246 except:
247 count_failed += 1
248 list_failed.append([filename, check['host_name'], check['service_description']])
250 return (count_services, count_failed, list_failed)
253 ##############################################################################
255 def read_xml(options):
256 if options.url != None:
257 import urllib2
259 if options.httpuser and options.httppasswd:
260 passman = urllib2.HTTPPasswordMgrWithDefaultRealm()
261 passman.add_password(None, options.url, options.httpuser, options.httppasswd)
262 authhandler = urllib2.HTTPBasicAuthHandler(passman)
263 opener = urllib2.build_opener(authhandler)
264 urllib2.install_opener(opener)
266 try:
267 response = urllib2.urlopen(options.url)
268 except urllib2.HTTPError, error:
269 print error
270 sys.exit(0)
271 except urllib2.URLError, error:
272 print error.reason[1]
273 sys.exit(0)
275 doc = libxml2.parseDoc(response.read())
276 response.close()
278 else:
279 doc = libxml2.parseFile(options.file)
281 return doc
284 def read_xml_from_string(content):
285 return libxml2.parseDoc(content)
288 ##############################################################################
290 def xml_check_version(xmldoc):
291 # FIXME: Check XML structure
292 try:
293 xmlnagixsc = xmldoc.xpathNewContext().xpathEval('/nagixsc')[0]
294 except:
295 return (False, 'Not a Nag(IX)SC XML file!')
297 try:
298 if xmlnagixsc.prop('version') != "1.0":
299 return (False, 'Wrong version (found "%s", need "1.0") of XML file!' % xmlnagixsc.prop('version'))
300 except:
301 return (False, 'No version information found in XML file!')
303 return (True, 'XML seems to be ok')
306 def xml_get_timestamp(xmldoc):
307 try:
308 timestamp = int(xmldoc.xpathNewContext().xpathEval('/nagixsc/timestamp')[0].get_content())
309 except:
310 return False
312 return timestamp
315 def xml_to_dict(xmldoc, verb=0, hostfilter=None, servicefilter=None):
316 checks = []
317 now = int(datetime.datetime.now().strftime('%s'))
318 filetimestamp = reset_future_timestamp(xml_get_timestamp(xmldoc), now)
320 if hostfilter:
321 hosts = xmldoc.xpathNewContext().xpathEval('/nagixsc/host[name="%s"] | /nagixsc/host[name="%s"]' % (hostfilter, encode(hostfilter)))
322 else:
323 hosts = xmldoc.xpathNewContext().xpathEval('/nagixsc/host')
325 for host in hosts:
326 xmlhostname = host.xpathEval('name')[0]
327 hostname = decode(xmlhostname.get_content(), xmlhostname.prop('encoding'))
328 debug(2, verb, 'Found host "%s"' % hostname)
330 # Look for Host check result
331 if host.xpathEval('returncode'):
332 retcode = host.xpathEval('returncode')[0].get_content()
333 else:
334 retcode = None
336 if host.xpathEval('output'):
337 xmloutput = host.xpathEval('output')[0]
338 output = decode(xmloutput.get_content(), xmloutput.prop('encoding')).rstrip()
339 else:
340 output = None
342 if host.xpathEval('timestamp'):
343 timestamp = reset_future_timestamp(int(host.xpathEval('timestamp')[0].get_content()), now)
344 else:
345 timestamp = filetimestamp
347 # Append only if no service filter
348 if not servicefilter and retcode and output:
349 checks.append({'host_name':hostname, 'service_description':None, 'returncode':retcode, 'output':output, 'timestamp':timestamp})
352 # Look for service filter
353 if servicefilter:
354 services = host.xpathEval('service[description="%s"] | service[description="%s"]' % (servicefilter, encode(servicefilter)))
355 else:
356 services = host.xpathEval('service')
358 # Loop over services in host
359 for service in services:
360 service_dict = {}
362 xmldescr = service.xpathEval('description')[0]
363 xmloutput = service.xpathEval('output')[0]
365 srvdescr = decode(xmldescr.get_content(), xmldescr.prop('encoding'))
366 retcode = service.xpathEval('returncode')[0].get_content()
367 output = decode(xmloutput.get_content(), xmloutput.prop('encoding')).rstrip()
369 try:
370 timestamp = reset_future_timestamp(int(service.xpathEval('timestamp')[0].get_content()), now)
371 except:
372 timestamp = filetimestamp
374 debug(2, verb, 'Found service "%s"' % srvdescr)
376 service_dict = {'host_name':hostname, 'service_description':srvdescr, 'returncode':retcode, 'output':output, 'timestamp':timestamp}
377 checks.append(service_dict)
379 debug(1, verb, 'Host: "%s" - Service: "%s" - RetCode: "%s" - Output: "%s"' % (hostname, srvdescr, retcode, output) )
381 return checks
384 def xml_from_dict(checks, encoding='base64'):
385 lasthost = None
387 db = [(check['host_name'], check) for check in checks]
388 db.sort()
390 xmldoc = libxml2.newDoc('1.0')
391 xmlroot = xmldoc.newChild(None, 'nagixsc', None)
392 xmlroot.setProp('version', '1.0')
393 xmltimestamp = xmlroot.newChild(None, 'timestamp', datetime.datetime.now().strftime('%s'))
395 for entry in db:
396 check = entry[1]
398 if check['host_name'] != lasthost:
399 xmlhost = xmlroot.newChild(None, 'host', None)
400 xmlhostname = xmlhost.newChild(None, 'name', encode(check['host_name'], encoding))
401 lasthost = check['host_name']
403 if check['service_description'] == '' or check['service_description'] == None:
404 # Host check result
405 xmlreturncode = xmlhost.newChild(None, 'returncode', str(check['returncode']))
406 xmloutput = xmlhost.newChild(None, 'output', encode(check['output'], encoding))
407 xmloutput.setProp('encoding', encoding)
408 if check.has_key('timestamp'):
409 xmltimestamp = xmlhost.newChild(None, 'timestamp', str(check['timestamp']))
410 else:
411 # Service check result
412 xmlservice = xmlhost.newChild(None, 'service', None)
413 xmlname = xmlservice.newChild(None, 'description', encode(check['service_description'], encoding))
414 xmlname.setProp('encoding', encoding)
415 xmlreturncode = xmlservice.newChild(None, 'returncode', str(check['returncode']))
416 xmloutput = xmlservice.newChild(None, 'output', encode(check['output'], encoding))
417 xmloutput.setProp('encoding', encoding)
418 if check.has_key('timestamp'):
419 xmltimestamp = xmlservice.newChild(None, 'timestamp', str(check['timestamp']))
421 return xmldoc
424 def xml_merge(xmldocs):
425 checks = []
426 for xmldoc in xmldocs:
427 checks.extend(xml_to_dict(xmldoc))
428 newxmldoc = xml_from_dict(checks)
429 return newxmldoc
432 def check_mark_outdated(check, now, maxtimediff, markold):
433 timedelta = now - check['timestamp']
434 if timedelta > maxtimediff:
435 check['output'] = 'Nag(ix)SC: Check result is %s(>%s) seconds old - %s' % (timedelta, maxtimediff, check['output'])
436 if markold:
437 check['returncode'] = 3
438 return check
441 def reset_future_timestamp(timestamp, now):
442 if timestamp <= now:
443 return timestamp
444 else:
445 return now
447 ##############################################################################
449 def encode_multipart(xmldoc, httpuser, httppasswd):
450 BOUNDARY = mimetools.choose_boundary()
451 CRLF = '\r\n'
452 L = []
453 L.append('--' + BOUNDARY)
454 L.append('Content-Disposition: form-data; name="xmlfile"; filename="xmlfile"')
455 L.append('Content-Type: application/xml')
456 L.append('')
457 L.append(xmldoc.serialize())
458 L.append('--' + BOUNDARY + '--')
459 L.append('')
460 body = CRLF.join(L)
461 content_type = 'multipart/form-data; boundary=%s' % BOUNDARY
462 headers = {'Content-Type': content_type, 'Content-Length': str(len(body))}
464 if httpuser and httppasswd:
465 headers['Authorization'] = 'Basic %s' % base64.b64encode(':'.join([httpuser, httppasswd]))
467 return (headers, body)
469 ##############################################################################
471 def daemonize(pidfile=None, stdin='/dev/null', stdout='/dev/null', stderr='/dev/null'):
472 # 1st fork
473 try:
474 pid = os.fork()
475 if pid > 0:
476 sys.exit(0)
477 except OSError, e:
478 sys.stderr.write("1st fork failed: (%d) %s\n" % (e.errno, e.strerror))
479 sys.exit(1)
480 # Prepare 2nd fork
481 os.chdir("/")
482 os.umask(0)
483 os.setsid( )
484 # 2nd fork
485 try:
486 pid = os.fork()
487 if pid > 0:
488 sys.exit(0)
489 except OSError, e:
490 sys.stderr.write("2nd fork failed: (%d) %s\n" % (e.errno, e.strerror))
491 sys.exit(1)
493 # Try to write PID file
494 if pidfile:
495 pid = str(os.getpid())
496 try:
497 file(pidfile, 'w+').write('%s\n' % pid)
498 except IOError:
499 sys.stderr.write("Could not write PID file, exiting...\n")
500 sys.exit(1)
502 # Redirect stdin, stdout, stderr
503 sys.stdout.flush()
504 sys.stderr.flush()
505 si = file(stdin, 'r')
506 so = file(stdout, 'a+')
507 se = file(stderr, 'a+', 0)
508 os.dup2(si.fileno(), sys.stdin.fileno())
509 os.dup2(so.fileno(), sys.stdout.fileno())
510 os.dup2(se.fileno(), sys.stderr.fileno())
512 return
514 ##############################################################################
516 class MyHTTPServer(SocketServer.ForkingMixIn, BaseHTTPServer.HTTPServer):
517 def __init__(self, server_address, HandlerClass, ssl=False, sslpemfile=None):
518 if ssl:
519 # FIXME: SSL is in Py2.6
520 try:
521 from OpenSSL import SSL
522 except:
523 print 'No Python OpenSSL wrapper/bindings found!'
524 sys.exit(127)
526 SocketServer.BaseServer.__init__(self, server_address, HandlerClass)
527 context = SSL.Context(SSL.SSLv23_METHOD)
528 context.use_privatekey_file (sslpemfile)
529 context.use_certificate_file(sslpemfile)
530 self.socket = SSL.Connection(context, socket.socket(self.address_family, self.socket_type))
531 else:
532 SocketServer.BaseServer.__init__(self, server_address, HandlerClass)
533 self.socket = socket.socket(self.address_family, self.socket_type)
535 self.server_bind()
536 self.server_activate()
539 class MyHTTPRequestHandler(BaseHTTPServer.BaseHTTPRequestHandler):
540 def setup(self):
541 self.connection = self.request
542 self.rfile = socket._fileobject(self.request, "rb", self.rbufsize)
543 self.wfile = socket._fileobject(self.request, "wb", self.wbufsize)
545 ##############################################################################
547 def prepare_socket(socket_path):
548 try:
549 if socket_path.startswith('/'):
550 s_family=socket.AF_UNIX
551 s_sockaddr = socket_path
552 elif socket_path.startswith('unix:'):
553 s_family=socket.AF_UNIX
554 s_sockaddr = socket_path[5:]
555 elif socket_path.find(':') >= 0:
556 s_port = socket_path.split(':')[-1]
557 s_host = ':'.join(socket_path.split(':')[:-1])
558 if s_host.startswith('[') and s_host.endswith(']'):
559 s_host = s_host[1:-1]
560 (s_family, s_socktype, s_proto, s_canonname, s_sockaddr) = socket.getaddrinfo(s_host, s_port, 0, socket.SOCK_STREAM)[0]
561 else:
562 return None
563 except:
564 return None
566 return (s_family, s_sockaddr)
569 def read_socket(s_opts, commands):
570 # print '%20s => %s %s' % (sock, s_family, s_sockaddr)
572 s = socket.socket(s_opts[0], socket.SOCK_STREAM)
573 s.connect(s_opts[1])
574 for line in commands:
575 if not line.endswith('\n'):
576 line += '\n'
577 s.send(line)
578 s.shutdown(socket.SHUT_WR)
580 answer = ''
581 try:
582 while True:
583 s.settimeout(10)
584 data = s.recv(32768)
585 if data:
586 answer += data
587 else:
588 break
589 except socket.timeout:
590 return ''
592 return answer
595 def livestatus2dict(s_opts, host=None, service=None):
596 checks = []
598 # Get host information only if NO service specified
599 if not service:
600 commands = []
601 commands.append('GET hosts\n')
602 commands.append('Columns: name state plugin_output long_plugin_output last_check\n')
603 if host:
604 commands.append('Filter: name = %s' % host)
605 answer = read_socket(s_opts, commands)
607 for line in answer.split('\n')[:-1]:
608 line = line.split(';')
609 checks.append({'host_name':line[0], 'service_description':None, 'returncode':line[1], 'output':'\n'.join([line[2], line[3]]).rstrip(), 'timestamp':str(line[4])})
611 # Get service information(s)
612 commands = []
613 commands.append('GET services\n')
614 commands.append('Columns: host_name description state plugin_output long_plugin_output last_check\n')
615 if host:
616 commands.append('Filter: host_name = %s' % host)
617 if service:
618 commands.append('Filter: description = %s' % service)
620 answer = read_socket(s_opts, commands)
622 for line in answer.split('\n')[:-1]:
623 line = line.split(';')
624 checks.append({'host_name':line[0], 'service_description':line[1], 'returncode':line[2], 'output':'\n'.join([line[3], line[4]]).rstrip(), 'timestamp':str(line[5])})
627 return checks
628 ##############################################################################