| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440 |
- #!/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)
|