644c9ab624b78cc35b748f5a46637cd3d67e5e31
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 cmd.terminate()
103 except OSError:
104 pass
106 check['timestamp'] = datetime.datetime.now().strftime('%s')
107 return check
110 ##############################################################################
112 def conf2dict(config, opt_host=None, opt_service=None):
113 checks = []
115 # Read "plugin_timeout" from "[nagixsc]", default "None" (no timeout)
116 try:
117 timeout = config.getint('nagixsc','plugin_timeout')
118 except (ConfigParser.NoSectionError, ConfigParser.NoOptionError):
119 timeout = None
121 # Read "plugin_timeout_returncode" from "[nagixsc]", default "2" (CRITICAL)
122 try:
123 timeout_returncode = config.getint('nagixsc','plugin_timeout_returncode')
124 except (ConfigParser.NoSectionError, ConfigParser.NoOptionError):
125 timeout_returncode = 2
127 # Sections are Hosts (not 'nagixsc'), options in sections are Services
128 hosts = config.sections()
129 if 'nagixsc' in hosts:
130 hosts.remove('nagixsc')
132 # Filter out host/section if it exists
133 if opt_host:
134 if opt_host in hosts:
135 hosts = [opt_host,]
136 else:
137 hosts = []
139 for host in hosts:
140 # Overwrite section/host name with '_host_name'
141 if config.has_option(host,'_host_name'):
142 host_name = config.get(host,'_host_name')
143 else:
144 host_name = host
147 services = config.options(host)
148 # Look for host check
149 if '_host_check' in services and not opt_service:
150 cmdline = config.get(host, '_host_check')
151 check = exec_check(host_name, None, cmdline, timeout, timeout_returncode)
152 checks.append(check)
155 # Filter out service if given in cmd line options
156 if opt_service:
157 if opt_service in services:
158 services = [opt_service,]
159 else:
160 services = []
162 for service in services:
163 # If option starts with '_' it may be a NagixSC option in the future
164 if service[0] != '_':
165 cmdline = config.get(host, service)
167 check = exec_check(host_name, service, cmdline, timeout, timeout_returncode)
168 checks.append(check)
170 return checks
173 ##############################################################################
175 def dict2out_passive(checks, xmltimestamp, opt_pipe, opt_verb=0):
176 FORMAT_HOST = '[%s] PROCESS_HOST_CHECK_RESULT;%s;%s;%s'
177 FORMAT_SERVICE = '[%s] PROCESS_SERVICE_CHECK_RESULT;%s;%s;%s;%s'
178 count_services = 0
179 now = datetime.datetime.now().strftime('%s')
181 # Prepare
182 if opt_verb <= 2:
183 pipe = open(opt_pipe, "w")
184 else:
185 pipe = None
187 # Output
188 for check in checks:
189 count_services += 1
190 if check.has_key('timestamp'):
191 timestamp = check['timestamp']
192 else:
193 timestamp = xmltimestamp
194 count_services += 1
196 if check['service_description'] == None or check['service_description'] == '':
197 # Host check
198 line = FORMAT_HOST % (now, check['host_name'], check['returncode'], check['output'].replace('\n', '\\n'))
199 else:
200 # Service check
201 line = FORMAT_SERVICE % (now, check['host_name'], check['service_description'], check['returncode'], check['output'].replace('\n', '\\n'))
203 if pipe:
204 pipe.write(line + '\n')
205 debug(2, opt_verb, line)
207 # Close
208 if pipe:
209 pipe.close()
210 else:
211 print "Passive check results NOT written to Nagios pipe due to -vvv!"
213 return count_services
216 def dict2out_checkresult(checks, xmltimestamp, opt_checkresultdir, opt_verb):
217 count_services = 0
218 count_failed = 0
219 list_failed = []
220 chars = string.letters + string.digits
221 ctimestamp = datetime.datetime.now().ctime()
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 filename = os.path.join(opt_checkresultdir, 'c' + ''.join([random.choice(chars) for i in range(6)]))
231 try:
232 crfile = open(filename, "w")
233 if check['service_description'] == None or check['service_description'] == '':
234 # Host check
235 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') ) )
236 else:
237 # Service check
238 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') ) )
239 crfile.close()
241 # Create OK file
242 open(filename + '.ok', 'w').close()
243 except:
244 count_failed += 1
245 list_failed.append([filename, check['host_name'], check['service_description']])
247 return (count_services, count_failed, list_failed)
250 ##############################################################################
252 def read_xml(options):
253 if options.url != None:
254 import urllib2
256 if options.httpuser and options.httppasswd:
257 passman = urllib2.HTTPPasswordMgrWithDefaultRealm()
258 passman.add_password(None, options.url, options.httpuser, options.httppasswd)
259 authhandler = urllib2.HTTPBasicAuthHandler(passman)
260 opener = urllib2.build_opener(authhandler)
261 urllib2.install_opener(opener)
263 try:
264 response = urllib2.urlopen(options.url)
265 except urllib2.HTTPError, error:
266 print error
267 sys.exit(0)
268 except urllib2.URLError, error:
269 print error.reason[1]
270 sys.exit(0)
272 doc = libxml2.parseDoc(response.read())
273 response.close()
275 else:
276 doc = libxml2.parseFile(options.file)
278 return doc
281 def read_xml_from_string(content):
282 return libxml2.parseDoc(content)
285 ##############################################################################
287 def xml_check_version(xmldoc):
288 # FIXME: Check XML structure
289 try:
290 xmlnagixsc = xmldoc.xpathNewContext().xpathEval('/nagixsc')[0]
291 except:
292 return (False, 'Not a Nag(IX)SC XML file!')
294 try:
295 if xmlnagixsc.prop('version') != "1.0":
296 return (False, 'Wrong version (found "%s", need "1.0") of XML file!' % xmlnagixsc.prop('version'))
297 except:
298 return (False, 'No version information found in XML file!')
300 return (True, 'XML seems to be ok')
303 def xml_get_timestamp(xmldoc):
304 try:
305 timestamp = int(xmldoc.xpathNewContext().xpathEval('/nagixsc/timestamp')[0].get_content())
306 except:
307 return False
309 return timestamp
312 def xml_to_dict(xmldoc, verb=0, hostfilter=None, servicefilter=None):
313 checks = []
314 now = int(datetime.datetime.now().strftime('%s'))
315 filetimestamp = reset_future_timestamp(xml_get_timestamp(xmldoc), now)
317 if hostfilter:
318 hosts = xmldoc.xpathNewContext().xpathEval('/nagixsc/host[name="%s"] | /nagixsc/host[name="%s"]' % (hostfilter, encode(hostfilter)))
319 else:
320 hosts = xmldoc.xpathNewContext().xpathEval('/nagixsc/host')
322 for host in hosts:
323 xmlhostname = host.xpathEval('name')[0]
324 hostname = decode(xmlhostname.get_content(), xmlhostname.prop('encoding'))
325 debug(2, verb, 'Found host "%s"' % hostname)
327 # Look for Host check result
328 if host.xpathEval('returncode'):
329 retcode = host.xpathEval('returncode')[0].get_content()
330 else:
331 retcode = None
333 if host.xpathEval('output'):
334 xmloutput = host.xpathEval('output')[0]
335 output = decode(xmloutput.get_content(), xmloutput.prop('encoding')).rstrip()
336 else:
337 output = None
339 if host.xpathEval('timestamp'):
340 timestamp = reset_future_timestamp(int(host.xpathEval('timestamp')[0].get_content()), now)
341 else:
342 timestamp = filetimestamp
344 # Append only if no service filter
345 if not servicefilter and retcode and output:
346 checks.append({'host_name':hostname, 'service_description':None, 'returncode':retcode, 'output':output, 'timestamp':timestamp})
349 # Look for service filter
350 if servicefilter:
351 services = host.xpathEval('service[description="%s"] | service[description="%s"]' % (servicefilter, encode(servicefilter)))
352 else:
353 services = host.xpathEval('service')
355 # Loop over services in host
356 for service in services:
357 service_dict = {}
359 xmldescr = service.xpathEval('description')[0]
360 xmloutput = service.xpathEval('output')[0]
362 srvdescr = decode(xmldescr.get_content(), xmldescr.prop('encoding'))
363 retcode = service.xpathEval('returncode')[0].get_content()
364 output = decode(xmloutput.get_content(), xmloutput.prop('encoding')).rstrip()
366 try:
367 timestamp = reset_future_timestamp(int(service.xpathEval('timestamp')[0].get_content()), now)
368 except:
369 timestamp = filetimestamp
371 debug(2, verb, 'Found service "%s"' % srvdescr)
373 service_dict = {'host_name':hostname, 'service_description':srvdescr, 'returncode':retcode, 'output':output, 'timestamp':timestamp}
374 checks.append(service_dict)
376 debug(1, verb, 'Host: "%s" - Service: "%s" - RetCode: "%s" - Output: "%s"' % (hostname, srvdescr, retcode, output) )
378 return checks
381 def xml_from_dict(checks, encoding='base64'):
382 lasthost = None
384 db = [(check['host_name'], check) for check in checks]
385 db.sort()
387 xmldoc = libxml2.newDoc('1.0')
388 xmlroot = xmldoc.newChild(None, 'nagixsc', None)
389 xmlroot.setProp('version', '1.0')
390 xmltimestamp = xmlroot.newChild(None, 'timestamp', datetime.datetime.now().strftime('%s'))
392 for entry in db:
393 check = entry[1]
395 if check['host_name'] != lasthost:
396 xmlhost = xmlroot.newChild(None, 'host', None)
397 xmlhostname = xmlhost.newChild(None, 'name', encode(check['host_name'], encoding))
398 lasthost = check['host_name']
400 if check['service_description'] == '' or check['service_description'] == None:
401 # Host check result
402 xmlreturncode = xmlhost.newChild(None, 'returncode', str(check['returncode']))
403 xmloutput = xmlhost.newChild(None, 'output', encode(check['output'], encoding))
404 xmloutput.setProp('encoding', encoding)
405 if check.has_key('timestamp'):
406 xmltimestamp = xmlhost.newChild(None, 'timestamp', str(check['timestamp']))
407 else:
408 # Service check result
409 xmlservice = xmlhost.newChild(None, 'service', None)
410 xmlname = xmlservice.newChild(None, 'description', encode(check['service_description'], encoding))
411 xmlname.setProp('encoding', encoding)
412 xmlreturncode = xmlservice.newChild(None, 'returncode', str(check['returncode']))
413 xmloutput = xmlservice.newChild(None, 'output', encode(check['output'], encoding))
414 xmloutput.setProp('encoding', encoding)
415 if check.has_key('timestamp'):
416 xmltimestamp = xmlservice.newChild(None, 'timestamp', str(check['timestamp']))
418 return xmldoc
421 def xml_merge(xmldocs):
422 checks = []
423 for xmldoc in xmldocs:
424 checks.extend(xml_to_dict(xmldoc))
425 newxmldoc = xml_from_dict(checks)
426 return newxmldoc
429 def check_mark_outdated(check, now, maxtimediff, markold):
430 timedelta = now - check['timestamp']
431 if timedelta > maxtimediff:
432 check['output'] = 'Nag(ix)SC: Check result is %s(>%s) seconds old - %s' % (timedelta, maxtimediff, check['output'])
433 if markold:
434 check['returncode'] = 3
435 return check
438 def reset_future_timestamp(timestamp, now):
439 if timestamp <= now:
440 return timestamp
441 else:
442 return now
444 ##############################################################################
446 def encode_multipart(xmldoc, httpuser, httppasswd):
447 BOUNDARY = mimetools.choose_boundary()
448 CRLF = '\r\n'
449 L = []
450 L.append('--' + BOUNDARY)
451 L.append('Content-Disposition: form-data; name="xmlfile"; filename="xmlfile"')
452 L.append('Content-Type: application/xml')
453 L.append('')
454 L.append(xmldoc.serialize())
455 L.append('--' + BOUNDARY + '--')
456 L.append('')
457 body = CRLF.join(L)
458 content_type = 'multipart/form-data; boundary=%s' % BOUNDARY
459 headers = {'Content-Type': content_type, 'Content-Length': str(len(body))}
461 if httpuser and httppasswd:
462 headers['Authorization'] = 'Basic %s' % base64.b64encode(':'.join([httpuser, httppasswd]))
464 return (headers, body)
466 ##############################################################################
468 def daemonize(pidfile=None, stdin='/dev/null', stdout='/dev/null', stderr='/dev/null'):
469 # 1st fork
470 try:
471 pid = os.fork()
472 if pid > 0:
473 sys.exit(0)
474 except OSError, e:
475 sys.stderr.write("1st fork failed: (%d) %s\n" % (e.errno, e.strerror))
476 sys.exit(1)
477 # Prepare 2nd fork
478 os.chdir("/")
479 os.umask(0)
480 os.setsid( )
481 # 2nd fork
482 try:
483 pid = os.fork()
484 if pid > 0:
485 sys.exit(0)
486 except OSError, e:
487 sys.stderr.write("2nd fork failed: (%d) %s\n" % (e.errno, e.strerror))
488 sys.exit(1)
490 # Try to write PID file
491 if pidfile:
492 pid = str(os.getpid())
493 try:
494 file(pidfile, 'w+').write('%s\n' % pid)
495 except IOError:
496 sys.stderr.write("Could not write PID file, exiting...\n")
497 sys.exit(1)
499 # Redirect stdin, stdout, stderr
500 sys.stdout.flush()
501 sys.stderr.flush()
502 si = file(stdin, 'r')
503 so = file(stdout, 'a+')
504 se = file(stderr, 'a+', 0)
505 os.dup2(si.fileno(), sys.stdin.fileno())
506 os.dup2(so.fileno(), sys.stdout.fileno())
507 os.dup2(se.fileno(), sys.stderr.fileno())
509 return
511 ##############################################################################
513 class MyHTTPServer(SocketServer.ForkingMixIn, BaseHTTPServer.HTTPServer):
514 def __init__(self, server_address, HandlerClass, ssl=False, sslpemfile=None):
515 if ssl:
516 # FIXME: SSL is in Py2.6
517 try:
518 from OpenSSL import SSL
519 except:
520 print 'No Python OpenSSL wrapper/bindings found!'
521 sys.exit(127)
523 SocketServer.BaseServer.__init__(self, server_address, HandlerClass)
524 context = SSL.Context(SSL.SSLv23_METHOD)
525 context.use_privatekey_file (sslpemfile)
526 context.use_certificate_file(sslpemfile)
527 self.socket = SSL.Connection(context, socket.socket(self.address_family, self.socket_type))
528 else:
529 SocketServer.BaseServer.__init__(self, server_address, HandlerClass)
530 self.socket = socket.socket(self.address_family, self.socket_type)
532 self.server_bind()
533 self.server_activate()
536 class MyHTTPRequestHandler(BaseHTTPServer.BaseHTTPRequestHandler):
537 def setup(self):
538 self.connection = self.request
539 self.rfile = socket._fileobject(self.request, "rb", self.rbufsize)
540 self.wfile = socket._fileobject(self.request, "wb", self.wbufsize)
542 ##############################################################################