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 try:
65 cmd = subprocess.Popen(shlex.split(cmdline), stdout=subprocess.PIPE)
66 output = cmd.communicate()[0].rstrip()
67 retcode = cmd.returncode
68 except OSError:
69 output = 'Could not execute "%s"' % cmdline
70 retcode = 127
72 return {'host_name':host_name, 'service_description':service_descr, 'returncode':retcode, 'output':output, 'timestamp':datetime.datetime.now().strftime('%s')}
75 ##############################################################################
77 def conf2dict(config, opt_host=None, opt_service=None):
78 checks = []
80 # Sections are Hosts (not 'nagixsc'), options in sections are Services
81 hosts = config.sections()
82 if 'nagixsc' in hosts:
83 hosts.remove('nagixsc')
85 # Filter out host/section if it exists
86 if opt_host:
87 if opt_host in hosts:
88 hosts = [opt_host,]
89 else:
90 hosts = []
92 for host in hosts:
93 # Overwrite section/host name with '_host_name'
94 if config.has_option(host,'_host_name'):
95 host_name = config.get(host,'_host_name')
96 else:
97 host_name = host
100 services = config.options(host)
101 # Look for host check
102 if '_host_check' in services and not opt_service:
103 cmdline = config.get(host, '_host_check')
104 check = exec_check(host_name, None, cmdline)
105 checks.append(check)
108 # Filter out service if given in cmd line options
109 if opt_service:
110 if opt_service in services:
111 services = [opt_service,]
112 else:
113 services = []
115 for service in services:
116 # If option starts with '_' it may be a NagixSC option in the future
117 if service[0] != '_':
118 cmdline = config.get(host, service)
120 check = exec_check(host_name, service, cmdline)
121 checks.append(check)
123 return checks
126 ##############################################################################
128 def dict2out_passive(checks, xmltimestamp, opt_pipe, opt_verb=0):
129 FORMAT_HOST = '[%s] PROCESS_HOST_CHECK_RESULT;%s;%s;%s'
130 FORMAT_SERVICE = '[%s] PROCESS_SERVICE_CHECK_RESULT;%s;%s;%s;%s'
131 count_services = 0
132 now = datetime.datetime.now().strftime('%s')
134 # Prepare
135 if opt_verb <= 2:
136 pipe = open(opt_pipe, "w")
137 else:
138 pipe = None
140 # Output
141 for check in checks:
142 count_services += 1
143 if check.has_key('timestamp'):
144 timestamp = check['timestamp']
145 else:
146 timestamp = xmltimestamp
147 count_services += 1
149 if check['service_description'] == None or check['service_description'] == '':
150 # Host check
151 line = FORMAT_HOST % (now, check['host_name'], check['returncode'], check['output'].replace('\n', '\\n'))
152 else:
153 # Service check
154 line = FORMAT_SERVICE % (now, check['host_name'], check['service_description'], check['returncode'], check['output'].replace('\n', '\\n'))
156 if pipe:
157 pipe.write(line + '\n')
158 debug(2, opt_verb, line)
160 # Close
161 if pipe:
162 pipe.close()
163 else:
164 print "Passive check results NOT written to Nagios pipe due to -vvv!"
166 return count_services
169 def dict2out_checkresult(checks, xmltimestamp, opt_checkresultdir, opt_verb):
170 count_services = 0
171 count_failed = 0
172 list_failed = []
173 chars = string.letters + string.digits
174 ctimestamp = datetime.datetime.now().ctime()
176 for check in checks:
177 count_services += 1
178 if check.has_key('timestamp'):
179 timestamp = check['timestamp']
180 else:
181 timestamp = xmltimestamp
183 filename = os.path.join(opt_checkresultdir, 'c' + ''.join([random.choice(chars) for i in range(6)]))
184 try:
185 crfile = open(filename, "w")
186 if check['service_description'] == None or check['service_description'] == '':
187 # Host check
188 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') ) )
189 else:
190 # Service check
191 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') ) )
192 crfile.close()
194 # Create OK file
195 open(filename + '.ok', 'w').close()
196 except:
197 count_failed += 1
198 list_failed.append([filename, check['host_name'], check['service_description']])
200 return (count_services, count_failed, list_failed)
203 ##############################################################################
205 def read_xml(options):
206 if options.url != None:
207 import urllib2
209 if options.httpuser and options.httppasswd:
210 passman = urllib2.HTTPPasswordMgrWithDefaultRealm()
211 passman.add_password(None, options.url, options.httpuser, options.httppasswd)
212 authhandler = urllib2.HTTPBasicAuthHandler(passman)
213 opener = urllib2.build_opener(authhandler)
214 urllib2.install_opener(opener)
216 try:
217 response = urllib2.urlopen(options.url)
218 except urllib2.HTTPError, error:
219 print error
220 sys.exit(0)
221 except urllib2.URLError, error:
222 print error.reason[1]
223 sys.exit(0)
225 doc = libxml2.parseDoc(response.read())
226 response.close()
228 else:
229 doc = libxml2.parseFile(options.file)
231 return doc
234 def read_xml_from_string(content):
235 return libxml2.parseDoc(content)
238 ##############################################################################
240 def xml_check_version(xmldoc):
241 # FIXME: Check XML structure
242 try:
243 xmlnagixsc = xmldoc.xpathNewContext().xpathEval('/nagixsc')[0]
244 except:
245 return (False, 'Not a Nag(IX)SC XML file!')
247 try:
248 if xmlnagixsc.prop('version') != "1.0":
249 return (False, 'Wrong version (found "%s", need "1.0") of XML file!' % xmlnagixsc.prop('version'))
250 except:
251 return (False, 'No version information found in XML file!')
253 return (True, 'XML seems to be ok')
256 def xml_get_timestamp(xmldoc):
257 try:
258 timestamp = int(xmldoc.xpathNewContext().xpathEval('/nagixsc/timestamp')[0].get_content())
259 except:
260 return False
262 return timestamp
265 def xml_to_dict(xmldoc, verb=0, hostfilter=None, servicefilter=None):
266 checks = []
267 now = int(datetime.datetime.now().strftime('%s'))
268 filetimestamp = reset_future_timestamp(xml_get_timestamp(xmldoc), now)
270 if hostfilter:
271 hosts = xmldoc.xpathNewContext().xpathEval('/nagixsc/host[name="%s"] | /nagixsc/host[name="%s"]' % (hostfilter, encode(hostfilter)))
272 else:
273 hosts = xmldoc.xpathNewContext().xpathEval('/nagixsc/host')
275 for host in hosts:
276 xmlhostname = host.xpathEval('name')[0]
277 hostname = decode(xmlhostname.get_content(), xmlhostname.prop('encoding'))
278 debug(2, verb, 'Found host "%s"' % hostname)
280 # Look for Host check result
281 if host.xpathEval('returncode'):
282 retcode = host.xpathEval('returncode')[0].get_content()
283 else:
284 retcode = None
286 if host.xpathEval('output'):
287 xmloutput = host.xpathEval('output')[0]
288 output = decode(xmloutput.get_content(), xmloutput.prop('encoding')).rstrip()
289 else:
290 output = None
292 if host.xpathEval('timestamp'):
293 timestamp = reset_future_timestamp(int(host.xpathEval('timestamp')[0].get_content()), now)
294 else:
295 timestamp = filetimestamp
297 if retcode and output:
298 checks.append({'host_name':hostname, 'service_description':None, 'returncode':retcode, 'output':output, 'timestamp':timestamp})
301 # Look for service filter
302 if servicefilter:
303 services = host.xpathEval('service[description="%s"] | service[description="%s"]' % (servicefilter, encode(servicefilter)))
304 else:
305 services = host.xpathEval('service')
307 # Loop over services in host
308 for service in services:
309 service_dict = {}
311 xmldescr = service.xpathEval('description')[0]
312 xmloutput = service.xpathEval('output')[0]
314 srvdescr = decode(xmldescr.get_content(), xmldescr.prop('encoding'))
315 retcode = service.xpathEval('returncode')[0].get_content()
316 output = decode(xmloutput.get_content(), xmloutput.prop('encoding')).rstrip()
318 try:
319 timestamp = reset_future_timestamp(int(service.xpathEval('timestamp')[0].get_content()), now)
320 except:
321 timestamp = filetimestamp
323 debug(2, verb, 'Found service "%s"' % srvdescr)
325 service_dict = {'host_name':hostname, 'service_description':srvdescr, 'returncode':retcode, 'output':output, 'timestamp':timestamp}
326 checks.append(service_dict)
328 debug(1, verb, 'Host: "%s" - Service: "%s" - RetCode: "%s" - Output: "%s"' % (hostname, srvdescr, retcode, output) )
330 return checks
333 def xml_from_dict(checks, encoding='base64'):
334 lasthost = None
336 db = [(check['host_name'], check) for check in checks]
337 db.sort()
339 xmldoc = libxml2.newDoc('1.0')
340 xmlroot = xmldoc.newChild(None, 'nagixsc', None)
341 xmlroot.setProp('version', '1.0')
342 xmltimestamp = xmlroot.newChild(None, 'timestamp', datetime.datetime.now().strftime('%s'))
344 for entry in db:
345 check = entry[1]
347 if check['host_name'] != lasthost:
348 xmlhost = xmlroot.newChild(None, 'host', None)
349 xmlhostname = xmlhost.newChild(None, 'name', encode(check['host_name'], encoding))
350 lasthost = check['host_name']
352 if check['service_description'] == '' or check['service_description'] == None:
353 # Host check result
354 xmlreturncode = xmlhost.newChild(None, 'returncode', str(check['returncode']))
355 xmloutput = xmlhost.newChild(None, 'output', encode(check['output'], encoding))
356 xmloutput.setProp('encoding', encoding)
357 if check.has_key('timestamp'):
358 xmltimestamp = xmlhost.newChild(None, 'timestamp', str(check['timestamp']))
359 else:
360 # Service check result
361 xmlservice = xmlhost.newChild(None, 'service', None)
362 xmlname = xmlservice.newChild(None, 'description', encode(check['service_description'], encoding))
363 xmlname.setProp('encoding', encoding)
364 xmlreturncode = xmlservice.newChild(None, 'returncode', str(check['returncode']))
365 xmloutput = xmlservice.newChild(None, 'output', encode(check['output'], encoding))
366 xmloutput.setProp('encoding', encoding)
367 if check.has_key('timestamp'):
368 xmltimestamp = xmlservice.newChild(None, 'timestamp', str(check['timestamp']))
370 return xmldoc
373 def check_mark_outdated(check, now, maxtimediff, markold):
374 timedelta = now - check['timestamp']
375 if timedelta > maxtimediff:
376 check['output'] = 'Nag(ix)SC: Check result is %s(>%s) seconds old - %s' % (timedelta, maxtimediff, check['output'])
377 if markold:
378 check['returncode'] = 3
379 return check
382 def reset_future_timestamp(timestamp, now):
383 if timestamp <= now:
384 return timestamp
385 else:
386 return now
388 ##############################################################################
390 def encode_multipart(xmldoc, httpuser, httppasswd):
391 BOUNDARY = mimetools.choose_boundary()
392 CRLF = '\r\n'
393 L = []
394 L.append('--' + BOUNDARY)
395 L.append('Content-Disposition: form-data; name="xmlfile"; filename="xmlfile"')
396 L.append('Content-Type: application/xml')
397 L.append('')
398 L.append(xmldoc.serialize())
399 L.append('--' + BOUNDARY + '--')
400 L.append('')
401 body = CRLF.join(L)
402 content_type = 'multipart/form-data; boundary=%s' % BOUNDARY
403 headers = {'Content-Type': content_type, 'Content-Length': str(len(body))}
405 if httpuser and httppasswd:
406 headers['Authorization'] = 'Basic %s' % base64.b64encode(':'.join([httpuser, httppasswd]))
408 return (headers, body)
410 ##############################################################################
412 class MyHTTPServer(BaseHTTPServer.HTTPServer):
413 def __init__(self, server_address, HandlerClass, ssl=False, sslpemfile=None):
414 if ssl:
415 # FIXME: SSL is in Py2.6
416 try:
417 from OpenSSL import SSL
418 except:
419 print 'No Python OpenSSL wrapper/bindings found!'
420 sys.exit(127)
422 SocketServer.BaseServer.__init__(self, server_address, HandlerClass)
423 context = SSL.Context(SSL.SSLv23_METHOD)
424 context.use_privatekey_file (sslpemfile)
425 context.use_certificate_file(sslpemfile)
426 self.socket = SSL.Connection(context, socket.socket(self.address_family, self.socket_type))
427 else:
428 SocketServer.BaseServer.__init__(self, server_address, HandlerClass)
429 self.socket = socket.socket(self.address_family, self.socket_type)
431 self.server_bind()
432 self.server_activate()
435 class MyHTTPRequestHandler(BaseHTTPServer.BaseHTTPRequestHandler):
436 def setup(self):
437 self.connection = self.request
438 self.rfile = socket._fileobject(self.request, "rb", self.rbufsize)
439 self.wfile = socket._fileobject(self.request, "wb", self.wbufsize)
441 ##############################################################################