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 socket
12 import string
13 import subprocess
14 import sys
16 def debug(level, verb, string):
17 if level <= verb:
18 print "%s: %s" % (level, string)
21 ##############################################################################
23 def available_encodings():
24 return ['base64', 'plain',]
27 def check_encoding(enc):
28 if enc in available_encodings():
29 return True
30 else:
31 return False
34 def decode(data, encoding):
35 if encoding == 'plain':
36 return data
37 else:
38 return base64.b64decode(data)
41 def encode(data, encoding=None):
42 if encoding == 'plain':
43 return data
44 else:
45 return base64.b64encode(data)
48 ##############################################################################
50 def read_inifile(inifile):
51 config = ConfigParser.RawConfigParser()
52 config.optionxform = str # We need case-sensitive options
53 ini_list = config.read(inifile)
55 if ini_list:
56 return config
57 else:
58 return False
61 ##############################################################################
63 def exec_check(host_name, service_descr, cmdline):
64 cmdarray = shlex.split(cmdline)
66 check = {}
67 check['host_name'] = host_name
68 check['service_description'] = service_descr
70 if len(cmdarray) == 0:
71 check['output'] = 'No command line specified!'
72 check['returncode'] = 127
73 return check
75 try:
76 cmd = subprocess.Popen(cmdarray, stdout=subprocess.PIPE)
77 check['output'] = cmd.communicate()[0].rstrip()
78 check['returncode'] = cmd.returncode
79 except OSError:
80 check['output'] = 'Could not execute "%s"' % cmdline
81 check['returncode'] = 127
83 check['timestamp'] = datetime.datetime.now().strftime('%s')
84 return check
87 ##############################################################################
89 def conf2dict(config, opt_host=None, opt_service=None):
90 checks = []
92 # Sections are Hosts (not 'nagixsc'), options in sections are Services
93 hosts = config.sections()
94 if 'nagixsc' in hosts:
95 hosts.remove('nagixsc')
97 # Filter out host/section if it exists
98 if opt_host:
99 if opt_host in hosts:
100 hosts = [opt_host,]
101 else:
102 hosts = []
104 for host in hosts:
105 # Overwrite section/host name with '_host_name'
106 if config.has_option(host,'_host_name'):
107 host_name = config.get(host,'_host_name')
108 else:
109 host_name = host
112 services = config.options(host)
113 # Look for host check
114 if '_host_check' in services and not opt_service:
115 cmdline = config.get(host, '_host_check')
116 check = exec_check(host_name, None, cmdline)
117 checks.append(check)
120 # Filter out service if given in cmd line options
121 if opt_service:
122 if opt_service in services:
123 services = [opt_service,]
124 else:
125 services = []
127 for service in services:
128 # If option starts with '_' it may be a NagixSC option in the future
129 if service[0] != '_':
130 cmdline = config.get(host, service)
132 check = exec_check(host_name, service, cmdline)
133 checks.append(check)
135 return checks
138 ##############################################################################
140 def dict2out_passive(checks, xmltimestamp, opt_pipe, opt_verb=0):
141 FORMAT_HOST = '[%s] PROCESS_HOST_CHECK_RESULT;%s;%s;%s'
142 FORMAT_SERVICE = '[%s] PROCESS_SERVICE_CHECK_RESULT;%s;%s;%s;%s'
143 count_services = 0
144 now = datetime.datetime.now().strftime('%s')
146 # Prepare
147 if opt_verb <= 2:
148 pipe = open(opt_pipe, "w")
149 else:
150 pipe = None
152 # Output
153 for check in checks:
154 count_services += 1
155 if check.has_key('timestamp'):
156 timestamp = check['timestamp']
157 else:
158 timestamp = xmltimestamp
159 count_services += 1
161 if check['service_description'] == None or check['service_description'] == '':
162 # Host check
163 line = FORMAT_HOST % (now, check['host_name'], check['returncode'], check['output'].replace('\n', '\\n'))
164 else:
165 # Service check
166 line = FORMAT_SERVICE % (now, check['host_name'], check['service_description'], check['returncode'], check['output'].replace('\n', '\\n'))
168 if pipe:
169 pipe.write(line + '\n')
170 debug(2, opt_verb, line)
172 # Close
173 if pipe:
174 pipe.close()
175 else:
176 print "Passive check results NOT written to Nagios pipe due to -vvv!"
178 return count_services
181 def dict2out_checkresult(checks, xmltimestamp, opt_checkresultdir, opt_verb):
182 count_services = 0
183 count_failed = 0
184 list_failed = []
185 chars = string.letters + string.digits
186 ctimestamp = datetime.datetime.now().ctime()
188 for check in checks:
189 count_services += 1
190 if check.has_key('timestamp'):
191 timestamp = check['timestamp']
192 else:
193 timestamp = xmltimestamp
195 filename = os.path.join(opt_checkresultdir, 'c' + ''.join([random.choice(chars) for i in range(6)]))
196 try:
197 crfile = open(filename, "w")
198 if check['service_description'] == None or check['service_description'] == '':
199 # Host check
200 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') ) )
201 else:
202 # Service check
203 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') ) )
204 crfile.close()
206 # Create OK file
207 open(filename + '.ok', 'w').close()
208 except:
209 count_failed += 1
210 list_failed.append([filename, check['host_name'], check['service_description']])
212 return (count_services, count_failed, list_failed)
215 ##############################################################################
217 def read_xml(options):
218 if options.url != None:
219 import urllib2
221 if options.httpuser and options.httppasswd:
222 passman = urllib2.HTTPPasswordMgrWithDefaultRealm()
223 passman.add_password(None, options.url, options.httpuser, options.httppasswd)
224 authhandler = urllib2.HTTPBasicAuthHandler(passman)
225 opener = urllib2.build_opener(authhandler)
226 urllib2.install_opener(opener)
228 try:
229 response = urllib2.urlopen(options.url)
230 except urllib2.HTTPError, error:
231 print error
232 sys.exit(0)
233 except urllib2.URLError, error:
234 print error.reason[1]
235 sys.exit(0)
237 doc = libxml2.parseDoc(response.read())
238 response.close()
240 else:
241 doc = libxml2.parseFile(options.file)
243 return doc
246 def read_xml_from_string(content):
247 return libxml2.parseDoc(content)
250 ##############################################################################
252 def xml_check_version(xmldoc):
253 # FIXME: Check XML structure
254 try:
255 xmlnagixsc = xmldoc.xpathNewContext().xpathEval('/nagixsc')[0]
256 except:
257 return (False, 'Not a Nag(IX)SC XML file!')
259 try:
260 if xmlnagixsc.prop('version') != "1.0":
261 return (False, 'Wrong version (found "%s", need "1.0") of XML file!' % xmlnagixsc.prop('version'))
262 except:
263 return (False, 'No version information found in XML file!')
265 return (True, 'XML seems to be ok')
268 def xml_get_timestamp(xmldoc):
269 try:
270 timestamp = int(xmldoc.xpathNewContext().xpathEval('/nagixsc/timestamp')[0].get_content())
271 except:
272 return False
274 return timestamp
277 def xml_to_dict(xmldoc, verb=0, hostfilter=None, servicefilter=None):
278 checks = []
279 now = int(datetime.datetime.now().strftime('%s'))
280 filetimestamp = reset_future_timestamp(xml_get_timestamp(xmldoc), now)
282 if hostfilter:
283 hosts = xmldoc.xpathNewContext().xpathEval('/nagixsc/host[name="%s"] | /nagixsc/host[name="%s"]' % (hostfilter, encode(hostfilter)))
284 else:
285 hosts = xmldoc.xpathNewContext().xpathEval('/nagixsc/host')
287 for host in hosts:
288 xmlhostname = host.xpathEval('name')[0]
289 hostname = decode(xmlhostname.get_content(), xmlhostname.prop('encoding'))
290 debug(2, verb, 'Found host "%s"' % hostname)
292 # Look for Host check result
293 if host.xpathEval('returncode'):
294 retcode = host.xpathEval('returncode')[0].get_content()
295 else:
296 retcode = None
298 if host.xpathEval('output'):
299 xmloutput = host.xpathEval('output')[0]
300 output = decode(xmloutput.get_content(), xmloutput.prop('encoding')).rstrip()
301 else:
302 output = None
304 if host.xpathEval('timestamp'):
305 timestamp = reset_future_timestamp(int(host.xpathEval('timestamp')[0].get_content()), now)
306 else:
307 timestamp = filetimestamp
309 # Append only if no service filter
310 if not servicefilter and retcode and output:
311 checks.append({'host_name':hostname, 'service_description':None, 'returncode':retcode, 'output':output, 'timestamp':timestamp})
314 # Look for service filter
315 if servicefilter:
316 services = host.xpathEval('service[description="%s"] | service[description="%s"]' % (servicefilter, encode(servicefilter)))
317 else:
318 services = host.xpathEval('service')
320 # Loop over services in host
321 for service in services:
322 service_dict = {}
324 xmldescr = service.xpathEval('description')[0]
325 xmloutput = service.xpathEval('output')[0]
327 srvdescr = decode(xmldescr.get_content(), xmldescr.prop('encoding'))
328 retcode = service.xpathEval('returncode')[0].get_content()
329 output = decode(xmloutput.get_content(), xmloutput.prop('encoding')).rstrip()
331 try:
332 timestamp = reset_future_timestamp(int(service.xpathEval('timestamp')[0].get_content()), now)
333 except:
334 timestamp = filetimestamp
336 debug(2, verb, 'Found service "%s"' % srvdescr)
338 service_dict = {'host_name':hostname, 'service_description':srvdescr, 'returncode':retcode, 'output':output, 'timestamp':timestamp}
339 checks.append(service_dict)
341 debug(1, verb, 'Host: "%s" - Service: "%s" - RetCode: "%s" - Output: "%s"' % (hostname, srvdescr, retcode, output) )
343 return checks
346 def xml_from_dict(checks, encoding='base64'):
347 lasthost = None
349 db = [(check['host_name'], check) for check in checks]
350 db.sort()
352 xmldoc = libxml2.newDoc('1.0')
353 xmlroot = xmldoc.newChild(None, 'nagixsc', None)
354 xmlroot.setProp('version', '1.0')
355 xmltimestamp = xmlroot.newChild(None, 'timestamp', datetime.datetime.now().strftime('%s'))
357 for entry in db:
358 check = entry[1]
360 if check['host_name'] != lasthost:
361 xmlhost = xmlroot.newChild(None, 'host', None)
362 xmlhostname = xmlhost.newChild(None, 'name', encode(check['host_name'], encoding))
363 lasthost = check['host_name']
365 if check['service_description'] == '' or check['service_description'] == None:
366 # Host check result
367 xmlreturncode = xmlhost.newChild(None, 'returncode', str(check['returncode']))
368 xmloutput = xmlhost.newChild(None, 'output', encode(check['output'], encoding))
369 xmloutput.setProp('encoding', encoding)
370 if check.has_key('timestamp'):
371 xmltimestamp = xmlhost.newChild(None, 'timestamp', str(check['timestamp']))
372 else:
373 # Service check result
374 xmlservice = xmlhost.newChild(None, 'service', None)
375 xmlname = xmlservice.newChild(None, 'description', encode(check['service_description'], encoding))
376 xmlname.setProp('encoding', encoding)
377 xmlreturncode = xmlservice.newChild(None, 'returncode', str(check['returncode']))
378 xmloutput = xmlservice.newChild(None, 'output', encode(check['output'], encoding))
379 xmloutput.setProp('encoding', encoding)
380 if check.has_key('timestamp'):
381 xmltimestamp = xmlservice.newChild(None, 'timestamp', str(check['timestamp']))
383 return xmldoc
386 def xml_merge(xmldocs):
387 checks = []
388 for xmldoc in xmldocs:
389 checks.extend(xml_to_dict(xmldoc))
390 newxmldoc = xml_from_dict(checks)
391 return newxmldoc
394 def check_mark_outdated(check, now, maxtimediff, markold):
395 timedelta = now - check['timestamp']
396 if timedelta > maxtimediff:
397 check['output'] = 'Nag(ix)SC: Check result is %s(>%s) seconds old - %s' % (timedelta, maxtimediff, check['output'])
398 if markold:
399 check['returncode'] = 3
400 return check
403 def reset_future_timestamp(timestamp, now):
404 if timestamp <= now:
405 return timestamp
406 else:
407 return now
409 ##############################################################################
411 def encode_multipart(xmldoc, httpuser, httppasswd):
412 BOUNDARY = mimetools.choose_boundary()
413 CRLF = '\r\n'
414 L = []
415 L.append('--' + BOUNDARY)
416 L.append('Content-Disposition: form-data; name="xmlfile"; filename="xmlfile"')
417 L.append('Content-Type: application/xml')
418 L.append('')
419 L.append(xmldoc.serialize())
420 L.append('--' + BOUNDARY + '--')
421 L.append('')
422 body = CRLF.join(L)
423 content_type = 'multipart/form-data; boundary=%s' % BOUNDARY
424 headers = {'Content-Type': content_type, 'Content-Length': str(len(body))}
426 if httpuser and httppasswd:
427 headers['Authorization'] = 'Basic %s' % base64.b64encode(':'.join([httpuser, httppasswd]))
429 return (headers, body)
431 ##############################################################################
433 def daemonize(pidfile=None, stdin='/dev/null', stdout='/dev/null', stderr='/dev/null'):
434 # 1st fork
435 try:
436 pid = os.fork()
437 if pid > 0:
438 sys.exit(0)
439 except OSError, e:
440 sys.stderr.write("1st fork failed: (%d) %sn" % (e.errno, e.strerror))
441 sys.exit(1)
442 # Prepare 2nd fork
443 os.chdir("/")
444 os.umask(0)
445 os.setsid( )
446 # 2nd fork
447 try:
448 pid = os.fork()
449 if pid > 0:
450 sys.exit(0)
451 except OSError, e:
452 sys.stderr.write("2nd fork failed: (%d) %sn" % (e.errno, e.strerror))
453 sys.exit(1)
454 # Redirect stdin, stdout, stderr
455 sys.stdout.flush()
456 sys.stderr.flush()
457 si = file(stdin, 'r')
458 so = file(stdout, 'a+')
459 se = file(stderr, 'a+', 0)
460 os.dup2(si.fileno(), sys.stdin.fileno())
461 os.dup2(so.fileno(), sys.stdout.fileno())
462 os.dup2(se.fileno(), sys.stderr.fileno())
464 if pidfile:
465 pid = str(os.getpid())
466 file(pidfile, 'w+').write('%s\n' % pid)
468 return
470 ##############################################################################
472 class MyHTTPServer(BaseHTTPServer.HTTPServer):
473 def __init__(self, server_address, HandlerClass, ssl=False, sslpemfile=None):
474 if ssl:
475 # FIXME: SSL is in Py2.6
476 try:
477 from OpenSSL import SSL
478 except:
479 print 'No Python OpenSSL wrapper/bindings found!'
480 sys.exit(127)
482 SocketServer.BaseServer.__init__(self, server_address, HandlerClass)
483 context = SSL.Context(SSL.SSLv23_METHOD)
484 context.use_privatekey_file (sslpemfile)
485 context.use_certificate_file(sslpemfile)
486 self.socket = SSL.Connection(context, socket.socket(self.address_family, self.socket_type))
487 else:
488 SocketServer.BaseServer.__init__(self, server_address, HandlerClass)
489 self.socket = socket.socket(self.address_family, self.socket_type)
491 self.server_bind()
492 self.server_activate()
495 class MyHTTPRequestHandler(BaseHTTPServer.BaseHTTPRequestHandler):
496 def setup(self):
497 self.connection = self.request
498 self.rfile = socket._fileobject(self.request, "rb", self.rbufsize)
499 self.wfile = socket._fileobject(self.request, "wb", self.wbufsize)
501 ##############################################################################