Jelajahi Sumber

Added directory plugins-python containing three Python plugins

John C. Frickson 8 tahun lalu
induk
melakukan
f1710b882b

+ 47 - 0
plugins-python/Makefile.am

@@ -0,0 +1,47 @@
+## Process this file with automake to produce Makefile.in
+
+if RELEASE_PRESENT
+NP_VERSION = @NP_RELEASE@
+else
+NP-VERSION-FILE: .FORCE-NP-VERSION-FILE
+	@$(SHELL_PATH) $(top_srcdir)/NP-VERSION-GEN
+.FORCE-NP-VERSION-FILE:
+-include NP-VERSION-FILE
+endif
+
+SUFFIXES = .py .in
+
+VPATH=$(top_srcdir) $(top_srcdir)/plugins-python
+
+libexec_SCRIPTS = check_heartbleed.py  check_imap_login.py check_ncpa.py
+
+EXTRA_DIST=check_heartbleed.in  check_imap_login.in  check_ncpa.in
+
+EDIT = sed \
+  -e 's|[@]NP_VERSION[@]|$(NP_VERSION)|g' \
+  -e 's|[@]TRUSTED_PATH[@]|$(with_trusted_path)|g' \
+  -e 's|[@]PYTHON[@]|$(PYTHON)|g' \
+  -e 's|[@]libexecdir[@]|$(libexecdir)|g'
+
+#TESTS_ENVIRONMENT=perl -I $(top_builddir) -I $(top_srcdir)
+
+#TESTS = @SCRIPT_TEST@
+
+#test:
+#	perl -I $(top_builddir) -I $(top_srcdir) ../test.pl
+#	perl -I $(top_builddir) -I $(top_srcdir) ../test.pl t/utils.t	# utils.t is excluded from above, so manually ask to test
+
+#test-debug:
+#	NPTEST_DEBUG=1 HARNESS_VERBOSE=1 perl -I $(top_builddir) -I $(top_srcdir) ../test.pl
+#	NPTEST_DEBUG=1 HARNESS_VERBOSE=1 perl -I $(top_builddir) -I $(top_srcdir) ../test.pl t/utils.t	# utils.t is excluded from above, so manually ask to test
+
+CLEANFILES=$(libexec_SCRIPTS)
+
+.in.py:
+	@if [ ! "$(PYTHON)" = "missing" ]; then \
+	    $(EDIT) $< > $@; \
+	    chmod +x $@; \
+	fi
+
+clean-local:
+	rm -f NP-VERSION-FILE *.pyc

+ 331 - 0
plugins-python/check_heartbleed.in

