1 import BaseHTTPServer
2 import ConfigParser
3 import SocketServer
4 import base64
5 import libxml2
6 import mimetools
7 import os
8 import random
9 import shlex
10 import signal
11 import socket
12 import string
13 import subprocess
14 import sys
15 import time
16 import urllib2
18 def debug(level, verb, string):
19 if level <= verb:
20 print "%s: %s" % (level, string)
23 ##############################################################################
25 class ExecTimeoutError(Exception):
26 pass
28 ##############################################################################
30 def available_encodings():
31 return ['base64', 'plain',]
34 def check_encoding(enc):
35 if enc in available_encodings():
36 return True
37 else:
38 return False
41 def decode(data, encoding):
42 if encoding == 'plain':
43 return data
44 else:
45 return base64.b64decode(data)
48 def encode(data, encoding=None):
49 if encoding == 'plain':
50 return data
51 else:
52 return base64.b64encode(data)
55 ##############################################################################
57 def read_inifile(inifile):
58 config = ConfigParser.RawConfigParser()
59 config.optionxform = str # We need case-sensitive options
60 ini_list = config.read(inifile)
62 if ini_list:
63 return config
64 else:
65 return False
68 ##############################################################################
70 def exec_timeout_handler(signum, frame):
71 raise ExecTimeoutError
73 def exec_check(host_name, service_descr, cmdline, cmdprefix='', timeout=None, timeout_returncode=2):
74 cmdarray = shlex.split(cmdline)
76 check = {}
77 check['host_name'] = host_name
78 check['service_description'] = service_descr
80 if len(cmdarray) == 0:
81 check['output'] = 'No command line specified!'
82 check['returncode'] = 127
83 return check
85 check['commandline'] = cmdline
86 check['command'] = cmdarray[0].split(os.path.sep)[-1]
88 if cmdprefix:
89 check['fullcommandline'] = cmdprefix + ' ' + cmdline
90 cmdarray = shlex.split(cmdprefix) + cmdarray
91 else:
92 check['fullcommandline'] = cmdline
94 if timeout:
95 signal.signal(signal.SIGALRM, exec_timeout_handler)
96 signal.alarm(timeout)
98 try:
99 cmd = subprocess.Popen(cmdarray, stdout=subprocess.PIPE)
100 check['output'] = cmd.communicate()[0].rstrip()
101 check['returncode'] = cmd.returncode
102 except OSError:
103 check['output'] = 'Could not execute "%s"' % cmdline
104 check['returncode'] = 127
105 except ExecTimeoutError:
106 check['output'] = 'Plugin timed out after %s seconds' % timeout
107 check['returncode'] = timeout_returncode
109 if timeout:
110 signal.alarm(0)
111 try:
112 if sys.version_info >= (2, 6):
113 cmd.terminate()
114 else:
115 os.kill(cmd.pid, 15)
116 except OSError:
117 pass
119 check['timestamp'] = str(long(time.time()))
120 return check
123 ##############################################################################
125 def conf2dict(config, opt_host=None, opt_service=None):
126 checks = []
128 # Read "plugin_timeout" from "[nagixsc]", default "None" (no timeout)
129 try:
130 timeout = config.getint('nagixsc','plugin_timeout')
131 except (ConfigParser.NoSectionError, ConfigParser.NoOptionError):
132 timeout = None
134 # Read "plugin_timeout_returncode" from "[nagixsc]", default "2" (CRITICAL)
135 try:
136 timeout_returncode = config.getint('nagixsc','plugin_timeout_returncode')
137 except (ConfigParser.NoSectionError, ConfigParser.NoOptionError):
138 timeout_returncode = 2
140 # Read "add_pnp4nagios_template_hint" from "[nagixsc]", default "False"
141 try:
142 add_pnp4nagios_template_hint = config.getboolean('nagixsc','add_pnp4nagios_template_hint')
143 except (ConfigParser.NoSectionError, ConfigParser.NoOptionError):
144 add_pnp4nagios_template_hint = False
146 # Read "command_prefix" from "[nagixsc]", default "" (empty string)
147 try:
148 cmdprefix_conffile = config.get('nagixsc','command_prefix')
149 except (ConfigParser.NoSectionError, ConfigParser.NoOptionError):
150 cmdprefix_conffile = ''
152 # Sections are Hosts (not 'nagixsc'), options in sections are Services
153 hosts = config.sections()
154 if 'nagixsc' in hosts:
155 hosts.remove('nagixsc')
157 # Filter out host/section if it exists
158 if opt_host:
159 if opt_host in hosts:
160 hosts = [opt_host,]
161 else:
162 hosts = []
164 for host in hosts:
165 # Overwrite section/host name with '_host_name'
166 if config.has_option(host,'_host_name'):
167 host_name = config.get(host,'_host_name')
168 else:
169 host_name = host
172 services = config.options(host)
173 # Look for host/section specific "command_prefix"
174 if '_command_prefix' in services:
175 cmdprefix = config.get(host, '_command_prefix')
176 else:
177 cmdprefix = cmdprefix_conffile
179 # Look for host check
180 if '_host_check' in services and not opt_service:
181 cmdline = config.get(host, '_host_check')
182 check = exec_check(host_name, None, cmdline, cmdprefix, timeout, timeout_returncode)
183 if add_pnp4nagios_template_hint and '|' in check['output']:
184 check['output'] += ' [%s]' % check['command']
185 checks.append(check)
188 # Filter out service if given in cmd line options
189 if opt_service:
190 if opt_service in services:
191 services = [opt_service,]
192 else:
193 services = []
195 for service in services:
196 # If option starts with '_' it may be a NagixSC option in the future
197 if service[0] != '_':
198 cmdline = config.get(host, service)
200 check = exec_check(host_name, service, cmdline, cmdprefix, timeout, timeout_returncode)
201 if add_pnp4nagios_template_hint and '|' in check['output']:
202 check['output'] += ' [%s]' % check['command']
203 checks.append(check)
205 return checks
208 ##############################################################################
210 def dict2out_passive(checks, xmltimestamp, opt_pipe, opt_verb=0):
211 FORMAT_HOST = '[%s] PROCESS_HOST_CHECK_RESULT;%s;%s;%s'
212 FORMAT_SERVICE = '[%s] PROCESS_SERVICE_CHECK_RESULT;%s;%s;%s;%s'
213 count_services = 0
214 now = str(long(time.time()))
216 # Prepare
217 if opt_verb <= 2:
218 pipe = open(opt_pipe, "w")
219 else:
220 pipe = None
222 # Output
223 for check in checks:
224 count_services += 1
225 if check.has_key('timestamp'):
226 timestamp = check['timestamp']
227 else:
228 timestamp = xmltimestamp
230 if check['service_description'] == None or check['service_description'] == '':
231 # Host check
232 line = FORMAT_HOST % (timestamp, check['host_name'], check['returncode'], check['output'].replace('\n', '\\n'))
233 else:
234 # Service check
235 line = FORMAT_SERVICE % (timestamp, check['host_name'], check['service_description'], check['returncode'], check['output'].replace('\n', '\\n'))
237 if pipe:
238 pipe.write(line + '\n')
239 debug(2, opt_verb, line)
241 # Close
242 if pipe:
243 pipe.close()
244 else:
245 print "Passive check results NOT written to Nagios pipe due to -vvv!"
247 return count_services
250 def dict2out_checkresult(checks, xmltimestamp, opt_checkresultdir, opt_verb=0):
251 count_services = 0
252 count_failed = 0
253 list_failed = []
254 chars = string.letters + string.digits
255 ctimestamp = time.ctime()
256 random.seed()
258 for check in checks:
259 count_services += 1
260 if check.has_key('timestamp'):
261 timestamp = check['timestamp']
262 else:
263 timestamp = xmltimestamp
265 filename = os.path.join(opt_checkresultdir, 'c' + ''.join([random.choice(chars) for i in range(6)]))
266 try:
267 crfile = open(filename, "w")
268 if check['service_description'] == None or check['service_description'] == '':
269 # Host check
270 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') ) )
271 else:
272 # Service check
273 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') ) )
274 crfile.close()
276 # Create OK file
277 open(filename + '.ok', 'w').close()
278 except:
279 count_failed += 1
280 list_failed.append([filename, check['host_name'], check['service_description']])
282 return (count_services, count_failed, list_failed)
285 ##############################################################################
287 def read_xml(options):
288 if options.url != None:
290 if options.httpuser and options.httppasswd:
291 passman = urllib2.HTTPPasswordMgrWithDefaultRealm()
292 passman.add_password(None, options.url, options.httpuser, options.httppasswd)
293 authhandler = urllib2.HTTPBasicAuthHandler(passman)
294 opener = urllib2.build_opener(authhandler)
295 urllib2.install_opener(opener)
297 try:
298 response = urllib2.urlopen(options.url)
299 except urllib2.HTTPError, error:
300 print error
301 sys.exit(0)
302 except urllib2.URLError, error:
303 print error.reason[1]
304 sys.exit(0)
306 doc = libxml2.parseDoc(response.read())
307 response.close()
309 else:
310 doc = libxml2.parseFile(options.file)
312 return doc
315 def read_xml_from_string(content):
316 return libxml2.parseDoc(content)
319 def write_xml(xmldoc, outfile, httpuser=None, httppasswd=None):
320 if outfile.startswith('http'):
321 (headers, body) = encode_multipart(xmldoc, httpuser, httppasswd)
322 response = urllib2.urlopen(urllib2.Request(outfile, body, headers)).read()
323 return response
325 elif outfile == '-':
326 xmldoc.saveFormatFile('-', format=1)
327 return None
329 else:
330 xmldoc.saveFile(outfile)
331 return None
334 def write_xml_or_die(xmldoc, outfile, httpuser=None, httppasswd=None):
335 try:
336 response = write_xml(xmldoc, outfile, httpuser, httppasswd)
337 except urllib2.HTTPError, error:
338 print error
339 sys.exit(11)
340 except urllib2.URLError, error:
341 print error.reason[1]
342 sys.exit(12)
344 return response
347 ##############################################################################
349 def xml_check_version(xmldoc):
350 # FIXME: Check XML structure
351 try:
352 xmlnagixsc = xmldoc.xpathNewContext().xpathEval('/nagixsc')[0]
353 except:
354 return (False, 'Not a Nag(IX)SC XML file!')
356 try:
357 if xmlnagixsc.prop('version') != "1.0":
358 return (False, 'Wrong version (found "%s", need "1.0") of XML file!' % xmlnagixsc.prop('version'))
359 except:
360 return (False, 'No version information found in XML file!')
362 return (True, 'XML seems to be ok')
365 def xml_get_timestamp(xmldoc):
366 try:
367 timestamp = int(xmldoc.xpathNewContext().xpathEval('/nagixsc/timestamp')[0].get_content())
368 except:
369 return False
371 return timestamp
374 def xml_to_dict(xmldoc, verb=0, hostfilter=None, servicefilter=None):
375 checks = []
376 now = long(time.time())
377 filetimestamp = reset_future_timestamp(xml_get_timestamp(xmldoc), now)
379 if hostfilter:
380 hosts = xmldoc.xpathNewContext().xpathEval('/nagixsc/host[name="%s"] | /nagixsc/host[name="%s"]' % (hostfilter, encode(hostfilter)))
381 else:
382 hosts = xmldoc.xpathNewContext().xpathEval('/nagixsc/host')
384 for host in hosts:
385 xmlhostname = host.xpathEval('name')[0]
386 hostname = decode(xmlhostname.get_content(), xmlhostname.prop('encoding'))
387 debug(2, verb, 'Found host "%s"' % hostname)
389 # Look for Host check result
390 if host.xpathEval('returncode'):
391 retcode = host.xpathEval('returncode')[0].get_content()
392 else:
393 retcode = None
395 if host.xpathEval('output'):
396 xmloutput = host.xpathEval('output')[0]
397 output = decode(xmloutput.get_content(), xmloutput.prop('encoding')).rstrip()
398 else:
399 output = None
401 if host.xpathEval('timestamp'):
402 timestamp = reset_future_timestamp(int(host.xpathEval('timestamp')[0].get_content()), now)
403 else:
404 timestamp = filetimestamp
406 # Append only if no service filter
407 if not servicefilter and retcode and output:
408 checks.append({'host_name':hostname, 'service_description':None, 'returncode':retcode, 'output':output, 'timestamp':timestamp})
411 # Look for service filter
412 if servicefilter:
413 services = host.xpathEval('service[description="%s"] | service[description="%s"]' % (servicefilter, encode(servicefilter)))
414 else:
415 services = host.xpathEval('service')
417 # Loop over services in host
418 for service in services:
419 service_dict = {}
421 xmldescr = service.xpathEval('description')[0]
422 xmloutput = service.xpathEval('output')[0]
424 srvdescr = decode(xmldescr.get_content(), xmldescr.prop('encoding'))
425 retcode = service.xpathEval('returncode')[0].get_content()
426 output = decode(xmloutput.get_content(), xmloutput.prop('encoding')).rstrip()
428 try:
429 timestamp = reset_future_timestamp(int(service.xpathEval('timestamp')[0].get_content()), now)
430 except:
431 timestamp = filetimestamp
433 debug(2, verb, 'Found service "%s"' % srvdescr)
435 service_dict = {'host_name':hostname, 'service_description':srvdescr, 'returncode':retcode, 'output':output, 'timestamp':timestamp}
436 checks.append(service_dict)
438 debug(1, verb, 'Host: "%s" - Service: "%s" - RetCode: "%s" - Output: "%s"' % (hostname, srvdescr, retcode, output) )
440 return checks
443 def xml_from_dict(checks, encoding='base64'):
444 lasthost = None
446 db = [(check['host_name'], check) for check in checks]
447 db.sort()
449 xmldoc = libxml2.newDoc('1.0')
450 xmlroot = xmldoc.newChild(None, 'nagixsc', None)
451 xmlroot.setProp('version', '1.0')
452 xmltimestamp = xmlroot.newChild(None, 'timestamp', str(long(time.time())))
454 for entry in db:
455 check = entry[1]
457 if check['host_name'] != lasthost:
458 xmlhost = xmlroot.newChild(None, 'host', None)
459 xmlhostname = xmlhost.newChild(None, 'name', encode(check['host_name'], encoding))
460 lasthost = check['host_name']
462 if check['service_description'] == '' or check['service_description'] == None:
463 # Host check result
464 xmlreturncode = xmlhost.newChild(None, 'returncode', str(check['returncode']))
465 xmloutput = xmlhost.newChild(None, 'output', encode(check['output'], encoding))
466 xmloutput.setProp('encoding', encoding)
467 if check.has_key('timestamp'):
468 xmltimestamp = xmlhost.newChild(None, 'timestamp', str(check['timestamp']))
469 else:
470 # Service check result
471 xmlservice = xmlhost.newChild(None, 'service', None)
472 xmlname = xmlservice.newChild(None, 'description', encode(check['service_description'], encoding))
473 xmlname.setProp('encoding', encoding)
474 xmlreturncode = xmlservice.newChild(None, 'returncode', str(check['returncode']))
475 xmloutput = xmlservice.newChild(None, 'output', encode(check['output'], encoding))
476 xmloutput.setProp('encoding', encoding)
477 if check.has_key('timestamp'):
478 xmltimestamp = xmlservice.newChild(None, 'timestamp', str(check['timestamp']))
480 return xmldoc
483 def xml_merge(xmldocs):
484 checks = []
485 for xmldoc in xmldocs:
486 checks.extend(xml_to_dict(xmldoc))
487 newxmldoc = xml_from_dict(checks)
488 return newxmldoc
491 def check_mark_outdated(check, now, maxtimediff, markold):
492 timedelta = now - check['timestamp']
493 if timedelta > maxtimediff:
494 check['output'] = 'Nag(ix)SC: Check result is %s(>%s) seconds old - %s' % (timedelta, maxtimediff, check['output'])
495 if markold:
496 check['returncode'] = 3
497 return check
500 def reset_future_timestamp(timestamp, now):
501 if timestamp <= now:
502 return timestamp
503 else:
504 return now
506 ##############################################################################
508 def encode_multipart(xmldoc, httpuser=None, httppasswd=None):
509 BOUNDARY = mimetools.choose_boundary()
510 CRLF = '\r\n'
511 L = []
512 L.append('--' + BOUNDARY)
513 L.append('Content-Disposition: form-data; name="xmlfile"; filename="xmlfile"')
514 L.append('Content-Type: application/xml')
515 L.append('')
516 L.append(xmldoc.serialize())
517 L.append('--' + BOUNDARY + '--')
518 L.append('')
519 body = CRLF.join(L)
520 content_type = 'multipart/form-data; boundary=%s' % BOUNDARY
521 headers = {'Content-Type': content_type, 'Content-Length': str(len(body))}
523 if httpuser and httppasswd:
524 headers['Authorization'] = 'Basic %s' % base64.b64encode(':'.join([httpuser, httppasswd]))
526 return (headers, body)
528 ##############################################################################
530 def daemonize(pidfile=None, stdin='/dev/null', stdout='/dev/null', stderr='/dev/null'):
531 # 1st fork
532 try:
533 pid = os.fork()
534 if pid > 0:
535 sys.exit(0)
536 except OSError, e:
537 sys.stderr.write("1st fork failed: (%d) %s\n" % (e.errno, e.strerror))
538 sys.exit(1)
539 # Prepare 2nd fork
540 os.chdir("/")
541 os.umask(0)
542 os.setsid( )
543 # 2nd fork
544 try:
545 pid = os.fork()
546 if pid > 0:
547 sys.exit(0)
548 except OSError, e:
549 sys.stderr.write("2nd fork failed: (%d) %s\n" % (e.errno, e.strerror))
550 sys.exit(1)
552 # Try to write PID file
553 if pidfile:
554 pid = str(os.getpid())
555 try:
556 file(pidfile, 'w+').write('%s\n' % pid)
557 except IOError:
558 sys.stderr.write("Could not write PID file, exiting...\n")
559 sys.exit(1)
561 # Redirect stdin, stdout, stderr
562 sys.stdout.flush()
563 sys.stderr.flush()
564 si = file(stdin, 'r')
565 so = file(stdout, 'a+')
566 se = file(stderr, 'a+', 0)
567 os.dup2(si.fileno(), sys.stdin.fileno())
568 os.dup2(so.fileno(), sys.stdout.fileno())
569 os.dup2(se.fileno(), sys.stderr.fileno())
571 return
573 ##############################################################################
575 if 'ForkingMixIn' in SocketServer.__dict__:
576 MixInClass = SocketServer.ForkingMixIn
577 else:
578 MixInClass = SocketServer.ThreadingMixIn
580 class MyHTTPServer(MixInClass, BaseHTTPServer.HTTPServer):
581 def __init__(self, server_address, HandlerClass, ssl=False, sslpemfile=None):
582 SocketServer.BaseServer.__init__(self, server_address, HandlerClass)
584 if ssl:
585 try:
586 import ssl
587 self.socket = ssl.wrap_socket(socket.socket(self.address_family, self.socket_type), keyfile=sslpemfile, certfile=sslpemfile)
589 except:
591 try:
592 from OpenSSL import SSL
593 except:
594 print 'No Python SSL or OpenSSL wrapper/bindings found!'
595 sys.exit(127)
597 context = SSL.Context(SSL.SSLv23_METHOD)
598 context.use_privatekey_file (sslpemfile)
599 context.use_certificate_file(sslpemfile)
600 self.socket = SSL.Connection(context, socket.socket(self.address_family, self.socket_type))
602 else:
603 self.socket = socket.socket(self.address_family, self.socket_type)
605 self.server_bind()
606 self.server_activate()
609 class MyHTTPRequestHandler(BaseHTTPServer.BaseHTTPRequestHandler):
610 def setup(self):
611 self.connection = self.request
612 self.rfile = socket._fileobject(self.request, "rb", self.rbufsize)
613 self.wfile = socket._fileobject(self.request, "wb", self.wbufsize)
615 ##############################################################################
617 def prepare_socket(socket_path):
618 try:
619 if socket_path.startswith('/'):
620 s_family=socket.AF_UNIX
621 s_sockaddr = socket_path
622 elif socket_path.startswith('unix:'):
623 s_family=socket.AF_UNIX
624 s_sockaddr = socket_path[5:]
625 elif socket_path.find(':') >= 0:
626 s_port = socket_path.split(':')[-1]
627 s_host = ':'.join(socket_path.split(':')[:-1])
628 if s_host.startswith('[') and s_host.endswith(']'):
629 s_host = s_host[1:-1]
630 (s_family, s_socktype, s_proto, s_canonname, s_sockaddr) = socket.getaddrinfo(s_host, s_port, 0, socket.SOCK_STREAM)[0]
631 else:
632 return None
633 except:
634 return None
636 return (s_family, s_sockaddr)
639 def read_socket(s_opts, commands):
640 # print '%20s => %s %s' % (sock, s_family, s_sockaddr)
642 s = socket.socket(s_opts[0], socket.SOCK_STREAM)
643 s.connect(s_opts[1])
644 for line in commands:
645 if not line.endswith('\n'):
646 line += '\n'
647 s.send(line)
648 s.shutdown(socket.SHUT_WR)
650 answer = ''
651 try:
652 while True:
653 s.settimeout(10)
654 data = s.recv(32768)
655 if data:
656 answer += data
657 else:
658 break
659 except socket.timeout:
660 return ''
662 return answer
665 def livestatus2dict(s_opts, host=None, service=None):
666 checks = []
668 # Get host information only if NO service specified
669 if not service:
670 commands = []
671 commands.append('GET hosts\n')
672 commands.append('Columns: name state plugin_output long_plugin_output last_check\n')
673 if host:
674 commands.append('Filter: name = %s' % host)
675 answer = read_socket(s_opts, commands)
677 for line in answer.split('\n')[:-1]:
678 line = line.split(';')
679 checks.append({'host_name':line[0], 'service_description':None, 'returncode':line[1], 'output':'\n'.join([line[2], line[3]]).rstrip(), 'timestamp':str(line[4])})
681 # Get service information(s)
682 commands = []
683 commands.append('GET services\n')
684 commands.append('Columns: host_name description state plugin_output long_plugin_output last_check\n')
685 if host:
686 commands.append('Filter: host_name = %s' % host)
687 if service:
688 commands.append('Filter: description = %s' % service)
690 answer = read_socket(s_opts, commands)
692 for line in answer.split('\n')[:-1]:
693 line = line.split(';')
694 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])})
697 return checks
698 ##############################################################################