2 # Change the above line if python is somewhere else
7 # Program: nmap plugin for Nagios
9 # Copyright (c) 2000 Jacob Lundqvist (jaclu@galdrion.com)
16 # Does a nmap scan, compares open ports to those given on command-line
17 # Reports warning for closed that should be open and error for
18 # open that should be closed.
19 # If optional ports are given, no warning is given if they are closed
20 # and they are included in the list of valid ports.
28 # 1.21 2004-07-23 rippeld@hillsboroughcounty.org Updated parsing of nmap output to correctly identify closed ports
29 # 1.20 2000-07-15 jaclu Updated params to correctly comply to plugin-standard
30 # moved support classes to utils.py
31 # 1.16 2000-07-14 jaclu made options and return codes more compatible with
32 # the plugin developer-guidelines
33 # 1.15 2000-07-14 jaclu added random string to temp-file name
34 # 1.14 2000-07-14 jaclu added check for error from subproc
35 # 1.10 2000-07-14 jaclu converted main part to class
36 # 1.08 2000-07-13 jaclu better param parsing
37 # 1.07 2000-07-13 jaclu changed nmap param to -P0
38 # 1.06 2000-07-13 jaclu make sure tmp file is deleted on errors
39 # 1.05 2000-07-12 jaclu in debug mode, show exit code
40 # 1.03 2000-07-12 jaclu error handling on nmap output
41 # 1.01 2000-07-12 jaclu added license
42 # 1.00 2000-07-12 jaclu implemented timeout handling
43 # 0.20 2000-07-10 jaclu Initial release
46 import sys
, os
, string
, whrandom
49 from getopt
import getopt
52 # import generic Nagios-plugin stuff
56 # Where temp files should be placed
57 tempfile
.tempdir
='/usr/local/nagios/var'
59 # Base name for tempfile
60 tempfile
.template
='check_nmap_tmp.'
62 # location and possibly params for nmap
63 nmap_cmd
='/usr/bin/nmap -P0'
71 # the class that does all the real work in this plugin...
76 # Retcodes, so we are compatible with nagios
84 def __init__(self
,cmd_line
=[]):
87 cmd_line: normaly sys.argv[1:] if called as standalone program
90 self
.host
='' # host to check
92 self
.debug
=0 # 1= show debug info
93 self
.ports
=[] # list of mandatory ports
94 self
.opt_ports
=[] # list of optional ports
95 self
.ranges
='' # port ranges for nmap
96 self
.exit_code
=0 # numerical exit-code
97 self
.exit_msg
='' # message to caller
99 self
.ParseCmdLine(cmd_line
)
102 """Actually run the process.
103 This method should be called exactly once.
107 # Only call check_host if cmd line was accepted earlier
109 if self
.exit_code
==0:
113 return self
.exit_code
,self
.exit_msg
116 return 'check_nmap %s' % _version_
118 #-----------------------------------------
120 # class internal stuff below...
122 #-----------------------------------------
127 def param2int_list(self
,s
):
128 lst
=string
.split(string
.replace(s
,',',' '))
130 for i
in range(len(lst
)):
136 def ParseCmdLine(self
,cmd_line
):
138 opt_list
=getopt(cmd_line
,'vH:ho:p:r:t:V',['debug','host=','help',
139 'optional=','port=','range=','timeout','version'])
140 for opt
in opt_list
[0]:
141 if opt
[0]=='-v' or opt
[0]=='--debug':
143 elif opt
[0]=='-H' or opt
[0]=='--host':
145 elif opt
[0]=='-h' or opt
[0]=='--help':
147 self
.exit_code
=1 # request termination
149 elif opt
[0]=='-o' or opt
[0]=='--optional':
150 self
.opt_ports
=self
.param2int_list(opt
[1])
151 elif opt
[0]=='-p' or opt
[0]=='--port':
152 self
.ports
=self
.param2int_list(opt
[1])
153 elif opt
[0]=='-r' or opt
[0]=='--range':
154 r
=string
.replace(opt
[1],':','-')
156 elif opt
[0]=='-t' or opt
[0]=='--timeout':
158 elif opt
[0]=='-V' or opt
[0]=='--version':
160 self
.exit_code
=1 # request termination
173 print 'host = %s' % self
.host
174 print 'timeout = %s' % self
.timeout
175 print 'ports = %s' % self
.ports
176 print 'optional ports = %s' % self
.opt_ports
177 print 'ranges = %s' % self
.ranges
181 # a option that wishes us to terminate now has been given...
183 # This way, you can test params in debug mode and see what this
184 # program recognised by suplying a version param at the end of
187 if self
.exit_code
<>0:
188 sys
.exit(self
.UNKNOWN
)
192 self
.exit_code
=self
.UNKNOWN
193 self
.exit_msg
='UNKNOWN: bad params, try running without any params for syntax'
197 'Check one host using nmap.'
199 # Create a tmp file for storing nmap output
201 # The tempfile module from python 1.5.2 is stupid
202 # two processes runing at aprox the same time gets
203 # the same tempfile...
204 # For this reason I use a random suffix for the tmp-file
205 # Still not 100% safe, but reduces the risk significally
206 # I also inserted checks at various places, so that
207 # _if_ two processes in deed get the same tmp-file
208 # the only result is a normal error message to nagios
210 r
=whrandom
.whrandom()
211 self
.tmp_file
=tempfile
.mktemp('.%s')%r.randint(0,100000)
213 print 'Tmpfile is: %s'%self
.tmp_file
215 # If a range is given, only run nmap on this range
218 global nmap_cmd
# needed, to avoid error on next line
219 # since we assigns to nmap_cmd :)
220 nmap_cmd
='%s -p %s' %(nmap_cmd
,self
.ranges
)
224 t
=utils
.Task('%s %s' %(nmap_cmd
,self
.host
))
226 # Configure a time-out handler
228 th
=utils
.TimeoutHandler(t
.Kill
, time_to_live
=self
.timeout
,
233 t
.Run(detach
=0, stdout
=self
.tmp_file
,stderr
='/dev/null')
235 # Wait for completition, error or timeout
237 nmap_exit_code
=t
.Wait(idlefunc
=th
.Check
, interval
=1)
242 self
.exit_code
=self
.CRITICAL
243 self
.exit_msg
='CRITICAL - Plugin timed out after %s seconds' % self
.timeout
246 # Check for exit status of subprocess
247 # Must do this after check for timeout, since the subprocess
248 # also returns error if aborted.
250 if nmap_exit_code
<> 0:
251 self
.exit_code
=self
.UNKNOWN
252 self
.exit_msg
='nmap program failed with code %s' % nmap_exit_code
258 f
= open(self
.tmp_file
, 'r')
262 self
.exit_code
=self
.UNKNOWN
263 self
.exit_msg
='Unable to get output from nmap'
267 # Store open ports in list
268 # scans for lines where first word contains '/'
269 # and stores part before '/'
277 if string
.find(s
,'/')<1:
279 p
=string
.split(s
,'/')[0]
280 if string
.find(l
,'open')>1:
281 self
.active_ports
.append(int(p
))
283 # failure due to strange output...
287 print 'Ports found by nmap: ',self
.active_ports
289 # Filter out optional ports, we don't check status for them...
292 for p
in self
.opt_ports
:
293 self
.active_ports
.remove(p
)
295 if self
.debug
and len(self
.opt_ports
)>0:
296 print 'optional ports removed:',self
.active_ports
298 # under extreame loads the remove(p) above failed for me
299 # a few times, this exception hanlder handles
300 # this bug-alike situation...
303 opened
=self
.CheckOpen()
304 closed
=self
.CheckClosed()
307 self
.exit_code
=self
.CRITICAL
308 self
.exit_msg
='PORTS CRITICAL - Open:%s Closed:%s'%(opened
,closed
)
310 self
.exit_code
=self
.WARNING
311 self
.exit_msg
='PORTS WARNING - Closed:%s'%closed
313 self
.exit_code
=self
.OK
314 self
.exit_msg
='PORTS ok - Only defined ports open'
318 # Compares requested ports on with actually open ports
319 # returns all open that should be closed
323 for p
in self
.active_ports
:
324 if p
not in self
.ports
:
325 opened
='%s %s' %(opened
,p
)
329 # Compares requested ports with actually open ports
330 # returns all ports that are should be open
332 def CheckClosed(self
):
335 if p
not in self
.active_ports
:
336 closed
='%s %s' % (closed
,p
)
342 # If temp file exists, get rid of it
344 if self
.tmp_file
<>'' and os
.path
.isfile(self
.tmp_file
):
346 os
.remove(self
.tmp_file
)
348 # temp-file colition, some other process already
349 # removed the same file...
353 # Show numerical exits as string in debug mode
356 print 'Exitcode:',self
.exit_code
,
357 if self
.exit_code
==self
.UNKNOWN
:
359 elif self
.exit_code
==self
.OK
:
361 elif self
.exit_code
==self
.WARNING
:
363 elif self
.exit_code
==self
.CRITICAL
:
368 # Check if invalid exit code
370 if self
.exit_code
<-1 or self
.exit_code
>2:
371 self
.exit_msg
=self
.exit_msg
+' - undefined exit code (%s)' % self
.exit_code
372 self
.exit_code
=self
.UNKNOWN
383 check_nmap plugin for Nagios
384 Copyright (c) 2000 Jacob Lundqvist (jaclu@galdrion.com)
386 Version: %s""" % _version_
391 Usage: check_ports [-v|--debug] [-H|--host host] [-V|--version] [-h|--help]
392 [-o|--optional port1,port2,port3 ...] [-r|--range range]
393 [-p|--port port1,port2,port3 ...] [-t|--timeout timeout]"""
397 'Help is displayed if run without params.'
402 -h = help (this screen ;-)
403 -v = debug mode, show some extra output
404 -H host = host to check (name or IP#)
405 -o ports = optional ports that can be open (one or more),
406 no warning is given if optional port is closed
407 -p ports = ports that should be open (one or more)
408 -r range = port range to feed to nmap. Example: :1024,2049,3000:7000
409 -t timeout = timeout in seconds, default 10
412 This plugin attempts to verify open ports on the specified host.
414 If all specified ports are open, OK is returned.
415 If any of them are closed, WARNING is returned (except for optional ports)
416 If other ports are open, CRITICAL is returned
418 If possible, supply an IP address for the host address,
419 as this will bypass the DNS lookup.
426 if __name__
== '__main__':
428 if len (sys
.argv
) < 2:
430 # No params given, show syntax and exit
435 nmap
=CheckNmap(sys
.argv
[1:])
436 exit_code
,exit_msg
=nmap
.Run()
439 # Give Nagios a msg and a code