@@ -0,0 +1,331 @@
+#!/usr/bin/python2
+
+# Check_Heartbleed.py v0.6
+# 18/4/2014
+
+# Quick and dirty demonstration of CVE-2014-0160 by Jared Stafford (jspenguin@jspenguin.org)
+# The author disclaims copyright to this source code.
+
+# Modified for simplified checking by Yonathan Klijnsma
+
+# Modified to turn into a Nagios Plugin by Scott Wilkerson (swilkerson@nagios.com)
+# Modified to include TLS v1.2, v1.1, v1.0, and SSLv3.0, defaults to 1.1 (sreinhardt@nagios.com)
+# 	Corrected Hello and Heartbeat packets to match versions
+#	Added optional verbose output
+#	Reimplemented output message and added Rich's idea for looping all supported versions
+# Suggested and implemented in another plugin looping of all versions by default (rich.brown@blueberryhillsoftware.com)
+
+import sys
+import struct
+import socket
+import time
+import select
+import re
+from optparse import OptionParser
+
+options = OptionParser(usage='%prog server [options]', description='Test for SSL heartbeat vulnerability (CVE-2014-0160)')
+options.add_option('-H', '--host', type='string', default='127.0.0.1', help='Host to connect to (default: 127.0.0.1)')
+options.add_option('-p', '--port', type='int', default=443, help='TCP port to test (default: 443)')
+options.add_option('-v', '--version', type='int', default=-1, help='TLS or SSL version to test [TLSv1.0(0), TLSv1.1(1), TLSv1.2(2), or SSLv3.0(3)] (default: all)')
+options.add_option('-u', '--udp', default=False, action='store_true', help='Use TCP or UDP protocols, no arguments needed. This does not work presently, keep to TCP. (default: TCP)')
+options.add_option('-t', '--timeout', type='int', default=10, help='Plugin timeout length (default: 10)')
+options.add_option('-V', '--verbose', default=False, action='store_true', help='Print verbose output, including hexdumps of packets.')
+
+def h2bin(x):
+    return x.replace(' ', '').replace('\n', '').decode('hex')
+
+# Returns correct versioning for handshake and hb packets
+def tls_ver ():
+    global opts
+    if opts.version == 0:    #TLSv1.0
+        return '''03 01'''
+    elif opts.version == 2:    #TLSv1.2
+        return '''03 03'''
+    elif opts.version == 3:    #SSLv3.0
+        return '''03 00'''
+    else:                    #TLSv1.1
+        return '''03 02'''
+
+# Builds hello packet with correct tls version for rest of connection
+def build_hello():
+
+    hello = h2bin('''
+    16 ''' + tls_ver() + ''' 00  dc 01 00 00 d8 ''' + tls_ver() + ''' 53
+    4e d0 57 9d 9b 72 0b bc  0c bc 2b 92 a8 48 97 cf
+    bd 39 04 cc 16 0a 85 03  90 9f 77 04 33 d4 de 00
+    00 66 c0 14 c0 0a c0 22  c0 21 00 39 00 38 00 88
+    00 87 c0 0f c0 05 00 35  00 84 c0 12 c0 08 c0 1c
+    c0 1b 00 16 00 13 c0 0d  c0 03 00 0a c0 13 c0 09
+    c0 1f c0 1e 00 33 00 32  00 9a 00 99 00 45 00 44
+    c0 0e c0 04 00 2f 00 96  00 41 c0 11 c0 07 c0 0c
+    c0 02 00 05 00 04 00 15  00 12 00 09 00 14 00 11
+    00 08 00 06 00 03 00 ff  01 00 00 49 00 0b 00 04
+    03 00 01 02 00 0a 00 34  00 32 00 0e 00 0d 00 19
+    00 0b 00 0c 00 18 00 09  00 0a 00 16 00 17 00 08
+    00 06 00 07 00 14 00 15  00 04 00 05 00 12 00 13
+    00 01 00 02 00 03 00 0f  00 10 00 11 00 23 00 00
+    00 0f 00 01 01                                  
+    ''')
+
+##### Hello Packet Layout #####
+#	16													# initiate handshake
+#	+ tls_ver() +  										# version of tls to use
+#	00  dc 												# Length
+#	01 													# Handshake type (hello)
+#	00 00 d8 											# Length
+#	+ tls_ver() +  										# version of tls to use
+#	53 43 5b 90 										# timestamp (change?)
+#	9d 9b 72 0b bc  0c bc 2b 92 a8 48 97 cf				# random bytes (seriously!)
+#   bd 39 04 cc 16 0a 85 03  90 9f 77 04 33 d4 de 		# random bytes (seriously!)
+#	00													# Length of session id (start new session)
+#   00 66 												# Length of ciphers supported list
+#	c0 14 c0 0a c0 22  c0 21 00 39 00 38 00 88			# 2 byte list of supported ciphers
+#   00 87 c0 0f c0 05 00 35  00 84 c0 12 c0 08 c0 1c	# 2 byte list of supported ciphers cont
+#   c0 1b 00 16 00 13 c0 0d  c0 03 00 0a c0 13 c0 09	# 2 byte list of supported ciphers cont
+#   c0 1f c0 1e 00 33 00 32  00 9a 00 99 00 45 00 44	# 2 byte list of supported ciphers cont
+#   c0 0e c0 04 00 2f 00 96  00 41 c0 11 c0 07 c0 0c	# 2 byte list of supported ciphers cont
+#   c0 02 00 05 00 04 00 15  00 12 00 09 00 14 00 11	# 2 byte list of supported ciphers cont
+#   00 08 00 06 00 03 00 ff  							# 2 byte list of supported ciphers cont
+#	01 													# Length of compression methods
+#	00 													# Null compression (none)
+#	00 49												# Length of TLS extension list
+#	00 0b 00 04 03 00 01 02								# Elliptic curve point formats extension
+#	00 0a 00 34  00 32 00 0e 00 0d 00 19				# Elliptic curve
+#   00 0b 00 0c 00 18 00 09  00 0a 00 16 00 17 00 08	# Elliptic curve cont
+#   00 06 00 07 00 14 00 15  00 04 00 05 00 12 00 13	# Elliptic curve cont
+#   00 01 00 02 00 03 00 0f  00 10 00 11				# Elliptic curve cont
+#	00 23 00 00											# TLS sessions ticket supported
+#   00 0f 00 01 01										# Heartbeat extension
+##### End Hello Packet #####
+
+    return hello
+
+# Builds and returns heartbleed packet that matches with tls version
+def build_hb():
+
+    hb = h2bin('''
+    18 ''' + tls_ver() + ''' 00 03
+    01 40 00
+    ''')
+
+##### Heartbleed Packet Layout #####
+#   18													# TLS Record Type (heartbeat) 
+#	+ tls_ver() + 										# TLS version
+#	00 03												# Length
+#   01 													# Heartbeat request
+#	40 00												# Length (16384 bytes)
+##### End Heartbleed Packet #####
+    
+    return hb
+
+# Builds and sends hb packet with zero size
+def build_empty_hb():
+
+    hb = h2bin('''
+    18 ''' + tls_ver() + ''' 00 03
+    01 00 00
+    ''')
+
+##### Heartbleed Packet Layout #####
+#   18													# TLS Record Type (heartbeat) 
+#	+ tls_ver() + 										# TLS version
+#	00 03												# Length
+#   01 													# Heartbeat request
+#	40 00												# Length (16384 bytes)
+##### End Heartbleed Packet #####
+    
+    return hb
+
+# Receives data from socket for specified length
+def recvall(s, length):
+    global opts
+    endtime = time.time() + opts.timeout
+    rdata = ''
+    remain = length
+
+    while remain > 0:
+        rtime = endtime - time.time() 
+        if rtime < 0:
+            return None
+        r, w, e = select.select([s], [], [], 5)
+        if s in r:
+            try:
+                data = s.recv(remain)
+            except socket.error:
+                # Should this be OK, as the server has sent a rst most likely and is therefore likely patched?
+                print 'UNKNOWN: Server ' + opts.host + ' closed connection after sending heartbeat. Likely the server has been patched.'
+                sys.exit(3)
+            # EOF?
+            if not data:
+                return None
+            rdata += data
+            remain -= len(data)
+    return rdata
+        
+# Receives messages and handles accordingly
+def recvmsg(s):
+    global opts
+    hdr = recvall(s, 5)
+    if hdr is None:
+        return None, None, None
+    typ, ver, ln = struct.unpack('>BHH', hdr)
+    pay = recvall(s, ln)
+    if pay is None:
+        return None, None, None
+    if opts.verbose == True:
+        print ' ... received message: type = %d, ver = %04x, length = %d, pay = %02x' % (typ, ver, len(pay), ord(pay[0]))
+    return typ, ver, pay
+
+# Sends empty hb packet
+def hit_hb(s, hb):
+    global opts
+
+    if opts.verbose == True:
+       print 'Sending malformed heartbeat packet...'
+
+    try:
+        s.send(hb)
+    except socket.error:
+        print 'UNKNOWN: Error sending heartbeat to ' + opts.host
+        sys.exit(3)
+
+    while True:
+        typ, ver, pay = recvmsg(s)
+        if typ == None:
+            returncode = 0
+            break
+
+        if typ == 24:
+            if pay > 3:
+                returncode = 2    # vulnerable
+                break
+            else:
+                returncode = 0
+                break
+
+        if typ == 21:    # TLS mismatch, hopefully we don't find this
+            returncode = 0
+            break
+
+    #Outside of while
+    if returncode == 0: # Not vulnerable message
+        if opts.version == 3: #respond with ssl instead of tls
+            message = 'SSLv3.0 is not vulnerable. '
+        else:
+            message = 'TLSv1.' + str(opts.version) + ' is not vulnerable. '
+    else: # vulnerable message
+        if opts.version == 3: #respond with ssl instead of tls
+            message = 'SSLv3.0 is vulnerable. '
+        else:
+            message = 'TLSv1.' + str(opts.version) + ' is vulnerable. '
+
+    return returncode, message
+
+# Prints nagios style output and exit codes
+def print_output(exitcode, outputmessage):
+
+    if exitcode == 2:
+        print 'CRITICAL: Server ' + opts.host + ' ' + outputmessage
+    else:
+        print 'OK: Server ' + opts.host + ' ' + outputmessage
+
+    sys.exit(exitcode)
+
+# Outputs packets as hex, used for verbose output
+def hexdump(s):
+
+    for b in xrange(0, len(s), 16):
+        lin = [c for c in s[b : b + 16]]
+        hxdat = ' '.join('%02X' % ord(c) for c in lin)
+        pdat = ''
+        for c in lin:
+            if 32 <= ord(c) <= 126:
+                pdat += c
+            else:
+                pdat += '.'
+        print '  %04x: %-48s %s' % (b, hxdat, pdat)
+    print
+
+# Initiates connection and handles initial hello\hb sending
+def connect(hb):
+    global opts
+ 
+    if opts.udp == True:
+        s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
+    else:
+        s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+    
+    s.settimeout(opts.timeout)
+
+    try: 
+        s.connect((opts.host, opts.port))
+    except socket.error:
+        print 'UNKNOWN: Connecton to server ' + opts.host + ' could not be established.'
+        sys.exit(3)
+
+    hello = build_hello()
+
+    if opts.verbose == True:
+        print 'Sending hello packet...'
+
+    try:
+        s.send(hello)
+    except socket.error:
+        print 'UNKNOWN: Error sending hello to ' + opts.host
+        sys.exit(3)
+
+    while True:
+        typ, ver, pay = recvmsg(s)
+        if typ == None:
+            print 'UNKNOWN: Server ' + opts.host + ' closed connection without sending Server Hello.'
+            sys.exit(3)
+        # Look for server hello done message.
+        if typ == 22 and ord(pay[0]) == 0x0E:
+            if opts.verbose == True:
+                hexdump(pay)
+            break
+        else:
+            if opts.verbose == True:
+                hexdump(pay)
+            continue
+
+    if opts.verbose == True:
+        print 'Sending malformed heartbeat packet...'
+
+    try:
+        s.send(hb)
+    except socket.error:
+        print 'UNKNOWN: Error sending heartbeat to ' + opts.host
+        sys.exit(3)
+
+    return s
+
+def main():
+    global opts
+    opts, args = options.parse_args()
+    exitcode = 0
+    outputmessage = ''
+
+    if opts.version == -1: # no version was specified, loop.
+
+        if opts.verbose == True:
+            print 'Checking all supported TLS and SSL versions.'
+
+        for opts.version in [0, 1, 2, 3]:
+            hb = build_hb()
+            s = connect(hb)
+            returncode, message = hit_hb(s, hb)
+
+            if returncode > exitcode:
+                exitcode = returncode
+            outputmessage += message
+            
+    else: # version was specified
+        hb = build_hb()
+        s = connect(hb)
+        exitcode, outputmessage = hit_hb(s, hb)
+
+    print_output(exitcode, outputmessage)
+
+if __name__ == '__main__':
+    main()

