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
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, 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 if timeout:
86 signal.signal(signal.SIGALRM, exec_timeout_handler)
87 signal.alarm(timeout)
89 try:
90 cmd = subprocess.Popen(cmdarray, stdout=subprocess.PIPE)
91 check['output'] = cmd.communicate()[0].rstrip()
92 check['returncode'] = cmd.returncode
93 except OSError:
94 check['output'] = 'Could not execute "%s"' % cmdline
95 check['returncode'] = 127
96 except ExecTimeoutError:
97 check['output'] = 'Plugin timed out after %s seconds' % timeout
98 check['returncode'] = timeout_returncode
100 if timeout:
101 signal.alarm(0)
102 try:
103 if sys.version_info >= (2, 6):
104 cmd.terminate()
105 else:
106 os.kill(cmd.pid, 15)
107 except OSError:
108 pass
110 check['timestamp'] = datetime.datetime.now().strftime('%s')
111 return check
114 ##############################################################################
116 def conf2dict(config, opt_host=None, opt_service=None):
117 checks = []
119 # Read "plugin_timeout" from "[nagixsc]", default "None" (no timeout)
120 try:
121 timeout = config.getint('nagixsc','plugin_timeout')
122 except (ConfigParser.NoSectionError, ConfigParser.NoOptionError):
123 timeout = None
125 # Read "plugin_timeout_returncode" from "[nagixsc]", default "2" (CRITICAL)
126 try:
127 timeout_returncode = config.getint('nagixsc','plugin_timeout_returncode')
128 except (ConfigParser.NoSectionError, ConfigParser.NoOptionError):
129 timeout_returncode = 2
131 # Sections are Hosts (not 'nagixsc'), options in sections are Services
132 hosts = config.sections()
133 if 'nagixsc' in hosts:
134 hosts.remove('nagixsc')
136 # Filter out host/section if it exists
137 if opt_host:
138 if opt_host in hosts:
139 hosts = [opt_host,]
140 else:
141 hosts = []
143 for host in hosts:
144 # Overwrite section/host name with '_host_name'
145 if config.has_option(host,'_host_name'):
146 host_name = config.get(host,'_host_name')
147 else:
148 host_name = host
151 services = config.options(host)
152 # Look for host check
153 if '_host_check' in services and not opt_service:
154 cmdline = config.get(host, '_host_check')
155 check = exec_check(host_name, None, cmdline, timeout, timeout_returncode)
156 checks.append(check)
159 # Filter out service if given in cmd line options
160 if opt_service:
161 if opt_service in services:
162 services = [opt_service,]
163 else:
164 services = []
166 for service in services:
167 # If option starts with '_' it may be a NagixSC option in the future
168 if service[0] != '_':
169 cmdline = config.get(host, service)
171 check = exec_check(host_name, service, cmdline, timeout, timeout_returncode)
172 checks.append(check)
174 return checks
177 ##############################################################################
179 def dict2out_passive(checks, xmltimestamp, opt_pipe, opt_verb=0):
180 FORMAT_HOST = '[%s] PROCESS_HOST_CHECK_RESULT;%s;%s;%s'
181 FORMAT_SERVICE = '[%s] PROCESS_SERVICE_CHECK_RESULT;%s;%s;%s;%s'
182 count_services = 0
183 now = datetime.datetime.now().strftime('%s')
185 # Prepare
186 if opt_verb <= 2:
187 pipe = open(opt_pipe, "w")
188 else:
189 pipe = None
191 # Output
192 for check in checks:
193 count_services += 1
194 if check.has_key('timestamp'):
195 timestamp = check['timestamp']
196 else:
197 timestamp = xmltimestamp
198 count_services += 1
200 if check['service_description'] == None or check['service_description'] == '':
201 # Host check
202 line = FORMAT_HOST % (now, check['host_name'], check['returncode'], check['output'].replace('\n', '\\n'))
203 else:
204 # Service check
205 line = FORMAT_SERVICE % (now, check['host_name'], check['service_description'], check['returncode'], check['output'].replace('\n', '\\n'))
207 if pipe:
208 pipe.write(line + '\n')
209 debug(2, opt_verb, line)
211 # Close
212 if pipe:
213 pipe.close()
214 else:
215 print "Passive check results NOT written to Nagios pipe due to -vvv!"
217 return count_services
220 def dict2out_checkresult(checks, xmltimestamp, opt_checkresultdir, opt_verb):
221 count_services = 0
222 count_failed = 0
223 list_failed = []
224 chars = string.letters + string.digits
225 ctimestamp = datetime.datetime.now().ctime()
227 for check in checks:
228 count_services += 1
229 if check.has_key('timestamp'):
230 timestamp = check['timestamp']
231 else:
232 timestamp = xmltimestamp
234 filename = os.path.join(opt_checkresultdir, 'c' + ''.join([random.choice(chars) for i in range(6)]))
235 try:
236 crfile = open(filename, "w")
237 if check['service_description'] == None or check['service_description'] == '':
238 # Host check
239 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') ) )
240 else:
241 # Service check
242 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') ) )
243 crfile.close()
245 # Create OK file
246 open(filename + '.ok', 'w').close()
247 except:
248 count_failed += 1
249 list_failed.append([filename, check['host_name'], check['service_description']])
251 return (count_services, count_failed, list_failed)
254 ##############################################################################
256 def read_xml(options):
257 if options.url != None:
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 def write_xml(xmldoc, outfile, httpuser=None, httppasswd=None):
289 if outfile.startswith('http'):
290 (headers, body) = encode_multipart(xmldoc, httpuser, httppasswd)
292 try:
293 response = urllib2.urlopen(urllib2.Request(outfile, body, headers)).read()
294 except urllib2.HTTPError, error:
295 print error
296 sys.exit(11)
297 except urllib2.URLError, error:
298 print error.reason[1]
299 sys.exit(12)
301 print response
303 elif outfile == '-':
304 xmldoc.saveFormatFile('-', format=1)
306 else:
307 xmldoc.saveFile(outfile)
310 ##############################################################################
312 def xml_check_version(xmldoc):
313 # FIXME: Check XML structure
314 try:
315 xmlnagixsc = xmldoc.xpathNewContext().xpathEval('/nagixsc')[0]
316 except:
317 return (False, 'Not a Nag(IX)SC XML file!')
319 try:
320 if xmlnagixsc.prop('version') != "1.0":
321 return (False, 'Wrong version (found "%s", need "1.0") of XML file!' % xmlnagixsc.prop('version'))
322 except:
323 return (False, 'No version information found in XML file!')
325 return (True, 'XML seems to be ok')
328 def xml_get_timestamp(xmldoc):
329 try:
330 timestamp = int(xmldoc.xpathNewContext().xpathEval('/nagixsc/timestamp')[0].get_content())
331 except:
332 return False
334 return timestamp
337 def xml_to_dict(xmldoc, verb=0, hostfilter=None, servicefilter=None):
338 checks = []
339 now = int(datetime.datetime.now().strftime('%s'))
340 filetimestamp = reset_future_timestamp(xml_get_timestamp(xmldoc), now)
342 if hostfilter:
343 hosts = xmldoc.xpathNewContext().xpathEval('/nagixsc/host[name="%s"] | /nagixsc/host[name="%s"]' % (hostfilter, encode(hostfilter)))
344 else:
345 hosts = xmldoc.xpathNewContext().xpathEval('/nagixsc/host')
347 for host in hosts:
348 xmlhostname = host.xpathEval('name')[0]
349 hostname = decode(xmlhostname.get_content(), xmlhostname.prop('encoding'))
350 debug(2, verb, 'Found host "%s"' % hostname)
352 # Look for Host check result
353 if host.xpathEval('returncode'):
354 retcode = host.xpathEval('returncode')[0].get_content()
355 else:
356 retcode = None
358 if host.xpathEval('output'):
359 xmloutput = host.xpathEval('output')[0]
360 output = decode(xmloutput.get_content(), xmloutput.prop('encoding')).rstrip()
361 else:
362 output = None
364 if host.xpathEval('timestamp'):
365 timestamp = reset_future_timestamp(int(host.xpathEval('timestamp')[0].get_content()), now)
366 else:
367 timestamp = filetimestamp
369 # Append only if no service filter
370 if not servicefilter and retcode and output:
371 checks.append({'host_name':hostname, 'service_description':None, 'returncode':retcode, 'output':output, 'timestamp':timestamp})
374 # Look for service filter
375 if servicefilter:
376 services = host.xpathEval('service[description="%s"] | service[description="%s"]' % (servicefilter, encode(servicefilter)))
377 else:
378 services = host.xpathEval('service')
380 # Loop over services in host
381 for service in services:
382 service_dict = {}
384 xmldescr = service.xpathEval('description')[0]
385 xmloutput = service.xpathEval('output')[0]
387 srvdescr = decode(xmldescr.get_content(), xmldescr.prop('encoding'))
388 retcode = service.xpathEval('returncode')[0].get_content()
389 output = decode(xmloutput.get_content(), xmloutput.prop('encoding')).rstrip()
391 try:
392 timestamp = reset_future_timestamp(int(service.xpathEval('timestamp')[0].get_content()), now)
393 except:
394 timestamp = filetimestamp
396 debug(2, verb, 'Found service "%s"' % srvdescr)
398 service_dict = {'host_name':hostname, 'service_description':srvdescr, 'returncode':retcode, 'output':output, 'timestamp':timestamp}
399 checks.append(service_dict)
401 debug(1, verb, 'Host: "%s" - Service: "%s" - RetCode: "%s" - Output: "%s"' % (hostname, srvdescr, retcode, output) )
403 return checks
406 def xml_from_dict(checks, encoding='base64'):
407 lasthost = None
409 db = [(check['host_name'], check) for check in checks]
410 db.sort()
412 xmldoc = libxml2.newDoc('1.0')
413 xmlroot = xmldoc.newChild(None, 'nagixsc', None)
414 xmlroot.setProp('version', '1.0')
415 xmltimestamp = xmlroot.newChild(None, 'timestamp', datetime.datetime.now().strftime('%s'))
417 for entry in db:
418 check = entry[1]
420 if check['host_name'] != lasthost:
421 xmlhost = xmlroot.newChild(None, 'host', None)
422 xmlhostname = xmlhost.newChild(None, 'name', encode(check['host_name'], encoding))
423 lasthost = check['host_name']
425 if check['service_description'] == '' or check['service_description'] == None:
426 # Host check result
427 xmlreturncode = xmlhost.newChild(None, 'returncode', str(check['returncode']))
428 xmloutput = xmlhost.newChild(None, 'output', encode(check['output'], encoding))
429 xmloutput.setProp('encoding', encoding)
430 if check.has_key('timestamp'):
431 xmltimestamp = xmlhost.newChild(None, 'timestamp', str(check['timestamp']))
432 else:
433 # Service check result
434 xmlservice = xmlhost.newChild(None, 'service', None)
435 xmlname = xmlservice.newChild(None, 'description', encode(check['service_description'], encoding))
436 xmlname.setProp('encoding', encoding)
437 xmlreturncode = xmlservice.newChild(None, 'returncode', str(check['returncode']))
438 xmloutput = xmlservice.newChild(None, 'output', encode(check['output'], encoding))
439 xmloutput.setProp('encoding', encoding)
440 if check.has_key('timestamp'):
441 xmltimestamp = xmlservice.newChild(None, 'timestamp', str(check['timestamp']))
443 return xmldoc
446 def xml_merge(xmldocs):
447 checks = []
448 for xmldoc in xmldocs:
449 checks.extend(xml_to_dict(xmldoc))
450 newxmldoc = xml_from_dict(checks)
451 return newxmldoc
454 def check_mark_outdated(check, now, maxtimediff, markold):
455 timedelta = now - check['timestamp']
456 if timedelta > maxtimediff:
457 check['output'] = 'Nag(ix)SC: Check result is %s(>%s) seconds old - %s' % (timedelta, maxtimediff, check['output'])
458 if markold:
459 check['returncode'] = 3
460 return check
463 def reset_future_timestamp(timestamp, now):
464 if timestamp <= now:
465 return timestamp
466 else:
467 return now
469 ##############################################################################
471 def encode_multipart(xmldoc, httpuser=None, httppasswd=None):
472 BOUNDARY = mimetools.choose_boundary()
473 CRLF = '\r\n'
474 L = []
475 L.append('--' + BOUNDARY)
476 L.append('Content-Disposition: form-data; name="xmlfile"; filename="xmlfile"')
477 L.append('Content-Type: application/xml')
478 L.append('')
479 L.append(xmldoc.serialize())
480 L.append('--' + BOUNDARY + '--')
481 L.append('')
482 body = CRLF.join(L)
483 content_type = 'multipart/form-data; boundary=%s' % BOUNDARY
484 headers = {'Content-Type': content_type, 'Content-Length': str(len(body))}
486 if httpuser and httppasswd:
487 headers['Authorization'] = 'Basic %s' % base64.b64encode(':'.join([httpuser, httppasswd]))
489 return (headers, body)
491 ##############################################################################
493 def daemonize(pidfile=None, stdin='/dev/null', stdout='/dev/null', stderr='/dev/null'):
494 # 1st fork
495 try:
496 pid = os.fork()
497 if pid > 0:
498 sys.exit(0)
499 except OSError, e:
500 sys.stderr.write("1st fork failed: (%d) %s\n" % (e.errno, e.strerror))
501 sys.exit(1)
502 # Prepare 2nd fork
503 os.chdir("/")
504 os.umask(0)
505 os.setsid( )
506 # 2nd fork
507 try:
508 pid = os.fork()
509 if pid > 0:
510 sys.exit(0)
511 except OSError, e:
512 sys.stderr.write("2nd fork failed: (%d) %s\n" % (e.errno, e.strerror))
513 sys.exit(1)
515 # Try to write PID file
516 if pidfile:
517 pid = str(os.getpid())
518 try:
519 file(pidfile, 'w+').write('%s\n' % pid)
520 except IOError:
521 sys.stderr.write("Could not write PID file, exiting...\n")
522 sys.exit(1)
524 # Redirect stdin, stdout, stderr
525 sys.stdout.flush()
526 sys.stderr.flush()
527 si = file(stdin, 'r')
528 so = file(stdout, 'a+')
529 se = file(stderr, 'a+', 0)
530 os.dup2(si.fileno(), sys.stdin.fileno())
531 os.dup2(so.fileno(), sys.stdout.fileno())
532 os.dup2(se.fileno(), sys.stderr.fileno())
534 return
536 ##############################################################################
538 class MyHTTPServer(SocketServer.ForkingMixIn, BaseHTTPServer.HTTPServer):
539 def __init__(self, server_address, HandlerClass, ssl=False, sslpemfile=None):
540 if ssl:
541 # FIXME: SSL is in Py2.6
542 try:
543 from OpenSSL import SSL
544 except:
545 print 'No Python OpenSSL wrapper/bindings found!'
546 sys.exit(127)
548 SocketServer.BaseServer.__init__(self, server_address, HandlerClass)
549 context = SSL.Context(SSL.SSLv23_METHOD)
550 context.use_privatekey_file (sslpemfile)
551 context.use_certificate_file(sslpemfile)
552 self.socket = SSL.Connection(context, socket.socket(self.address_family, self.socket_type))
553 else:
554 SocketServer.BaseServer.__init__(self, server_address, HandlerClass)
555 self.socket = socket.socket(self.address_family, self.socket_type)
557 self.server_bind()
558 self.server_activate()
561 class MyHTTPRequestHandler(BaseHTTPServer.BaseHTTPRequestHandler):
562 def setup(self):
563 self.connection = self.request
564 self.rfile = socket._fileobject(self.request, "rb", self.rbufsize)
565 self.wfile = socket._fileobject(self.request, "wb", self.wbufsize)
567 ##############################################################################
569 def prepare_socket(socket_path):
570 try:
571 if socket_path.startswith('/'):
572 s_family=socket.AF_UNIX
573 s_sockaddr = socket_path
574 elif socket_path.startswith('unix:'):
575 s_family=socket.AF_UNIX
576 s_sockaddr = socket_path[5:]
577 elif socket_path.find(':') >= 0:
578 s_port = socket_path.split(':')[-1]
579 s_host = ':'.join(socket_path.split(':')[:-1])
580 if s_host.startswith('[') and s_host.endswith(']'):
581 s_host = s_host[1:-1]
582 (s_family, s_socktype, s_proto, s_canonname, s_sockaddr) = socket.getaddrinfo(s_host, s_port, 0, socket.SOCK_STREAM)[0]
583 else:
584 return None
585 except:
586 return None
588 return (s_family, s_sockaddr)
591 def read_socket(s_opts, commands):
592 # print '%20s => %s %s' % (sock, s_family, s_sockaddr)
594 s = socket.socket(s_opts[0], socket.SOCK_STREAM)
595 s.connect(s_opts[1])
596 for line in commands:
597 if not line.endswith('\n'):
598 line += '\n'
599 s.send(line)
600 s.shutdown(socket.SHUT_WR)
602 answer = ''
603 try:
604 while True:
605 s.settimeout(10)
606 data = s.recv(32768)
607 if data:
608 answer += data
609 else:
610 break
611 except socket.timeout:
612 return ''
614 return answer
617 def livestatus2dict(s_opts, host=None, service=None):
618 checks = []
620 # Get host information only if NO service specified
621 if not service:
622 commands = []
623 commands.append('GET hosts\n')
624 commands.append('Columns: name state plugin_output long_plugin_output last_check\n')
625 if host:
626 commands.append('Filter: name = %s' % host)
627 answer = read_socket(s_opts, commands)
629 for line in answer.split('\n')[:-1]:
630 line = line.split(';')
631 checks.append({'host_name':line[0], 'service_description':None, 'returncode':line[1], 'output':'\n'.join([line[2], line[3]]).rstrip(), 'timestamp':str(line[4])})
633 # Get service information(s)
634 commands = []
635 commands.append('GET services\n')
636 commands.append('Columns: host_name description state plugin_output long_plugin_output last_check\n')
637 if host:
638 commands.append('Filter: host_name = %s' % host)
639 if service:
640 commands.append('Filter: description = %s' % service)
642 answer = read_socket(s_opts, commands)
644 for line in answer.split('\n')[:-1]:
645 line = line.split(';')
646 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])})
649 return checks
650 ##############################################################################