Code

Changed license to GPL-2+ (from GPL-2only).
[nagixsc.git] / nagixsc / __init__.py
1 # Nag(ix)SC -- __init__.py
2 #
3 # Copyright (C) 2009-2010 Sven Velt <sv@teamix.net>
4 #
5 # This program is free software; you can redistribute it and/or modify it
6 # under the terms of the GNU General Public License as published by the
7 # Free Software Foundation; either version 2 of the License, or (at your
8 # option) any later version.
9 #
10 # This program is distributed in the hope that it will be useful, but
11 # WITHOUT ANY WARRANTY; without even the implied warranty of
12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13 # General Public License for more details.
14 #
15 # You should have received a copy of the GNU General Public License along
16 # with this program; if not, write to the Free Software Foundation, Inc.,
17 # 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA
19 import BaseHTTPServer
20 import ConfigParser
21 import SocketServer
22 import base64
23 import libxml2
24 import mimetools
25 import os
26 import random
27 import shlex
28 import signal
29 import socket
30 import string
31 import subprocess
32 import sys
33 import time
34 import urllib2
36 def debug(level, verb, string):
37         if level <= verb:
38                 print "%s: %s" % (level, string)
41 ##############################################################################
43 class ExecTimeoutError(Exception):
44         pass
46 ##############################################################################
48 def available_encodings():
49         return ['base64', 'plain',]
52 def check_encoding(enc):
53         if enc in available_encodings():
54                 return True
55         else:
56                 return False
59 def decode(data, encoding):
60         if encoding == 'plain':
61                 return data
62         else:
63                 return base64.b64decode(data)
66 def encode(data, encoding=None):
67         if encoding == 'plain':
68                 return data
69         else:
70                 return base64.b64encode(data)
73 ##############################################################################
75 def read_inifile(inifile):
76         config = ConfigParser.RawConfigParser()
77         config.optionxform = str # We need case-sensitive options
78         ini_list = config.read(inifile)
80         if ini_list:
81                 return config
82         else:
83                 return False
86 ##############################################################################
88 def exec_timeout_handler(signum, frame):
89         raise ExecTimeoutError
91 def exec_check(host_name, service_descr, cmdline, cmdprefix='', timeout=None, timeout_returncode=2):
92         cmdarray = shlex.split(cmdline)
94         check = {}
95         check['host_name'] = host_name
96         check['service_description'] = service_descr
98         if len(cmdarray) == 0:
99                 check['output'] = 'No command line specified!'
100                 check['returncode'] = 127
101                 return check
103         check['commandline'] = cmdline
104         check['command'] = cmdarray[0].split(os.path.sep)[-1]
106         if cmdprefix:
107                 check['fullcommandline'] = cmdprefix + ' ' + cmdline
108                 cmdarray = shlex.split(cmdprefix) + cmdarray
109         else:
110                 check['fullcommandline'] = cmdline
112         if timeout:
113                 signal.signal(signal.SIGALRM, exec_timeout_handler)
114                 signal.alarm(timeout)
116         try:
117                 cmd = subprocess.Popen(cmdarray, stdout=subprocess.PIPE)
118                 check['output'] = cmd.communicate()[0].rstrip()
119                 check['returncode'] = cmd.returncode
120         except OSError:
121                 check['output'] = 'Could not execute "%s"' % cmdline
122                 check['returncode'] = 127
123         except ExecTimeoutError:
124                 check['output'] = 'Plugin timed out after %s seconds' % timeout
125                 check['returncode'] = timeout_returncode
127         if timeout:
128                 signal.alarm(0)
129                 try:
130                         if sys.version_info >= (2, 6):
131                                 cmd.terminate()
132                         else:
133                                 os.kill(cmd.pid, 15)
134                 except OSError:
135                         pass
137         check['timestamp'] = str(long(time.time()))
138         return check
141 ##############################################################################
143 def conf2dict(config, opt_host=None, opt_service=None):
144         checks = []
146         # Read "plugin_timeout" from "[nagixsc]", default "None" (no timeout)
147         try:
148                 timeout = config.getint('nagixsc','plugin_timeout')
149         except (ConfigParser.NoSectionError, ConfigParser.NoOptionError):
150                 timeout = None
152         # Read "plugin_timeout_returncode" from "[nagixsc]", default "2" (CRITICAL)
153         try:
154                 timeout_returncode = config.getint('nagixsc','plugin_timeout_returncode')
155         except (ConfigParser.NoSectionError, ConfigParser.NoOptionError):
156                 timeout_returncode = 2
158         # Read "add_pnp4nagios_template_hint" from "[nagixsc]", default "False"
159         try:
160                 add_pnp4nagios_template_hint = config.getboolean('nagixsc','add_pnp4nagios_template_hint')
161         except (ConfigParser.NoSectionError, ConfigParser.NoOptionError):
162                 add_pnp4nagios_template_hint = False
164         # Read "command_prefix" from "[nagixsc]", default "" (empty string)
165         try:
166                 cmdprefix_conffile = config.get('nagixsc','command_prefix')
167         except (ConfigParser.NoSectionError, ConfigParser.NoOptionError):
168                 cmdprefix_conffile = ''
170         # Sections are Hosts (not 'nagixsc'), options in sections are Services
171         hosts = config.sections()
172         if 'nagixsc' in hosts:
173                 hosts.remove('nagixsc')
175         # Filter out host/section if it exists
176         if opt_host:
177                 if opt_host in hosts:
178                         hosts = [opt_host,]
179                 else:
180                         hosts = []
182         for host in hosts:
183                 # Overwrite section/host name with '_host_name'
184                 if config.has_option(host,'_host_name'):
185                         host_name = config.get(host,'_host_name')
186                 else:
187                         host_name = host
190                 services = config.options(host)
191                 # Look for host/section specific "command_prefix"
192                 if '_command_prefix' in services:
193                         cmdprefix = config.get(host, '_command_prefix')
194                 else:
195                         cmdprefix = cmdprefix_conffile
197                 # Look for host check
198                 if '_host_check' in services and not opt_service:
199                         cmdline = config.get(host, '_host_check')
200                         check = exec_check(host_name, None, cmdline, cmdprefix, timeout, timeout_returncode)
201                         if add_pnp4nagios_template_hint and '|' in check['output']:
202                                 check['output'] += ' [%s]' % check['command']
203                         checks.append(check)
206                 # Filter out service if given in cmd line options
207                 if opt_service:
208                         if opt_service in services:
209                                 services = [opt_service,]
210                         else:
211                                 services = []
213                 for service in services:
214                         # If option starts with '_' it may be a NagixSC option in the future
215                         if service[0] != '_':
216                                 cmdline = config.get(host, service)
218                                 check = exec_check(host_name, service, cmdline, cmdprefix, timeout, timeout_returncode)
219                                 if add_pnp4nagios_template_hint and '|' in check['output']:
220                                         check['output'] += ' [%s]' % check['command']
221                                 checks.append(check)
223         return checks
226 ##############################################################################
228 def dict2out_passive(checks, xmltimestamp, opt_pipe, opt_verb=0):
229         FORMAT_HOST = '[%s] PROCESS_HOST_CHECK_RESULT;%s;%s;%s'
230         FORMAT_SERVICE = '[%s] PROCESS_SERVICE_CHECK_RESULT;%s;%s;%s;%s'
231         count_services = 0
232         now = str(long(time.time()))
234         # Prepare
235         if opt_verb <= 2:
236                 pipe = open(opt_pipe, "w")
237         else:
238                 pipe = None
240         # Output
241         for check in checks:
242                 count_services += 1
243                 if check.has_key('timestamp'):
244                         timestamp = check['timestamp']
245                 else:
246                         timestamp = xmltimestamp
248                 if check['service_description'] == None or check['service_description'] == '':
249                         # Host check
250                         line = FORMAT_HOST % (timestamp, check['host_name'], check['returncode'], check['output'].replace('\n', '\\n'))
251                 else:
252                         # Service check
253                         line =  FORMAT_SERVICE % (timestamp, check['host_name'], check['service_description'], check['returncode'], check['output'].replace('\n', '\\n'))
255                 if pipe:
256                         pipe.write(line + '\n')
257                 debug(2, opt_verb, line)
259         # Close
260         if pipe:
261                 pipe.close()
262         else:
263                 print "Passive check results NOT written to Nagios pipe due to -vvv!"
265         return count_services
268 def dict2out_checkresult(checks, xmltimestamp, opt_checkresultdir, opt_verb=0):
269         count_services = 0
270         count_failed = 0
271         list_failed = []
272         chars = string.letters + string.digits
273         ctimestamp = time.ctime()
274         random.seed()
276         for check in checks:
277                 count_services += 1
278                 if check.has_key('timestamp'):
279                         timestamp = check['timestamp']
280                 else:
281                         timestamp = xmltimestamp
283                 filename = os.path.join(opt_checkresultdir, 'c' + ''.join([random.choice(chars) for i in range(6)]))
284                 try:
285                         crfile = open(filename, "w")
286                         if check['service_description'] == None or check['service_description'] == '':
287                                 # Host check
288                                 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') ) )
289                         else:
290                                 # Service check
291                                 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') ) )
292                         crfile.close()
294                         # Create OK file
295                         open(filename + '.ok', 'w').close()
296                 except:
297                         count_failed += 1
298                         list_failed.append([filename, check['host_name'], check['service_description']])
300         return (count_services, count_failed, list_failed)
303 ##############################################################################
305 def read_xml(options):
306         if options.url != None:
308                 if options.httpuser and options.httppasswd:
309                         passman = urllib2.HTTPPasswordMgrWithDefaultRealm()
310                         passman.add_password(None, options.url, options.httpuser, options.httppasswd)
311                         authhandler = urllib2.HTTPBasicAuthHandler(passman)
312                         opener = urllib2.build_opener(authhandler)
313                         urllib2.install_opener(opener)
315                 try:
316                         response = urllib2.urlopen(options.url)
317                 except urllib2.HTTPError, error:
318                         print error
319                         sys.exit(0)
320                 except urllib2.URLError, error:
321                         print error.reason[1]
322                         sys.exit(0)
324                 doc = libxml2.parseDoc(response.read())
325                 response.close()
327         else:
328                 doc = libxml2.parseFile(options.file)
330         return doc
333 def read_xml_from_string(content):
334         return libxml2.parseDoc(content)
337 def write_xml(xmldoc, outfile, httpuser=None, httppasswd=None):
338         if outfile.startswith('http'):
339                 (headers, body) = encode_multipart(xmldoc, httpuser, httppasswd)
340                 response = urllib2.urlopen(urllib2.Request(outfile, body, headers)).read()
341                 return response
343         elif outfile == '-':
344                 xmldoc.saveFormatFile('-', format=1)
345                 return None
347         else:
348                 xmldoc.saveFile(outfile)
349                 return None
352 def write_xml_or_die(xmldoc, outfile, httpuser=None, httppasswd=None):
353         try:
354                 response = write_xml(xmldoc, outfile, httpuser, httppasswd)
355         except urllib2.HTTPError, error:
356                 print error
357                 sys.exit(11)
358         except urllib2.URLError, error:
359                 print error.reason[1]
360                 sys.exit(12)
362         return response
365 ##############################################################################
367 def xml_check_version(xmldoc):
368         # FIXME: Check XML structure
369         try:
370                 xmlnagixsc = xmldoc.xpathNewContext().xpathEval('/nagixsc')[0]
371         except:
372                 return (False, 'Not a Nag(IX)SC XML file!')
374         try:
375                 if xmlnagixsc.prop('version') != "1.0":
376                         return (False, 'Wrong version (found "%s", need "1.0") of XML file!' % xmlnagixsc.prop('version'))
377         except:
378                 return (False, 'No version information found in XML file!')
380         return (True, 'XML seems to be ok')
383 def xml_get_timestamp(xmldoc):
384         try:
385                 timestamp = int(xmldoc.xpathNewContext().xpathEval('/nagixsc/timestamp')[0].get_content())
386         except:
387                 return False
389         return timestamp
392 def xml_to_dict(xmldoc, verb=0, hostfilter=None, servicefilter=None):
393         checks = []
394         now = long(time.time())
395         filetimestamp = reset_future_timestamp(xml_get_timestamp(xmldoc), now)
397         if hostfilter:
398                 hosts = xmldoc.xpathNewContext().xpathEval('/nagixsc/host[name="%s"] | /nagixsc/host[name="%s"]' % (hostfilter, encode(hostfilter)))
399         else:
400                 hosts = xmldoc.xpathNewContext().xpathEval('/nagixsc/host')
402         for host in hosts:
403                 xmlhostname = host.xpathEval('name')[0]
404                 hostname = decode(xmlhostname.get_content(), xmlhostname.prop('encoding'))
405                 debug(2, verb, 'Found host "%s"' % hostname)
407                 # Look for Host check result
408                 if host.xpathEval('returncode'):
409                         retcode   = host.xpathEval('returncode')[0].get_content()
410                 else:
411                         retcode   = None
413                 if host.xpathEval('output'):
414                         xmloutput = host.xpathEval('output')[0]
415                         output    = decode(xmloutput.get_content(), xmloutput.prop('encoding')).rstrip()
416                 else:
417                         output    = None
419                 if host.xpathEval('timestamp'):
420                         timestamp = reset_future_timestamp(int(host.xpathEval('timestamp')[0].get_content()), now)
421                 else:
422                         timestamp = filetimestamp
424                 # Append only if no service filter
425                 if not servicefilter and retcode and output:
426                         checks.append({'host_name':hostname, 'service_description':None, 'returncode':retcode, 'output':output, 'timestamp':timestamp})
429                 # Look for service filter
430                 if servicefilter:
431                         services = host.xpathEval('service[description="%s"] | service[description="%s"]' % (servicefilter, encode(servicefilter)))
432                 else:
433                         services = host.xpathEval('service')
435                 # Loop over services in host
436                 for service in services:
437                         service_dict = {}
439                         xmldescr  = service.xpathEval('description')[0]
440                         xmloutput = service.xpathEval('output')[0]
442                         srvdescr = decode(xmldescr.get_content(), xmldescr.prop('encoding'))
443                         retcode  = service.xpathEval('returncode')[0].get_content()
444                         output   = decode(xmloutput.get_content(), xmloutput.prop('encoding')).rstrip()
446                         try:
447                                 timestamp = reset_future_timestamp(int(service.xpathEval('timestamp')[0].get_content()), now)
448                         except:
449                                 timestamp = filetimestamp
451                         debug(2, verb, 'Found service "%s"' % srvdescr)
453                         service_dict = {'host_name':hostname, 'service_description':srvdescr, 'returncode':retcode, 'output':output, 'timestamp':timestamp}
454                         checks.append(service_dict)
456                         debug(1, verb, 'Host: "%s" - Service: "%s" - RetCode: "%s" - Output: "%s"' % (hostname, srvdescr, retcode, output) )
458         return checks
461 def xml_from_dict(checks, encoding='base64'):
462         lasthost = None
464         db = [(check['host_name'], check) for check in checks]
465         db.sort()
467         xmldoc = libxml2.newDoc('1.0')
468         xmlroot = xmldoc.newChild(None, 'nagixsc', None)
469         xmlroot.setProp('version', '1.0')
470         xmltimestamp = xmlroot.newChild(None, 'timestamp', str(long(time.time())))
472         for entry in db:
473                 check = entry[1]
475                 if check['host_name'] != lasthost:
476                         xmlhost = xmlroot.newChild(None, 'host', None)
477                         xmlhostname = xmlhost.newChild(None, 'name', encode(check['host_name'], encoding))
478                         lasthost = check['host_name']
480                 if check['service_description'] == '' or check['service_description'] == None:
481                         # Host check result
482                         xmlreturncode = xmlhost.newChild(None, 'returncode', str(check['returncode']))
483                         xmloutput     = xmlhost.newChild(None, 'output', encode(check['output'], encoding))
484                         xmloutput.setProp('encoding', encoding)
485                         if check.has_key('timestamp'):
486                                 xmltimestamp  = xmlhost.newChild(None, 'timestamp', str(check['timestamp']))
487                 else:
488                         # Service check result
489                         xmlservice    = xmlhost.newChild(None, 'service', None)
490                         xmlname       = xmlservice.newChild(None, 'description', encode(check['service_description'], encoding))
491                         xmlname.setProp('encoding', encoding)
492                         xmlreturncode = xmlservice.newChild(None, 'returncode', str(check['returncode']))
493                         xmloutput     = xmlservice.newChild(None, 'output', encode(check['output'], encoding))
494                         xmloutput.setProp('encoding', encoding)
495                         if check.has_key('timestamp'):
496                                 xmltimestamp  = xmlservice.newChild(None, 'timestamp', str(check['timestamp']))
498         return xmldoc
501 def xml_merge(xmldocs):
502         checks = []
503         for xmldoc in xmldocs:
504                 checks.extend(xml_to_dict(xmldoc))
505         newxmldoc = xml_from_dict(checks)
506         return newxmldoc
509 def check_mark_outdated(check, now, maxtimediff, markold):
510         timedelta = now - check['timestamp']
511         if timedelta > maxtimediff:
512                 check['output'] = 'Nag(ix)SC: Check result is %s(>%s) seconds old - %s' % (timedelta, maxtimediff, check['output'])
513                 if markold:
514                         check['returncode'] = 3
515         return check
518 def reset_future_timestamp(timestamp, now):
519         if timestamp <= now:
520                 return timestamp
521         else:
522                 return now
524 ##############################################################################
526 def encode_multipart(xmldoc, httpuser=None, httppasswd=None):
527         BOUNDARY = mimetools.choose_boundary()
528         CRLF = '\r\n'
529         L = []
530         L.append('--' + BOUNDARY)
531         L.append('Content-Disposition: form-data; name="xmlfile"; filename="xmlfile"')
532         L.append('Content-Type: application/xml')
533         L.append('')
534         L.append(xmldoc.serialize())
535         L.append('--' + BOUNDARY + '--')
536         L.append('')
537         body = CRLF.join(L)
538         content_type = 'multipart/form-data; boundary=%s' % BOUNDARY
539         headers = {'Content-Type': content_type, 'Content-Length': str(len(body))}
541         if httpuser and httppasswd:
542                 headers['Authorization'] = 'Basic %s' % base64.b64encode(':'.join([httpuser, httppasswd]))
544         return (headers, body)
546 ##############################################################################
548 def daemonize(pidfile=None, stdin='/dev/null', stdout='/dev/null', stderr='/dev/null'):
549         # 1st fork
550         try:
551                 pid = os.fork()
552                 if pid > 0:
553                         sys.exit(0)
554         except OSError, e:
555                 sys.stderr.write("1st fork failed: (%d) %s\n" % (e.errno, e.strerror))
556                 sys.exit(1)
557         # Prepare 2nd fork
558         os.chdir("/")
559         os.umask(0)
560         os.setsid( )
561         # 2nd fork
562         try:
563                 pid = os.fork()
564                 if pid > 0:
565                         sys.exit(0)
566         except OSError, e:
567                 sys.stderr.write("2nd fork failed: (%d) %s\n" % (e.errno, e.strerror))
568                 sys.exit(1)
570         # Try to write PID file
571         if pidfile:
572                 pid = str(os.getpid())
573                 try:
574                         file(pidfile, 'w+').write('%s\n' % pid)
575                 except IOError:
576                         sys.stderr.write("Could not write PID file, exiting...\n")
577                         sys.exit(1)
579         # Redirect stdin, stdout, stderr
580         sys.stdout.flush()
581         sys.stderr.flush()
582         si = file(stdin, 'r')
583         so = file(stdout, 'a+')
584         se = file(stderr, 'a+', 0)
585         os.dup2(si.fileno(), sys.stdin.fileno())
586         os.dup2(so.fileno(), sys.stdout.fileno())
587         os.dup2(se.fileno(), sys.stderr.fileno())
589         return
591 ##############################################################################
593 if 'ForkingMixIn' in SocketServer.__dict__:
594         MixInClass = SocketServer.ForkingMixIn
595 else:
596         MixInClass = SocketServer.ThreadingMixIn
598 class MyHTTPServer(MixInClass, BaseHTTPServer.HTTPServer):
599         def __init__(self, server_address, HandlerClass, ssl=False, sslpemfile=None):
600                 SocketServer.BaseServer.__init__(self, server_address, HandlerClass)
602                 if ssl:
603                         try:
604                                 import ssl
605                                 self.socket = ssl.wrap_socket(socket.socket(self.address_family, self.socket_type), keyfile=sslpemfile, certfile=sslpemfile)
607                         except:
609                                 try:
610                                         from OpenSSL import SSL
611                                 except:
612                                         print 'No Python SSL or OpenSSL wrapper/bindings found!'
613                                         sys.exit(127)
615                                 context = SSL.Context(SSL.SSLv23_METHOD)
616                                 context.use_privatekey_file (sslpemfile)
617                                 context.use_certificate_file(sslpemfile)
618                                 self.socket = SSL.Connection(context, socket.socket(self.address_family, self.socket_type))
620                 else:
621                         self.socket = socket.socket(self.address_family, self.socket_type)
623                 self.server_bind()
624                 self.server_activate()
627 class MyHTTPRequestHandler(BaseHTTPServer.BaseHTTPRequestHandler):
628         def setup(self):
629                 self.connection = self.request
630                 self.rfile = socket._fileobject(self.request, "rb", self.rbufsize)
631                 self.wfile = socket._fileobject(self.request, "wb", self.wbufsize)
633 ##############################################################################
635 def prepare_socket(socket_path):
636         try:
637                 if socket_path.startswith('/'):
638                         s_family=socket.AF_UNIX
639                         s_sockaddr = socket_path
640                 elif socket_path.startswith('unix:'):
641                         s_family=socket.AF_UNIX
642                         s_sockaddr = socket_path[5:]
643                 elif socket_path.find(':') >= 0:
644                         s_port = socket_path.split(':')[-1]
645                         s_host = ':'.join(socket_path.split(':')[:-1])
646                         if s_host.startswith('[') and s_host.endswith(']'):
647                                 s_host = s_host[1:-1]
648                         (s_family, s_socktype, s_proto, s_canonname, s_sockaddr) = socket.getaddrinfo(s_host, s_port, 0, socket.SOCK_STREAM)[0]
649                 else:
650                         return None
651         except:
652                 return None
654         return (s_family, s_sockaddr)
657 def read_socket(s_opts, commands):
658         # print '%20s => %s %s' % (sock, s_family, s_sockaddr)
660         s = socket.socket(s_opts[0], socket.SOCK_STREAM)
661         s.connect(s_opts[1])
662         for line in commands:
663                 if not line.endswith('\n'):
664                         line += '\n'
665                 s.send(line)
666         s.shutdown(socket.SHUT_WR)
668         answer = ''
669         try:
670                 while True:
671                         s.settimeout(10)
672                         data = s.recv(32768)
673                         if data:
674                                 answer += data
675                         else:
676                                 break
677         except socket.timeout:
678                 return ''
680         return answer
683 def livestatus2dict(s_opts, host=None, service=None):
684         checks = []
686         # Get host information only if NO service specified
687         if not service:
688                 commands = []
689                 commands.append('GET hosts\n')
690                 commands.append('Columns: name state plugin_output long_plugin_output last_check\n')
691                 if host:
692                         commands.append('Filter: name = %s' % host)
693                 answer = read_socket(s_opts, commands)
695                 for line in answer.split('\n')[:-1]:
696                         line = line.split(';')
697                         checks.append({'host_name':line[0], 'service_description':None, 'returncode':line[1], 'output':'\n'.join([line[2], line[3]]).rstrip(), 'timestamp':str(line[4])})
699         # Get service information(s)
700         commands = []
701         commands.append('GET services\n')
702         commands.append('Columns: host_name description state plugin_output long_plugin_output last_check\n')
703         if host:
704                 commands.append('Filter: host_name = %s' % host)
705         if service:
706                 commands.append('Filter: description = %s' % service)
708         answer = read_socket(s_opts, commands)
710         for line in answer.split('\n')[:-1]:
711                 line = line.split(';')
712                 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])})
713                                 
715         return checks
716 ##############################################################################