+ 61 - 0
plugins-python/check_imap_login.in

@@ -0,0 +1,61 @@
+#!/usr/bin/python
+# vi:si:et:sw=4:sts=4:ts=4
+# -*- coding: UTF-8 -*-
+# -*- Mode: Python -*-
+#
+# Copyright (C) 2005 Bertera Pietro <pietro@bertera.it>
+
+# This file may be distributed and/or modified under the terms of
+# the GNU General Public License version 2 as published by
+# the Free Software Foundation.
+# This file is distributed without any warranty; without even the implied
+# warranty of merchantability or fitness for a particular purpose.
+# See "LICENSE.GPL" in the source distribution for more information.
+
+import sys, os, imaplib, getopt
+
+def usage():
+    print "-u <user>"
+    print "-p <password>"
+    print "-s use SSL"
+    print "-H <host>"
+
+def main():
+	try:
+	    opts, args = getopt.getopt(sys.argv[1:], "u:p:sH:")	
+    except getopt.GetoptError:
+        usage()
+        return 3
+    
+    user = host = password = use_ssl = None
+    
+    for o, a in opts:
+        if o == "-u":
+            user = a
+        elif o == "-p":
+            password = a
+        elif o == "-s":
+            use_ssl = True
+        elif o == "-H":
+            host = a  
+    if user == None or password == None or host == None:
+        usage()
+        return 1
+    
+    if use_ssl:
+        M = imaplib.IMAP4_SSL(host=host)
+    else:
+    	M = imaplib.IMAP4(host)
+    
+    try:	
+        M.login(user, password)
+	except Exception, e:
+        print "CRITICAL: IMAP Login not Successful: %s" % e
+        sys.exit(2)
+    
+    M.logout()
+    print "OK IMAP Login Successful"
+    return 0
+
+if __name__ == "__main__":
+        sys.exit(main())

