Code

Rework http2nagios (and xml2nagios) and add SSL
[nagixsc.git] / nagixsc.py
1 import BaseHTTPServer
2 import ConfigParser
3 import SocketServer
4 import base64
5 import datetime
6 import libxml2
7 import os
8 import random
9 import shlex
10 import socket
11 import string
12 import subprocess
13 import sys
15 def debug(level, verb, string):
16         if level <= verb:
17                 print "%s: %s" % (level, string)
20 ##############################################################################
22 def available_encodings():
23         return ['base64', 'plain',]
26 def check_encoding(enc):
27         if enc in available_encodings():
28                 return True
29         else:
30                 return False
33 def decode(data, encoding):
34         if encoding == 'plain':
35                 return data
36         else:
37                 return base64.b64decode(data)
40 def encode(data, encoding=None):
41         if encoding == 'plain':
42                 return data
43         else:
44                 return base64.b64encode(data)
47 ##############################################################################
49 def read_inifile(inifile):
50         config = ConfigParser.RawConfigParser()
51         config.optionxform = str # We need case-sensitive options
52         ini_list = config.read(inifile)
54         if ini_list:
55                 return config
56         else:
57                 return False
60 ##############################################################################
62 def exec_check(host_name, service_descr, cmdline):
63         try:
64                 cmd     = subprocess.Popen(shlex.split(cmdline), stdout=subprocess.PIPE)
65                 output  = cmd.communicate()[0].rstrip()
66                 retcode = cmd.returncode
67         except OSError:
68                 output  = 'Could not execute "%s"' % cmdline
69                 retcode = 127
71         return {'host_name':host_name, 'service_description':service_descr, 'returncode':retcode, 'output':output, 'timestamp':datetime.datetime.now().strftime('%s')}
74 ##############################################################################
76 def conf2dict(config, opt_host=None, opt_service=None):
77         checks = []
79         # Sections are Hosts (not 'nagixsc'), options in sections are Services
80         hosts = config.sections()
81         if 'nagixsc' in hosts:
82                 hosts.remove('nagixsc')
84         # Filter out host/section if it exists
85         if opt_host:
86                 if opt_host in hosts:
87                         hosts = [opt_host,]
88                 else:
89                         hosts = []
91         for host in hosts:
92                 # Overwrite section/host name with '_host_name'
93                 if config.has_option(host,'_host_name'):
94                         host_name = config.get(host,'_host_name')
95                 else:
96                         host_name = host
99                 services = config.options(host)
100                 # Look for host check
101                 if '_host_check' in services and not opt_service:
102                         cmdline = config.get(host, '_host_check')
103                         check = exec_check(host_name, None, cmdline)
104                         checks.append(check)
107                 # Filter out service if given in cmd line options
108                 if opt_service:
109                         if opt_service in services:
110                                 services = [opt_service,]
111                         else:
112                                 services = []
114                 for service in services:
115                         # If option starts with '_' it may be a NagixSC option in the future
116                         if service[0] != '_':
117                                 cmdline = config.get(host, service)
119                                 check = exec_check(host_name, service, cmdline)
120                                 checks.append(check)
122         return checks
125 ##############################################################################
127 def dict2out_passive(checks, xmltimestamp, opt_pipe, opt_verb=0):
128         FORMAT_HOST = '[%s] PROCESS_HOST_CHECK_RESULT;%s;%s;%s'
129         FORMAT_SERVICE = '[%s] PROCESS_SERVICE_CHECK_RESULT;%s;%s;%s;%s'
130         count_services = 0
131         now = datetime.datetime.now().strftime('%s')
133         # Prepare
134         if opt_verb <= 2:
135                 pipe = open(opt_pipe, "w")
136         else:
137                 pipe = None
139         # Output
140         for check in checks:
141                 count_services += 1
142                 if check.has_key('timestamp'):
143                         timestamp = check['timestamp']
144                 else:
145                         timestamp = xmltimestamp
146                 count_services += 1
148                 if check['service_description'] == None or check['service_description'] == '':
149                         # Host check
150                         line = FORMAT_HOST % (now, check['host_name'], check['returncode'], check['output'].replace('\n', '\\n'))
151                 else:
152                         # Service check
153                         line =  FORMAT_SERVICE % (now, check['host_name'], check['service_description'], check['returncode'], check['output'].replace('\n', '\\n'))
155                 if pipe:
156                         pipe.write(line + '\n')
157                 debug(2, opt_verb, line)
159         # Close
160         if pipe:
161                 pipe.close()
162         else:
163                 print "Passive check results NOT written to Nagios pipe due to -vvv!"
165         return count_services
168 def dict2out_checkresult(checks, xmltimestamp, opt_checkresultdir, opt_verb):
169         count_services = 0
170         count_failed = 0
171         list_failed = []
172         chars = string.letters + string.digits
173         ctimestamp = datetime.datetime.now().ctime()
175         for check in checks:
176                 count_services += 1
177                 if check.has_key('timestamp'):
178                         timestamp = check['timestamp']
179                 else:
180                         timestamp = xmltimestamp
182                 filename = os.path.join(opt_checkresultdir, 'c' + ''.join([random.choice(chars) for i in range(6)]))
183                 try:
184                         crfile = open(filename, "w")
185                         if check['service_description'] == None or check['service_description'] == '':
186                                 # Host check
187                                 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') ) )
188                         else:
189                                 # Service check
190                                 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') ) )
191                         crfile.close()
193                         # Create OK file
194                         open(filename + '.ok', 'w').close()
195                 except:
196                         count_failed += 1
197                         list_failed.append([filename, check['host_name'], check['service_description']])
199         return (count_services, count_failed, list_failed)
202 ##############################################################################
204 def read_xml(options):
205         if options.url != None:
206                 import urllib2
208                 if options.httpuser and options.httppasswd:
209                         passman = urllib2.HTTPPasswordMgrWithDefaultRealm()
210                         passman.add_password(None, options.url, options.httpuser, options.httppasswd)
211                         authhandler = urllib2.HTTPBasicAuthHandler(passman)
212                         opener = urllib2.build_opener(authhandler)
213                         urllib2.install_opener(opener)
215                 try:
216                         response = urllib2.urlopen(options.url)
217                 except urllib2.HTTPError, error:
218                         print error
219                         sys.exit(0)
220                 except urllib2.URLError, error:
221                         print error.reason[1]
222                         sys.exit(0)
224                 doc = libxml2.parseDoc(response.read())
225                 response.close()
227         else:
228                 doc = libxml2.parseFile(options.file)
230         return doc
233 def read_xml_from_string(content):
234         return libxml2.parseDoc(content)
237 ##############################################################################
239 def xml_check_version(xmldoc):
240         # FIXME: Check XML structure
241         try:
242                 xmlnagixsc = xmldoc.xpathNewContext().xpathEval('/nagixsc')[0]
243         except:
244                 return (False, 'Not a Nag(IX)SC XML file!')
246         try:
247                 if xmlnagixsc.prop('version') != "1.0":
248                         return (False, 'Wrong version (found "%s", need "1.0") of XML file!' % xmlnagixsc.prop('version'))
249         except:
250                 return (False, 'No version information found in XML file!')
252         return (True, 'XML seems to be ok')
255 def xml_get_timestamp(xmldoc):
256         try:
257                 timestamp = int(xmldoc.xpathNewContext().xpathEval('/nagixsc/timestamp')[0].get_content())
258         except:
259                 return False
261         return timestamp
264 def xml_to_dict(xmldoc, verb=0, hostfilter=None, servicefilter=None):
265         checks = []
266         now = int(datetime.datetime.now().strftime('%s'))
267         filetimestamp = reset_future_timestamp(xml_get_timestamp(xmldoc), now)
269         if hostfilter:
270                 hosts = xmldoc.xpathNewContext().xpathEval('/nagixsc/host[name="%s"] | /nagixsc/host[name="%s"]' % (hostfilter, encode(hostfilter)))
271         else:
272                 hosts = xmldoc.xpathNewContext().xpathEval('/nagixsc/host')
274         for host in hosts:
275                 xmlhostname = host.xpathEval('name')[0]
276                 hostname = decode(xmlhostname.get_content(), xmlhostname.prop('encoding'))
277                 debug(2, verb, 'Found host "%s"' % hostname)
279                 # Look for Host check result
280                 if host.xpathEval('returncode'):
281                         retcode   = host.xpathEval('returncode')[0].get_content()
282                 else:
283                         retcode   = None
285                 if host.xpathEval('output'):
286                         xmloutput = host.xpathEval('output')[0]
287                         output    = decode(xmloutput.get_content(), xmloutput.prop('encoding')).rstrip()
288                 else:
289                         output    = None
291                 if host.xpathEval('timestamp'):
292                         timestamp = reset_future_timestamp(int(host.xpathEval('timestamp')[0].get_content()), now)
293                 else:
294                         timestamp = filetimestamp
296                 if retcode and output:
297                         checks.append({'host_name':hostname, 'service_description':None, 'returncode':retcode, 'output':output, 'timestamp':timestamp})
300                 # Look for service filter
301                 if servicefilter:
302                         services = host.xpathEval('service[description="%s"] | service[description="%s"]' % (servicefilter, encode(servicefilter)))
303                 else:
304                         services = host.xpathEval('service')
306                 # Loop over services in host
307                 for service in services:
308                         service_dict = {}
310                         xmldescr  = service.xpathEval('description')[0]
311                         xmloutput = service.xpathEval('output')[0]
313                         srvdescr = decode(xmldescr.get_content(), xmldescr.prop('encoding'))
314                         retcode  = service.xpathEval('returncode')[0].get_content()
315                         output   = decode(xmloutput.get_content(), xmloutput.prop('encoding')).rstrip()
317                         try:
318                                 timestamp = reset_future_timestamp(int(service.xpathEval('timestamp')[0].get_content()), now)
319                         except:
320                                 timestamp = filetimestamp
322                         debug(2, verb, 'Found service "%s"' % srvdescr)
324                         service_dict = {'host_name':hostname, 'service_description':srvdescr, 'returncode':retcode, 'output':output, 'timestamp':timestamp}
325                         checks.append(service_dict)
327                         debug(1, verb, 'Host: "%s" - Service: "%s" - RetCode: "%s" - Output: "%s"' % (hostname, srvdescr, retcode, output) )
329         return checks
332 def xml_from_dict(checks, encoding='base64'):
333         lasthost = None
335         db = [(check['host_name'], check) for check in checks]
336         db.sort()
338         xmldoc = libxml2.newDoc('1.0')
339         xmlroot = xmldoc.newChild(None, 'nagixsc', None)
340         xmlroot.setProp('version', '1.0')
341         xmltimestamp = xmlroot.newChild(None, 'timestamp', datetime.datetime.now().strftime('%s'))
343         for entry in db:
344                 check = entry[1]
346                 if check['host_name'] != lasthost:
347                         xmlhost = xmlroot.newChild(None, 'host', None)
348                         xmlhostname = xmlhost.newChild(None, 'name', encode(check['host_name'], encoding))
349                         lasthost = check['host_name']
351                 if check['service_description'] == '' or check['service_description'] == None:
352                         # Host check result
353                         xmlreturncode = xmlhost.newChild(None, 'returncode', str(check['returncode']))
354                         xmloutput     = xmlhost.newChild(None, 'output', encode(check['output'], encoding))
355                         xmloutput.setProp('encoding', encoding)
356                         if check.has_key('timestamp'):
357                                 xmltimestamp  = xmlhost.newChild(None, 'timestamp', str(check['timestamp']))
358                 else:
359                         # Service check result
360                         xmlservice    = xmlhost.newChild(None, 'service', None)
361                         xmlname       = xmlservice.newChild(None, 'description', encode(check['service_description'], encoding))
362                         xmlname.setProp('encoding', encoding)
363                         xmlreturncode = xmlservice.newChild(None, 'returncode', str(check['returncode']))
364                         xmloutput     = xmlservice.newChild(None, 'output', encode(check['output'], encoding))
365                         xmloutput.setProp('encoding', encoding)
366                         if check.has_key('timestamp'):
367                                 xmltimestamp  = xmlservice.newChild(None, 'timestamp', str(check['timestamp']))
369         return xmldoc
372 def check_mark_outdated(check, now, maxtimediff, markold):
373         timedelta = now - check['timestamp']
374         if timedelta > maxtimediff:
375                 check['output'] = 'Nag(ix)SC: Check result is %s(>%s) seconds old - %s' % (timedelta, maxtimediff, check['output'])
376                 if markold:
377                         check['returncode'] = 3
378         return check
381 def reset_future_timestamp(timestamp, now):
382         if timestamp <= now:
383                 return timestamp
384         else:
385                 return now
387 ##############################################################################
389 class MyHTTPServer(BaseHTTPServer.HTTPServer):
390         def __init__(self, server_address, HandlerClass, ssl=False, sslpemfile=None):
391                 if ssl:
392                         # FIXME: SSL is in Py2.6
393                         try:
394                                 from OpenSSL import SSL
395                         except:
396                                 print 'No Python OpenSSL wrapper/bindings found!'
397                                 sys.exit(127)
399                         SocketServer.BaseServer.__init__(self, server_address, HandlerClass)
400                         context = SSL.Context(SSL.SSLv23_METHOD)
401                         context.use_privatekey_file (sslpemfile)
402                         context.use_certificate_file(sslpemfile)
403                         self.socket = SSL.Connection(context, socket.socket(self.address_family, self.socket_type))
404                 else:
405                         SocketServer.BaseServer.__init__(self, server_address, HandlerClass)
406                         self.socket = socket.socket(self.address_family, self.socket_type)
408                 self.server_bind()
409                 self.server_activate()
412 class MyHTTPRequestHandler(BaseHTTPServer.BaseHTTPRequestHandler):
413         def setup(self):
414                 self.connection = self.request
415                 self.rfile = socket._fileobject(self.request, "rb", self.rbufsize)
416                 self.wfile = socket._fileobject(self.request, "wb", self.wbufsize)
418 ##############################################################################