Code

Added COPYING (GPLv2) and copyright headers to all source files.
[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; only version 2 of the License is applicable.
8 #
9 # This program is distributed in the hope that it will be useful, but
10 # WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12 # General Public License for more details.
13 #
14 # You should have received a copy of the GNU General Public License along
15 # with this program; if not, write to the Free Software Foundation, Inc.,
16 # 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA
18 import BaseHTTPServer
19 import ConfigParser
20 import SocketServer
21 import base64
22 import libxml2
23 import mimetools
24 import os
25 import random
26 import shlex
27 import signal
28 import socket
29 import string
30 import subprocess
31 import sys
32 import time
33 import urllib2
35 def debug(level, verb, string):
36         if level <= verb:
37                 print "%s: %s" % (level, string)
40 ##############################################################################
42 class ExecTimeoutError(Exception):
43         pass
45 ##############################################################################
47 def available_encodings():
48         return ['base64', 'plain',]
51 def check_encoding(enc):
52         if enc in available_encodings():
53                 return True
54         else:
55                 return False
58 def decode(data, encoding):
59         if encoding == 'plain':
60                 return data
61         else:
62                 return base64.b64decode(data)
65 def encode(data, encoding=None):
66         if encoding == 'plain':
67                 return data
68         else:
69                 return base64.b64encode(data)
72 ##############################################################################
74 def read_inifile(inifile):
75         config = ConfigParser.RawConfigParser()
76         config.optionxform = str # We need case-sensitive options
77         ini_list = config.read(inifile)
79         if ini_list:
80                 return config
81         else:
82                 return False
85 ##############################################################################
87 def exec_timeout_handler(signum, frame):
88         raise ExecTimeoutError
90 def exec_check(host_name, service_descr, cmdline, cmdprefix='', timeout=None, timeout_returncode=2):
91         cmdarray = shlex.split(cmdline)
93         check = {}
94         check['host_name'] = host_name
95         check['service_description'] = service_descr
97         if len(cmdarray) == 0:
98                 check['output'] = 'No command line specified!'
99                 check['returncode'] = 127
100                 return check
102         check['commandline'] = cmdline
103         check['command'] = cmdarray[0].split(os.path.sep)[-1]
105         if cmdprefix:
106                 check['fullcommandline'] = cmdprefix + ' ' + cmdline
107                 cmdarray = shlex.split(cmdprefix) + cmdarray
108         else:
109                 check['fullcommandline'] = cmdline
111         if timeout:
112                 signal.signal(signal.SIGALRM, exec_timeout_handler)
113                 signal.alarm(timeout)
115         try:
116                 cmd = subprocess.Popen(cmdarray, stdout=subprocess.PIPE)
117                 check['output'] = cmd.communicate()[0].rstrip()
118                 check['returncode'] = cmd.returncode
119         except OSError:
120                 check['output'] = 'Could not execute "%s"' % cmdline
121                 check['returncode'] = 127
122         except ExecTimeoutError:
123                 check['output'] = 'Plugin timed out after %s seconds' % timeout
124                 check['returncode'] = timeout_returncode
126         if timeout:
127                 signal.alarm(0)
128                 try:
129                         if sys.version_info >= (2, 6):
130                                 cmd.terminate()
131                         else:
132                                 os.kill(cmd.pid, 15)
133                 except OSError:
134                         pass
136         check['timestamp'] = str(long(time.time()))
137         return check
140 ##############################################################################
142 def conf2dict(config, opt_host=None, opt_service=None):
143         checks = []
145         # Read "plugin_timeout" from "[nagixsc]", default "None" (no timeout)
146         try:
147                 timeout = config.getint('nagixsc','plugin_timeout')
148         except (ConfigParser.NoSectionError, ConfigParser.NoOptionError):
149                 timeout = None
151         # Read "plugin_timeout_returncode" from "[nagixsc]", default "2" (CRITICAL)
152         try:
153                 timeout_returncode = config.getint('nagixsc','plugin_timeout_returncode')
154         except (ConfigParser.NoSectionError, ConfigParser.NoOptionError):
155                 timeout_returncode = 2
157         # Read "add_pnp4nagios_template_hint" from "[nagixsc]", default "False"
158         try:
159                 add_pnp4nagios_template_hint = config.getboolean('nagixsc','add_pnp4nagios_template_hint')
160         except (ConfigParser.NoSectionError, ConfigParser.NoOptionError):
161                 add_pnp4nagios_template_hint = False
163         # Read "command_prefix" from "[nagixsc]", default "" (empty string)
164         try:
165                 cmdprefix_conffile = config.get('nagixsc','command_prefix')
166         except (ConfigParser.NoSectionError, ConfigParser.NoOptionError):
167                 cmdprefix_conffile = ''
169         # Sections are Hosts (not 'nagixsc'), options in sections are Services
170         hosts = config.sections()
171         if 'nagixsc' in hosts:
172                 hosts.remove('nagixsc')
174         # Filter out host/section if it exists
175         if opt_host:
176                 if opt_host in hosts:
177                         hosts = [opt_host,]
178                 else:
179                         hosts = []
181         for host in hosts:
182                 # Overwrite section/host name with '_host_name'
183                 if config.has_option(host,'_host_name'):
184                         host_name = config.get(host,'_host_name')
185                 else:
186                         host_name = host
189                 services = config.options(host)
190                 # Look for host/section specific "command_prefix"
191                 if '_command_prefix' in services:
192                         cmdprefix = config.get(host, '_command_prefix')
193                 else:
194                         cmdprefix = cmdprefix_conffile
196                 # Look for host check
197                 if '_host_check' in services and not opt_service:
198                         cmdline = config.get(host, '_host_check')
199                         check = exec_check(host_name, None, cmdline, cmdprefix, timeout, timeout_returncode)
200                         if add_pnp4nagios_template_hint and '|' in check['output']:
201                                 check['output'] += ' [%s]' % check['command']
202                         checks.append(check)
205                 # Filter out service if given in cmd line options
206                 if opt_service:
207                         if opt_service in services:
208                                 services = [opt_service,]
209                         else:
210                                 services = []
212                 for service in services:
213                         # If option starts with '_' it may be a NagixSC option in the future
214                         if service[0] != '_':
215                                 cmdline = config.get(host, service)
217                                 check = exec_check(host_name, service, cmdline, cmdprefix, timeout, timeout_returncode)
218                                 if add_pnp4nagios_template_hint and '|' in check['output']:
219                                         check['output'] += ' [%s]' % check['command']
220                                 checks.append(check)
222         return checks
225 ##############################################################################
227 def dict2out_passive(checks, xmltimestamp, opt_pipe, opt_verb=0):
228         FORMAT_HOST = '[%s] PROCESS_HOST_CHECK_RESULT;%s;%s;%s'
229         FORMAT_SERVICE = '[%s] PROCESS_SERVICE_CHECK_RESULT;%s;%s;%s;%s'
230         count_services = 0
231         now = str(long(time.time()))
233         # Prepare
234         if opt_verb <= 2:
235                 pipe = open(opt_pipe, "w")
236         else:
237                 pipe = None
239         # Output
240         for check in checks:
241                 count_services += 1
242                 if check.has_key('timestamp'):
243                         timestamp = check['timestamp']
244                 else:
245                         timestamp = xmltimestamp
247                 if check['service_description'] == None or check['service_description'] == '':
248                         # Host check
249                         line = FORMAT_HOST % (timestamp, check['host_name'], check['returncode'], check['output'].replace('\n', '\\n'))
250                 else:
251                         # Service check
252                         line =  FORMAT_SERVICE % (timestamp, check['host_name'], check['service_description'], check['returncode'], check['output'].replace('\n', '\\n'))
254                 if pipe:
255                         pipe.write(line + '\n')
256                 debug(2, opt_verb, line)
258         # Close
259         if pipe:
260                 pipe.close()
261         else:
262                 print "Passive check results NOT written to Nagios pipe due to -vvv!"
264         return count_services
267 def dict2out_checkresult(checks, xmltimestamp, opt_checkresultdir, opt_verb=0):
268         count_services = 0
269         count_failed = 0
270         list_failed = []
271         chars = string.letters + string.digits
272         ctimestamp = time.ctime()
273         random.seed()
275         for check in checks:
276                 count_services += 1
277                 if check.has_key('timestamp'):
278                         timestamp = check['timestamp']
279                 else:
280                         timestamp = xmltimestamp
282                 filename = os.path.join(opt_checkresultdir, 'c' + ''.join([random.choice(chars) for i in range(6)]))
283                 try:
284                         crfile = open(filename, "w")
285                         if check['service_description'] == None or check['service_description'] == '':
286                                 # Host check
287                                 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') ) )
288                         else:
289                                 # Service check
290                                 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') ) )
291                         crfile.close()
293                         # Create OK file
294                         open(filename + '.ok', 'w').close()
295                 except:
296                         count_failed += 1
297                         list_failed.append([filename, check['host_name'], check['service_description']])
299         return (count_services, count_failed, list_failed)
302 ##############################################################################
304 def read_xml(options):
305         if options.url != None:
307                 if options.httpuser and options.httppasswd:
308                         passman = urllib2.HTTPPasswordMgrWithDefaultRealm()
309                         passman.add_password(None, options.url, options.httpuser, options.httppasswd)
310                         authhandler = urllib2.HTTPBasicAuthHandler(passman)
311                         opener = urllib2.build_opener(authhandler)
312                         urllib2.install_opener(opener)
314                 try:
315                         response = urllib2.urlopen(options.url)
316                 except urllib2.HTTPError, error:
317                         print error
318                         sys.exit(0)
319                 except urllib2.URLError, error:
320                         print error.reason[1]
321                         sys.exit(0)
323                 doc = libxml2.parseDoc(response.read())
324                 response.close()
326         else:
327                 doc = libxml2.parseFile(options.file)
329         return doc
332 def read_xml_from_string(content):
333         return libxml2.parseDoc(content)
336 def write_xml(xmldoc, outfile, httpuser=None, httppasswd=None):
337         if outfile.startswith('http'):
338                 (headers, body) = encode_multipart(xmldoc, httpuser, httppasswd)
339                 response = urllib2.urlopen(urllib2.Request(outfile, body, headers)).read()
340                 return response
342         elif outfile == '-':
343                 xmldoc.saveFormatFile('-', format=1)
344                 return None
346         else:
347                 xmldoc.saveFile(outfile)
348                 return None
351 def write_xml_or_die(xmldoc, outfile, httpuser=None, httppasswd=None):
352         try:
353                 response = write_xml(xmldoc, outfile, httpuser, httppasswd)
354         except urllib2.HTTPError, error:
355                 print error
356                 sys.exit(11)
357         except urllib2.URLError, error:
358                 print error.reason[1]
359                 sys.exit(12)
361         return response
364 ##############################################################################
366 def xml_check_version(xmldoc):
367         # FIXME: Check XML structure
368         try:
369                 xmlnagixsc = xmldoc.xpathNewContext().xpathEval('/nagixsc')[0]
370         except:
371                 return (False, 'Not a Nag(IX)SC XML file!')
373         try:
374                 if xmlnagixsc.prop('version') != "1.0":
375                         return (False, 'Wrong version (found "%s", need "1.0") of XML file!' % xmlnagixsc.prop('version'))
376         except:
377                 return (False, 'No version information found in XML file!')
379         return (True, 'XML seems to be ok')
382 def xml_get_timestamp(xmldoc):
383         try:
384                 timestamp = int(xmldoc.xpathNewContext().xpathEval('/nagixsc/timestamp')[0].get_content())
385         except:
386                 return False
388         return timestamp
391 def xml_to_dict(xmldoc, verb=0, hostfilter=None, servicefilter=None):
392         checks = []
393         now = long(time.time())
394         filetimestamp = reset_future_timestamp(xml_get_timestamp(xmldoc), now)
396         if hostfilter:
397                 hosts = xmldoc.xpathNewContext().xpathEval('/nagixsc/host[name="%s"] | /nagixsc/host[name="%s"]' % (hostfilter, encode(hostfilter)))
398         else:
399                 hosts = xmldoc.xpathNewContext().xpathEval('/nagixsc/host')
401         for host in hosts:
402                 xmlhostname = host.xpathEval('name')[0]
403                 hostname = decode(xmlhostname.get_content(), xmlhostname.prop('encoding'))
404                 debug(2, verb, 'Found host "%s"' % hostname)
406                 # Look for Host check result
407                 if host.xpathEval('returncode'):
408                         retcode   = host.xpathEval('returncode')[0].get_content()
409                 else:
410                         retcode   = None
412                 if host.xpathEval('output'):
413                         xmloutput = host.xpathEval('output')[0]
414                         output    = decode(xmloutput.get_content(), xmloutput.prop('encoding')).rstrip()
415                 else:
416                         output    = None
418                 if host.xpathEval('timestamp'):
419                         timestamp = reset_future_timestamp(int(host.xpathEval('timestamp')[0].get_content()), now)
420                 else:
421                         timestamp = filetimestamp
423                 # Append only if no service filter
424                 if not servicefilter and retcode and output:
425                         checks.append({'host_name':hostname, 'service_description':None, 'returncode':retcode, 'output':output, 'timestamp':timestamp})
428                 # Look for service filter
429                 if servicefilter:
430                         services = host.xpathEval('service[description="%s"] | service[description="%s"]' % (servicefilter, encode(servicefilter)))
431                 else:
432                         services = host.xpathEval('service')
434                 # Loop over services in host
435                 for service in services:
436                         service_dict = {}
438                         xmldescr  = service.xpathEval('description')[0]
439                         xmloutput = service.xpathEval('output')[0]
441                         srvdescr = decode(xmldescr.get_content(), xmldescr.prop('encoding'))
442                         retcode  = service.xpathEval('returncode')[0].get_content()
443                         output   = decode(xmloutput.get_content(), xmloutput.prop('encoding')).rstrip()
445                         try:
446                                 timestamp = reset_future_timestamp(int(service.xpathEval('timestamp')[0].get_content()), now)
447                         except:
448                                 timestamp = filetimestamp
450                         debug(2, verb, 'Found service "%s"' % srvdescr)
452                         service_dict = {'host_name':hostname, 'service_description':srvdescr, 'returncode':retcode, 'output':output, 'timestamp':timestamp}
453                         checks.append(service_dict)
455                         debug(1, verb, 'Host: "%s" - Service: "%s" - RetCode: "%s" - Output: "%s"' % (hostname, srvdescr, retcode, output) )
457         return checks
460 def xml_from_dict(checks, encoding='base64'):
461         lasthost = None
463         db = [(check['host_name'], check) for check in checks]
464         db.sort()
466         xmldoc = libxml2.newDoc('1.0')
467         xmlroot = xmldoc.newChild(None, 'nagixsc', None)
468         xmlroot.setProp('version', '1.0')
469         xmltimestamp = xmlroot.newChild(None, 'timestamp', str(long(time.time())))
471         for entry in db:
472                 check = entry[1]
474                 if check['host_name'] != lasthost:
475                         xmlhost = xmlroot.newChild(None, 'host', None)
476                         xmlhostname = xmlhost.newChild(None, 'name', encode(check['host_name'], encoding))
477                         lasthost = check['host_name']
479                 if check['service_description'] == '' or check['service_description'] == None:
480                         # Host check result
481                         xmlreturncode = xmlhost.newChild(None, 'returncode', str(check['returncode']))
482                         xmloutput     = xmlhost.newChild(None, 'output', encode(check['output'], encoding))
483                         xmloutput.setProp('encoding', encoding)
484                         if check.has_key('timestamp'):
485                                 xmltimestamp  = xmlhost.newChild(None, 'timestamp', str(check['timestamp']))
486                 else:
487                         # Service check result
488                         xmlservice    = xmlhost.newChild(None, 'service', None)
489                         xmlname       = xmlservice.newChild(None, 'description', encode(check['service_description'], encoding))
490                         xmlname.setProp('encoding', encoding)
491                         xmlreturncode = xmlservice.newChild(None, 'returncode', str(check['returncode']))
492                         xmloutput     = xmlservice.newChild(None, 'output', encode(check['output'], encoding))
493                         xmloutput.setProp('encoding', encoding)
494                         if check.has_key('timestamp'):
495                                 xmltimestamp  = xmlservice.newChild(None, 'timestamp', str(check['timestamp']))
497         return xmldoc
500 def xml_merge(xmldocs):
501         checks = []
502         for xmldoc in xmldocs:
503                 checks.extend(xml_to_dict(xmldoc))
504         newxmldoc = xml_from_dict(checks)
505         return newxmldoc
508 def check_mark_outdated(check, now, maxtimediff, markold):
509         timedelta = now - check['timestamp']
510         if timedelta > maxtimediff:
511                 check['output'] = 'Nag(ix)SC: Check result is %s(>%s) seconds old - %s' % (timedelta, maxtimediff, check['output'])
512                 if markold:
513                         check['returncode'] = 3
514         return check
517 def reset_future_timestamp(timestamp, now):
518         if timestamp <= now:
519                 return timestamp
520         else:
521                 return now
523 ##############################################################################
525 def encode_multipart(xmldoc, httpuser=None, httppasswd=None):
526         BOUNDARY = mimetools.choose_boundary()
527         CRLF = '\r\n'
528         L = []
529         L.append('--' + BOUNDARY)
530         L.append('Content-Disposition: form-data; name="xmlfile"; filename="xmlfile"')
531         L.append('Content-Type: application/xml')
532         L.append('')
533         L.append(xmldoc.serialize())
534         L.append('--' + BOUNDARY + '--')
535         L.append('')
536         body = CRLF.join(L)
537         content_type = 'multipart/form-data; boundary=%s' % BOUNDARY
538         headers = {'Content-Type': content_type, 'Content-Length': str(len(body))}
540         if httpuser and httppasswd:
541                 headers['Authorization'] = 'Basic %s' % base64.b64encode(':'.join([httpuser, httppasswd]))
543         return (headers, body)
545 ##############################################################################
547 def daemonize(pidfile=None, stdin='/dev/null', stdout='/dev/null', stderr='/dev/null'):
548         # 1st fork
549         try:
550                 pid = os.fork()
551                 if pid > 0:
552                         sys.exit(0)
553         except OSError, e:
554                 sys.stderr.write("1st fork failed: (%d) %s\n" % (e.errno, e.strerror))
555                 sys.exit(1)
556         # Prepare 2nd fork
557         os.chdir("/")
558         os.umask(0)
559         os.setsid( )
560         # 2nd fork
561         try:
562                 pid = os.fork()
563                 if pid > 0:
564                         sys.exit(0)
565         except OSError, e:
566                 sys.stderr.write("2nd fork failed: (%d) %s\n" % (e.errno, e.strerror))
567                 sys.exit(1)
569         # Try to write PID file
570         if pidfile:
571                 pid = str(os.getpid())
572                 try:
573                         file(pidfile, 'w+').write('%s\n' % pid)
574                 except IOError:
575                         sys.stderr.write("Could not write PID file, exiting...\n")
576                         sys.exit(1)
578         # Redirect stdin, stdout, stderr
579         sys.stdout.flush()
580         sys.stderr.flush()
581         si = file(stdin, 'r')
582         so = file(stdout, 'a+')
583         se = file(stderr, 'a+', 0)
584         os.dup2(si.fileno(), sys.stdin.fileno())
585         os.dup2(so.fileno(), sys.stdout.fileno())
586         os.dup2(se.fileno(), sys.stderr.fileno())
588         return
590 ##############################################################################
592 if 'ForkingMixIn' in SocketServer.__dict__:
593         MixInClass = SocketServer.ForkingMixIn
594 else:
595         MixInClass = SocketServer.ThreadingMixIn
597 class MyHTTPServer(MixInClass, BaseHTTPServer.HTTPServer):
598         def __init__(self, server_address, HandlerClass, ssl=False, sslpemfile=None):
599                 SocketServer.BaseServer.__init__(self, server_address, HandlerClass)
601                 if ssl:
602                         try:
603                                 import ssl
604                                 self.socket = ssl.wrap_socket(socket.socket(self.address_family, self.socket_type), keyfile=sslpemfile, certfile=sslpemfile)
606                         except:
608                                 try:
609                                         from OpenSSL import SSL
610                                 except:
611                                         print 'No Python SSL or OpenSSL wrapper/bindings found!'
612                                         sys.exit(127)
614                                 context = SSL.Context(SSL.SSLv23_METHOD)
615                                 context.use_privatekey_file (sslpemfile)
616                                 context.use_certificate_file(sslpemfile)
617                                 self.socket = SSL.Connection(context, socket.socket(self.address_family, self.socket_type))
619                 else:
620                         self.socket = socket.socket(self.address_family, self.socket_type)
622                 self.server_bind()
623                 self.server_activate()
626 class MyHTTPRequestHandler(BaseHTTPServer.BaseHTTPRequestHandler):
627         def setup(self):
628                 self.connection = self.request
629                 self.rfile = socket._fileobject(self.request, "rb", self.rbufsize)
630                 self.wfile = socket._fileobject(self.request, "wb", self.wbufsize)
632 ##############################################################################
634 def prepare_socket(socket_path):
635         try:
636                 if socket_path.startswith('/'):
637                         s_family=socket.AF_UNIX
638                         s_sockaddr = socket_path
639                 elif socket_path.startswith('unix:'):
640                         s_family=socket.AF_UNIX
641                         s_sockaddr = socket_path[5:]
642                 elif socket_path.find(':') >= 0:
643                         s_port = socket_path.split(':')[-1]
644                         s_host = ':'.join(socket_path.split(':')[:-1])
645                         if s_host.startswith('[') and s_host.endswith(']'):
646                                 s_host = s_host[1:-1]
647                         (s_family, s_socktype, s_proto, s_canonname, s_sockaddr) = socket.getaddrinfo(s_host, s_port, 0, socket.SOCK_STREAM)[0]
648                 else:
649                         return None
650         except:
651                 return None
653         return (s_family, s_sockaddr)
656 def read_socket(s_opts, commands):
657         # print '%20s => %s %s' % (sock, s_family, s_sockaddr)
659         s = socket.socket(s_opts[0], socket.SOCK_STREAM)
660         s.connect(s_opts[1])
661         for line in commands:
662                 if not line.endswith('\n'):
663                         line += '\n'
664                 s.send(line)
665         s.shutdown(socket.SHUT_WR)
667         answer = ''
668         try:
669                 while True:
670                         s.settimeout(10)
671                         data = s.recv(32768)
672                         if data:
673                                 answer += data
674                         else:
675                                 break
676         except socket.timeout:
677                 return ''
679         return answer
682 def livestatus2dict(s_opts, host=None, service=None):
683         checks = []
685         # Get host information only if NO service specified
686         if not service:
687                 commands = []
688                 commands.append('GET hosts\n')
689                 commands.append('Columns: name state plugin_output long_plugin_output last_check\n')
690                 if host:
691                         commands.append('Filter: name = %s' % host)
692                 answer = read_socket(s_opts, commands)
694                 for line in answer.split('\n')[:-1]:
695                         line = line.split(';')
696                         checks.append({'host_name':line[0], 'service_description':None, 'returncode':line[1], 'output':'\n'.join([line[2], line[3]]).rstrip(), 'timestamp':str(line[4])})
698         # Get service information(s)
699         commands = []
700         commands.append('GET services\n')
701         commands.append('Columns: host_name description state plugin_output long_plugin_output last_check\n')
702         if host:
703                 commands.append('Filter: host_name = %s' % host)
704         if service:
705                 commands.append('Filter: description = %s' % service)
707         answer = read_socket(s_opts, commands)
709         for line in answer.split('\n')[:-1]:
710                 line = line.split(';')
711                 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])})
712                                 
714         return checks
715 ##############################################################################