#!/usr/bin/env python # This Nagios plugin may be used to check the health of an RDP server, such # as a Windows hosts offering remote desktop. Typically, a "strange" RDP # response is a good indication of a Windows host is having trouble (while # it is still responding to ping). # It seems that the RDP protocol is based on a protocol called x224, # and this plugin only goes as far as checking very basic x224 # protocol operations. Hence, the somewhat strange name of the plugin. # Author: Troels Arvin <tra@sst.dk> # Versioning: # $Revision: 9734 $ # $Date: 2009-03-30 00:13:05 +0200 (Mon, 30 Mar 2009) $ # Copyright (c) 2009, Danish National Board of Health. # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are met: # * Redistributions of source code must retain the above copyright # notice, this list of conditions and the following disclaimer. # * Redistributions in binary form must reproduce the above copyright # notice, this list of conditions and the following disclaimer in the # documentation and/or other materials provided with the distribution. # * Neither the name of the the Danish National Board of Health nor the # names of its contributors may be used to endorse or promote products # derived from this software without specific prior written permission. # # THIS SOFTWARE IS PROVIDED BY the Danish National Board of Health ''AS IS'' AND ANY # EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE # DISCLAIMED. IN NO EVENT SHALL the Danish National Board of Health BE LIABLE FOR ANY # DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES # (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND # ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS # SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. default_rdp_port = 3389 default_warning_sec = 3 default_critical_sec = 50 def do_conn(hostname,port,setup_payload,teardown_payload): try: s = socket.socket() t1 = time.time() # connect s.connect((hostname,port)) sent_bytes = s.send(setup_payload) if sent_bytes != len(setup_payload): print "Could not send RDP setup payload" sys.exit(2) setup_received = s.recv(1024) t2 = time.time() # disconnect sent_bytes = s.send(teardown_payload) if sent_bytes != len(teardown_payload): print "x224 CRITICAL: Could not send RDP teardown payload" sys.exit(2) s.close() elapsed = t2 - t1 l_setup_received = len(setup_received) l_expected = 11 if l_setup_received < l_expected: print "x224 CRITICAL: RDP response too short (%d < %d)" % (l_setup_received,l_expected) sys.exit(2) if l_setup_received > l_expected: print "x224 UNKNOWN: RDP response longer than expected (%d > %d)" % (l_setup_received,l_expected) sys.exit(3) except socket.error, e: if e[0] == -2: print "x224 UNKNOWN: Could not resolve hostname '%s': %s" % (hostname,e) sys.exit(3) print 'x224 CRITICAL: Could not set up connection on port %d: %s' % (port,e) sys.exit(2) except Exception, e: print 'x224 CRITICAL: Problem communicating with RDP server: %s' % e #[1] sys.exit(2) return (elapsed,setup_received) # wrapping in gigantic try-block to be able to return 3 if something # unexpected goes wrong try: import os import sys import getopt import socket import struct import time this_script = os.path.basename(__file__) def usage(): print """Usage: %s [-h|--help] -H hostname [-p|--port port] [-w|--warning seconds] [-c|--critical seconds] port : tcp port to connect to; default: %d warning seconds : number of seconds that an RDP response may take without emitting a warning; default: %d critical seconds: number of seconds that an RDP response may take without emitting status=critical; default: %d""" % (this_script,default_rdp_port,default_warning_sec,default_critical_sec) sys.exit(3) try: options, args = getopt.getopt(sys.argv[1:], "h:w:c:H:p:", "--help --warning= --critical=", ) except getopt.GetoptError: usage() sys.exit(3) warning_sec = default_warning_sec critical_sec = default_critical_sec rdp_port = default_rdp_port hostname = '' for name, value in options: if name in ("-h", "--help"): usage() if name == '-H': hostname = value if name in ('-p', '--port'): try: rdp_port = int(value) except Exception: print "Unable to convert port to integer\n" usage() if name in ("-w", "--warning"): try: warning_sec = int(value) except Exception: print "Unable to convert warning_sec to integer\n" usage() if name in ("-c", "--critical"): try: critical_sec = int(value) except Exception: print "Unable to convert critical_sec to integer\n" usage() if rdp_port < 0: print "port number (%d) negative" % rdp_port usage() if hostname == '': print "Hostname (-H) not indicated" usage() if (warning_sec > critical_sec): print "warning seconds (%d) may not be greater than critical_seconds (%d)" % (warning_sec,critical_sec) usage() # make sure that we don't give up before critical sec has had a chance to elapse socket.setdefaulttimeout(critical_sec+2) setup_x224_content = "Cookie: mstshash=\r\n" setup_x224_header = struct.pack( '!BBHHB', len(setup_x224_content)+6, # length, 1 byte 224, # code, 1 byte 0, # dst-ref, 1 short 0, # src-ref, 1 short 0 # class, 1 byte ) setup_x224 = setup_x224_header + setup_x224_content setup_tpkt_header = struct.pack( '!BBH', 3, # version, 1 byte 0, # reserved, 1 byte len(setup_x224)+4 # len, 1 short ) setup_payload = setup_tpkt_header + setup_x224 teardown_payload = struct.pack( '!BBHBBBBBBB', 3, # tpkt version, 1 byte 0, # tpkt reserved, 1 byte 11, # tpkt len, 1 short 6, # x224 len, 1 byte 128, # x224 code, 1 byte 0, # x224 ?, 1 byte 0, # x224 ?, 1 byte 0, # x224 ?, 1 byte 0, # x224 ?, 1 byte 0 # x224 ?, 1 byte ) elapsed,rec = do_conn(hostname,rdp_port,setup_payload,teardown_payload) if elapsed > critical_sec: print "RDP connection setup time (%f) was longer than (%d) seconds" % (elapsed,critical_sec) sys.exit(2) if elapsed > warning_sec: print "RDP connection setup time (%f) was longer than (%d) seconds" % (elapsed,warning_sec) sys.exit(1) rec_tpkt_header={} rec_x224_header={} rec_tpkt_header['version'], \ rec_tpkt_header['reserved'], \ rec_tpkt_header['length'], \ \ rec_x224_header['length'], \ rec_x224_header['code'], \ rec_x224_header['dst_ref'], \ rec_x224_header['src_ref'], \ rec_x224_header['class'] \ = struct.unpack('!BBHBBHHB',rec) # maybe, TODO: insert some analysis of the decoded response except struct.error, e: print "x224 CRITICAL: Could not decode RDP response: %s" % e sys.exit(2) except SystemExit, e: # Special case which is needed in order to convert the return code # from other exception handlers. sys.exit(int(str(e))) except: # At this point, we don't know what's going on, so let's # not output the details of the error into something which # would appear in the Nagios web interface. print "x224 UNKNOWN: An unhandled error occurred" sys.stderr.write('Unhandled error: %s' % sys.exc_info()[1]) sys.exit(3) print "x224 OK. Connection setup time: %f sec.|time=%fs;%d;%d;0" % (elapsed,elapsed,warning_sec,critical_sec) sys.exit(0)