diff options
Diffstat (limited to 'contrib/check_nmap.py')
-rw-r--r-- | contrib/check_nmap.py | 440 |
1 files changed, 440 insertions, 0 deletions
diff --git a/contrib/check_nmap.py b/contrib/check_nmap.py new file mode 100644 index 00000000..4f53406d --- /dev/null +++ b/contrib/check_nmap.py @@ -0,0 +1,440 @@ +#!/usr/bin/python +# Change the above line if python is somewhere else + +# +# check_nmap +# +# Program: nmap plugin for Nagios +# License: GPL +# Copyright (c) 2000 Jacob Lundqvist (jaclu@galdrion.com) +# +_version_ = '1.20' +# +# +# Description: +# +# Does a nmap scan, compares open ports to those given on command-line +# Reports warning for closed that should be open and error for +# open that should be closed. +# If optional ports are given, no warning is given if they are closed +# and they are included in the list of valid ports. +# +# Requirements: +# python +# nmap +# +# History +# ------- +# 1.20 2000-07-15 jaclu Updated params to correctly comply to plugin-standard +# moved support classes to utils.py +# 1.16 2000-07-14 jaclu made options and return codes more compatible with +# the plugin developer-guidelines +# 1.15 2000-07-14 jaclu added random string to temp-file name +# 1.14 2000-07-14 jaclu added check for error from subproc +# 1.10 2000-07-14 jaclu converted main part to class +# 1.08 2000-07-13 jaclu better param parsing +# 1.07 2000-07-13 jaclu changed nmap param to -P0 +# 1.06 2000-07-13 jaclu make sure tmp file is deleted on errors +# 1.05 2000-07-12 jaclu in debug mode, show exit code +# 1.03 2000-07-12 jaclu error handling on nmap output +# 1.01 2000-07-12 jaclu added license +# 1.00 2000-07-12 jaclu implemented timeout handling +# 0.20 2000-07-10 jaclu Initial release + + +import sys, os, string, whrandom + +import tempfile +from getopt import getopt + +# +# import generic Nagios-plugin stuff +# +import utils + +# Where temp files should be placed +tempfile.tempdir='/usr/local/nagios/var' + +# Base name for tempfile +tempfile.template='check_nmap_tmp.' + +# location and possibly params for nmap +nmap_cmd='/usr/bin/nmap -P0' + + + + + + +# +# the class that does all the real work in this plugin... +# +# +class CheckNmap: + + # Retcodes, so we are compatible with nagios + #ERROR= -1 + UNKNOWN= -1 + OK= 0 + WARNING= 1 + CRITICAL= 2 + + + def __init__(self,cmd_line=[]): + """Constructor. + arguments: + cmd_line: normaly sys.argv[1:] if called as standalone program + """ + self.tmp_file='' + self.host='' # host to check + self.timeout=10 + self.debug=0 # 1= show debug info + self.ports=[] # list of mandatory ports + self.opt_ports=[] # list of optional ports + self.ranges='' # port ranges for nmap + self.exit_code=0 # numerical exit-code + self.exit_msg='' # message to caller + + self.ParseCmdLine(cmd_line) + + def Run(self): + """Actually run the process. + This method should be called exactly once. + """ + + # + # Only call check_host if cmd line was accepted earlier + # + if self.exit_code==0: + self.CheckHost() + + self.CleanUp() + return self.exit_code,self.exit_msg + + def Version(self): + return 'check_nmap %s' % _version_ + + #----------------------------------------- + # + # class internal stuff below... + # + #----------------------------------------- + + # + # Param checks + # + def param2int_list(self,s): + lst=string.split(string.replace(s,',',' ')) + try: + for i in range(len(lst)): + lst[i]=int(lst[i]) + except: + lst=[] + return lst + + def ParseCmdLine(self,cmd_line): + try: + opt_list=getopt(cmd_line,'vH:ho:p:r:t:V',['debug','host=','help', + 'optional=','port=','range=','timeout','version']) + for opt in opt_list[0]: + if opt[0]=='-v' or opt[0]=='--debug': + self.debug=1 + elif opt[0]=='-H' or opt[0]=='--host': + self.host=opt[1] + elif opt[0]=='-h' or opt[0]=='--help': + doc_help() + self.exit_code=1 # request termination + break + elif opt[0]=='-o' or opt[0]=='--optional': + self.opt_ports=self.param2int_list(opt[1]) + elif opt[0]=='-p' or opt[0]=='--port': + self.ports=self.param2int_list(opt[1]) + elif opt[0]=='-r' or opt[0]=='--range': + r=string.replace(opt[1],':','-') + self.ranges=r + elif opt[0]=='-t' or opt[0]=='--timeout': + self.timeout=opt[1] + elif opt[0]=='-V' or opt[0]=='--version': + print self.Version() + self.exit_code=1 # request termination + break + else: + self.host='' + break + + except: + # unknown param + self.host='' + + if self.debug: + print 'Params:' + print '-------' + print 'host = %s' % self.host + print 'timeout = %s' % self.timeout + print 'ports = %s' % self.ports + print 'optional ports = %s' % self.opt_ports + print 'ranges = %s' % self.ranges + print + + # + # a option that wishes us to terminate now has been given... + # + # This way, you can test params in debug mode and see what this + # program recognised by suplying a version param at the end of + # the cmd-line + # + if self.exit_code<>0: + sys.exit(self.UNKNOWN) + + if self.host=='': + doc_syntax() + self.exit_code=self.UNKNOWN + self.exit_msg='UNKNOWN: bad params, try running without any params for syntax' + + + def CheckHost(self): + 'Check one host using nmap.' + # + # Create a tmp file for storing nmap output + # + # The tempfile module from python 1.5.2 is stupid + # two processes runing at aprox the same time gets + # the same tempfile... + # For this reason I use a random suffix for the tmp-file + # Still not 100% safe, but reduces the risk significally + # I also inserted checks at various places, so that + # _if_ two processes in deed get the same tmp-file + # the only result is a normal error message to nagios + # + r=whrandom.whrandom() + self.tmp_file=tempfile.mktemp('.%s')%r.randint(0,100000) + if self.debug: + print 'Tmpfile is: %s'%self.tmp_file + # + # If a range is given, only run nmap on this range + # + if self.ranges<>'': + global nmap_cmd # needed, to avoid error on next line + # since we assigns to nmap_cmd :) + nmap_cmd='%s -p %s' %(nmap_cmd,self.ranges) + # + # Prepare a task + # + t=utils.Task('%s %s' %(nmap_cmd,self.host)) + # + # Configure a time-out handler + # + th=utils.TimeoutHandler(t.Kill, time_to_live=self.timeout, + debug=self.debug) + # + # Fork of nmap cmd + # + t.Run(detach=0, stdout=self.tmp_file,stderr='/dev/null') + # + # Wait for completition, error or timeout + # + nmap_exit_code=t.Wait(idlefunc=th.Check, interval=1) + # + # Check for timeout + # + if th.WasTimeOut(): + self.exit_code=self.CRITICAL + self.exit_msg='CRITICAL - Plugin timed out after %s seconds' % self.timeout + return + # + # Check for exit status of subprocess + # Must do this after check for timeout, since the subprocess + # also returns error if aborted. + # + if nmap_exit_code <> 0: + self.exit_code=self.UNKNOWN + self.exit_msg='nmap program failed with code %s' % nmap_exit_code + return + # + # Read output + # + try: + f = open(self.tmp_file, 'r') + output=f.readlines() + f.close() + except: + self.exit_code=self.UNKNOWN + self.exit_msg='Unable to get output from nmap' + return + + # + # Store open ports in list + # scans for lines where first word contains '/' + # and stores part before '/' + # + self.active_ports=[] + try: + for l in output: + if len(l)<2: + continue + s=string.split(l)[0] + if string.find(s,'/')<1: + continue + p=string.split(s,'/')[0] + self.active_ports.append(int(p)) + except: + # failure due to strange output... + pass + + if self.debug: + print 'Ports found by nmap: ',self.active_ports + # + # Filter out optional ports, we don't check status for them... + # + try: + for p in self.opt_ports: + self.active_ports.remove(p) + + if self.debug and len(self.opt_ports)>0: + print 'optional ports removed:',self.active_ports + except: + # under extreame loads the remove(p) above failed for me + # a few times, this exception hanlder handles + # this bug-alike situation... + pass + + opened=self.CheckOpen() + closed=self.CheckClosed() + + if opened <>'': + self.exit_code=self.CRITICAL + self.exit_msg='PORTS CRITICAL - Open:%s Closed:%s'%(opened,closed) + elif closed <>'': + self.exit_code=self.WARNING + self.exit_msg='PORTS WARNING - Closed:%s'%closed + else: + self.exit_code=self.OK + self.exit_msg='PORTS ok - Only defined ports open' + + + # + # Compares requested ports on with actually open ports + # returns all open that should be closed + # + def CheckOpen(self): + opened='' + for p in self.active_ports: + if p not in self.ports: + opened='%s %s' %(opened,p) + return opened + + # + # Compares requested ports with actually open ports + # returns all ports that are should be open + # + def CheckClosed(self): + closed='' + for p in self.ports: + if p not in self.active_ports: + closed='%s %s' % (closed,p) + return closed + + + def CleanUp(self): + # + # If temp file exists, get rid of it + # + if self.tmp_file<>'' and os.path.isfile(self.tmp_file): + try: + os.remove(self.tmp_file) + except: + # temp-file colition, some other process already + # removed the same file... + pass + + # + # Show numerical exits as string in debug mode + # + if self.debug: + print 'Exitcode:',self.exit_code, + if self.exit_code==self.UNKNOWN: + print 'UNKNOWN' + elif self.exit_code==self.OK: + print 'OK' + elif self.exit_code==self.WARNING: + print 'WARNING' + elif self.exit_code==self.CRITICAL: + print 'CRITICAL' + else: + print 'undefined' + # + # Check if invalid exit code + # + if self.exit_code<-1 or self.exit_code>2: + self.exit_msg=self.exit_msg+' - undefined exit code (%s)' % self.exit_code + self.exit_code=self.UNKNOWN + + + + + +# +# Help texts +# +def doc_head(): + print """ +check_nmap plugin for Nagios +Copyright (c) 2000 Jacob Lundqvist (jaclu@galdrion.com) +License: GPL +Version: %s""" % _version_ + + +def doc_syntax(): + print """ +Usage: check_ports [-v|--debug] [-H|--host host] [-V|--version] [-h|--help] + [-o|--optional port1,port2,port3 ...] [-r|--range range] + [-p|--port port1,port2,port3 ...] [-t|--timeout timeout]""" + + +def doc_help(): + 'Help is displayed if run without params.' + doc_head() + doc_syntax() + print """ +Options: + -h = help (this screen ;-) + -v = debug mode, show some extra output + -H host = host to check (name or IP#) + -o ports = optional ports that can be open (one or more), + no warning is given if optional port is closed + -p ports = ports that should be open (one or more) + -r range = port range to feed to nmap. Example: :1024,2049,3000:7000 + -t timeout = timeout in seconds, default 10 + -V = Version info + +This plugin attempts to verify open ports on the specified host. + +If all specified ports are open, OK is returned. +If any of them are closed, WARNING is returned (except for optional ports) +If other ports are open, CRITICAL is returned + +If possible, supply an IP address for the host address, +as this will bypass the DNS lookup. +""" + + +# +# Main +# +if __name__ == '__main__': + + if len (sys.argv) < 2: + # + # No params given, show syntax and exit + # + doc_syntax() + sys.exit(-1) + + nmap=CheckNmap(sys.argv[1:]) + exit_code,exit_msg=nmap.Run() + + # + # Give Nagios a msg and a code + # + print exit_msg + sys.exit(exit_code) |