+ 300 - 0
plugins-python/check_ncpa.in

@@ -0,0 +1,300 @@
+#!@PYTHON@
+"""
+SYNOPSIS
+
+
+"""
+import sys
+import optparse
+import traceback
+import ssl
+
+# Python 2/3 Compatibility imports
+
+try:
+    import json
+except ImportError:
+    import simplejson as json
+
+try:
+    import urllib.request
+    import urllib.parse
+    import urllib.error
+except ImportError:
+    import urllib2
+    import urllib
+
+try:
+    urlencode = urllib.parse.urlencode
+except AttributeError:
+    urlencode = urllib.urlencode
+
+try:
+    urlopen = urllib.request.urlopen
+except AttributeError:
+    urlopen = urllib2.urlopen
+
+try:
+    urlquote = urllib.parse.quote
+except AttributeError:
+    urlquote = urllib.quote
+
+import shlex
+import re
+import signal
+
+__VERSION__ = '1.1.0'
+
+def pretty(d, indent=0, indenter=' ' * 4):
+    info_str = ''
+    for key, value in list(d.items()):
+        info_str += indenter * indent + str(key)
+        if isinstance(value, dict):
+            info_str += '/\n'
+            info_str += pretty(value, indent + 1, indenter)
+        else:
+            info_str += ': ' + str(value) + '\n'
+    return info_str
+
+
+def parse_args():
+    version = 'check_ncpa.py, Version %s' % __VERSION__
+
+    parser = optparse.OptionParser()
+    parser.add_option("-H", "--hostname", help="The hostname to be connected to.")
+    parser.add_option("-M", "--metric", default='',
+                      help="The metric to check, this is defined on client "
+                           "system. This would also be the plugin name in the "
+                           "plugins directory. Do not attach arguments to it, "
+                           "use the -a directive for that. DO NOT INCLUDE the api/ "
+                           "instruction.")
+    parser.add_option("-P", "--port", default=5693, type="int",
+                      help="Port to use to connect to the client.")
+    parser.add_option("-w", "--warning", default=None, type="str",
+                      help="Warning value to be passed for the check.")
+    parser.add_option("-c", "--critical", default=None, type="str",
+                      help="Critical value to be passed for the check.")
+    parser.add_option("-u", "--units", default=None,
+                      help="The unit prefix (k, Ki, M, Mi, G, Gi, T, Ti) for b and B unit "
+                           "types which calculates the value returned.")
+    parser.add_option("-n", "--unit", default=None,
+                      help="Overrides the unit with whatever unit you define. "
+                           "Does not perform calculations. This changes the unit of measurement only.")
+    parser.add_option("-a", "--arguments", default=None,
+                      help="Arguments for the plugin to be run. Not necessary "
+                           "unless you're running a custom plugin. Given in the same "
+                           "as you would call from the command line. Example: -a '-w 10 -c 20 -f /usr/local'")
+    parser.add_option("-t", "--token", default='',
+                      help="The token for connecting.")
+    parser.add_option("-T", "--timeout", default=60, type="int",
+                      help="Enforced timeout, will terminate plugins after "
+                           "this amount of seconds. [%default]")
+    parser.add_option("-d", "--delta", action='store_true',
+                      help="Signals that this check is a delta check and a "
+                           "local state will kept.")
+    parser.add_option("-l", "--list", action='store_true',
+                      help="List all values under a given node. Do not perform "
+                           "a check.")
+    parser.add_option("-v", "--verbose", action='store_true',
+                      help='Print more verbose error messages.')
+    parser.add_option("-D", "--debug", action='store_true',
+                      help='Print LOTS of error messages. Used mostly for debugging.')
+    parser.add_option("-V", "--version", action='store_true',
+                      help='Print version number of plugin.')
+    parser.add_option("-q", "--queryargs", default=None,
+                      help='Extra query arguments to pass in the NCPA URL.')
+    parser.add_option("-s", "--secure", action='store_true', default=False,
+                      help='Require successful certificate verification. Does not work on Python < 2.7.9.')
+    parser.add_option("-p", "--performance", action='store_true', default=False,
+                      help='Print performance data even when there is none. '
+                           'Will print data matching the return code of this script')
+    options, _ = parser.parse_args()
+
+    if options.version:
+        print(version)
+        sys.exit(0)
+
+    if options.arguments and options.metric and not 'plugin' in options.metric:
+        parser.print_help()
+        parser.error('You cannot specify arguments without running a custom plugin.')
+
+    if not options.hostname:
+        parser.print_help()
+        parser.error("Hostname is required for use.")
+
+    elif not options.metric and not options.list:
+        parser.print_help()
+        parser.error('No metric given, if you want to list all possible items '
+                     'use --list.')
+
+    options.metric = re.sub(r'^/?(api/)?', '', options.metric)
+
+    return options
+
+
+# ~ The following are all helper functions. I would normally split these out into
+# ~ a new module but this needs to be portable.
+
+
+def get_url_from_options(options):
+    host_part = get_host_part_from_options(options)
+    arguments = get_arguments_from_options(options)
+    return '%s?%s' % (host_part, arguments)
+
+
+def get_host_part_from_options(options):
+    """Gets the address that will be queries for the JSON.
+
+    """
+    hostname = options.hostname
+    port = options.port
+
+    if not options.metric is None:
+        metric = urlquote(options.metric)
+    else:
+        metric = ''
+
+    arguments = get_check_arguments_from_options(options)
+    if not metric and not arguments:
+        api_address = 'https://%s:%d/api' % (hostname, port)
+    else:
+        api_address = 'https://%s:%d/api/%s/%s' % (hostname, port, metric, arguments)
+
+    return api_address
+
+
+def get_check_arguments_from_options(options):
+    """Gets the escaped URL for plugin arguments to be added
+    to the end of the host URL. This is different from the get_arguments_from_options
+    in that this is meant for the syntax when the user is calling a check, whereas the below
+    is when GET arguments need to be added.
+
+    """
+    arguments = options.arguments
+    if arguments is None:
+        return ''
+    else:
+        lex = shlex.shlex(arguments)
+        lex.whitespace_split = True
+        arguments = '/'.join([urlquote(x, safe='') for x in lex])
+        return arguments
+
+
+def get_arguments_from_options(options, **kwargs):
+    """Returns the http query arguments. If there is a list variable specified,
+    it will return the arguments necessary to query for a list.
+
+    """
+
+    # Note: Changed back to units due to the units being what is passed via the
+    # API call which can confuse people if they don't match
+    arguments = { 'token': options.token,
+                  'units': options.units }
+    
+    if not options.list:
+        arguments['warning'] = options.warning
+        arguments['critical'] = options.critical
+        arguments['delta'] = options.delta
+        arguments['check'] = 1
+        arguments['unit'] = options.unit
+
+    if options.queryargs:
+        for argument in options.queryargs.split(','):
+            key, value = argument.split('=')
+            arguments[key] = value
+
+    #~ Encode the items in the dictionary that are not None
+    return urlencode(dict((k, v) for k, v in list(arguments.items()) if v is not None))
+
+
+def get_json(options):
+    """Get the page given by the options. This will call down the url and
+    encode its finding into a Python object (from JSON).
+
+    """
+    url = get_url_from_options(options)
+
+    if options.verbose:
+        print('Connecting to: ' + url)
+
+    try:
+        ctx = ssl.create_default_context()
+        if not options.secure:
+            ctx.check_hostname = False
+            ctx.verify_mode = ssl.CERT_NONE
+        ret = urlopen(url, context=ctx)
+    except AttributeError:
+        ret = urlopen(url)
+
+    ret = ''.join(ret)
+
+    if options.verbose:
+        print('File returned contained:\n' + ret)
+
+    arr = json.loads(ret)
+
+    if 'value' in arr:
+        return arr['value']
+
+    return arr
+
+
+def run_check(info_json):
+    """Run a check against the remote host.
+
+    """
+    return info_json['stdout'], info_json['returncode']
+
+
+def show_list(info_json):
+    """Show the list of available options.
+
+    """
+    return pretty(info_json), 0
+
+
+def timeout_handler(threshold):
+    def wrapped(signum, frames):
+        stdout = "UNKNOWN: Execution exceeded timeout threshold of %ds" % threshold
+        print stdout
+        sys.exit(3)
+    return wrapped
+
+
+def main():
+    options = parse_args()
+
+    # We need to ensure that we will only execute for a certain amount of
+    # seconds.
+    signal.signal(signal.SIGALRM, timeout_handler(options.timeout))
+    signal.alarm(options.timeout)
+
+    try:
+
+        if options.version:
+            stdout = 'The version of this plugin is %s' % __VERSION__
+            return stdout, 0
+
+        info_json = get_json(options)
+        if options.list:
+            return show_list(info_json)
+        else:
+            stdout, returncode = run_check(info_json)
+            if options.performance and stdout.find("|") == -1:
+                performance = " | 'status'={};1;2;".format(returncode)
+                return "{}{}".format(stdout, performance), returncode
+            else:
+                return stdout, returncode
+    except Exception, e:
+        if options.debug:
+            return 'The stack trace:' + traceback.format_exc(), 3
+        elif options.verbose:
+            return 'An error occurred:' + str(e), 3
+        else:
+            return 'UNKNOWN: Error occurred while running the plugin. Use the verbose flag for more details.', 3
+
+
+if __name__ == "__main__":
+    stdout, returncode = main()
+    print(stdout)
+    sys.exit(returncode)