Whitespace changes only
[monitoring-plugins.git] / contrib / check_nmap.py
blob07f6d7fa845c733a9294c2a6e74543d7e19fad65
1 #!/usr/bin/python
2 # Change the above line if python is somewhere else
5 # check_nmap
6 #
7 # Program: nmap plugin for Nagios
8 # License: GPL
9 # Copyright (c) 2000 Jacob Lundqvist (jaclu@galdrion.com)
11 _version_ = '1.21'
14 # Description:
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.
22 # Requirements:
23 # python
24 # nmap
26 # History
27 # -------
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
48 import tempfile
49 from getopt import getopt
52 # import generic Nagios-plugin stuff
54 import utils
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...
74 class CheckNmap:
76 # Retcodes, so we are compatible with nagios
77 #ERROR= -1
78 UNKNOWN= -1
79 OK= 0
80 WARNING= 1
81 CRITICAL= 2
84 def __init__(self,cmd_line=[]):
85 """Constructor.
86 arguments:
87 cmd_line: normaly sys.argv[1:] if called as standalone program
88 """
89 self.tmp_file=''
90 self.host='' # host to check
91 self.timeout=10
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)
101 def Run(self):
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:
110 self.CheckHost()
112 self.CleanUp()
113 return self.exit_code,self.exit_msg
115 def Version(self):
116 return 'check_nmap %s' % _version_
118 #-----------------------------------------
120 # class internal stuff below...
122 #-----------------------------------------
125 # Param checks
127 def param2int_list(self,s):
128 lst=string.split(string.replace(s,',',' '))
129 try:
130 for i in range(len(lst)):
131 lst[i]=int(lst[i])
132 except:
133 lst=[]
134 return lst
136 def ParseCmdLine(self,cmd_line):
137 try:
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':
142 self.debug=1
143 elif opt[0]=='-H' or opt[0]=='--host':
144 self.host=opt[1]
145 elif opt[0]=='-h' or opt[0]=='--help':
146 doc_help()
147 self.exit_code=1 # request termination
148 break
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],':','-')
155 self.ranges=r
156 elif opt[0]=='-t' or opt[0]=='--timeout':
157 self.timeout=opt[1]
158 elif opt[0]=='-V' or opt[0]=='--version':
159 print self.Version()
160 self.exit_code=1 # request termination
161 break
162 else:
163 self.host=''
164 break
166 except:
167 # unknown param
168 self.host=''
170 if self.debug:
171 print 'Params:'
172 print '-------'
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
178 print
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
185 # the cmd-line
187 if self.exit_code<>0:
188 sys.exit(self.UNKNOWN)
190 if self.host=='':
191 doc_syntax()
192 self.exit_code=self.UNKNOWN
193 self.exit_msg='UNKNOWN: bad params, try running without any params for syntax'
196 def CheckHost(self):
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)
212 if self.debug:
213 print 'Tmpfile is: %s'%self.tmp_file
215 # If a range is given, only run nmap on this range
217 if self.ranges<>'':
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)
222 # Prepare a task
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,
229 debug=self.debug)
231 # Fork of nmap cmd
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)
239 # Check for timeout
241 if th.WasTimeOut():
242 self.exit_code=self.CRITICAL
243 self.exit_msg='CRITICAL - Plugin timed out after %s seconds' % self.timeout
244 return
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
253 return
255 # Read output
257 try:
258 f = open(self.tmp_file, 'r')
259 output=f.readlines()
260 f.close()
261 except:
262 self.exit_code=self.UNKNOWN
263 self.exit_msg='Unable to get output from nmap'
264 return
267 # Store open ports in list
268 # scans for lines where first word contains '/'
269 # and stores part before '/'
271 self.active_ports=[]
272 try:
273 for l in output:
274 if len(l)<2:
275 continue
276 s=string.split(l)[0]
277 if string.find(s,'/')<1:
278 continue
279 p=string.split(s,'/')[0]
280 if string.find(l,'open')>1:
281 self.active_ports.append(int(p))
282 except:
283 # failure due to strange output...
284 pass
286 if self.debug:
287 print 'Ports found by nmap: ',self.active_ports
289 # Filter out optional ports, we don't check status for them...
291 try:
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
297 except:
298 # under extreame loads the remove(p) above failed for me
299 # a few times, this exception hanlder handles
300 # this bug-alike situation...
301 pass
303 opened=self.CheckOpen()
304 closed=self.CheckClosed()
306 if opened <>'':
307 self.exit_code=self.CRITICAL
308 self.exit_msg='PORTS CRITICAL - Open:%s Closed:%s'%(opened,closed)
309 elif closed <>'':
310 self.exit_code=self.WARNING
311 self.exit_msg='PORTS WARNING - Closed:%s'%closed
312 else:
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
321 def CheckOpen(self):
322 opened=''
323 for p in self.active_ports:
324 if p not in self.ports:
325 opened='%s %s' %(opened,p)
326 return opened
329 # Compares requested ports with actually open ports
330 # returns all ports that are should be open
332 def CheckClosed(self):
333 closed=''
334 for p in self.ports:
335 if p not in self.active_ports:
336 closed='%s %s' % (closed,p)
337 return closed
340 def CleanUp(self):
342 # If temp file exists, get rid of it
344 if self.tmp_file<>'' and os.path.isfile(self.tmp_file):
345 try:
346 os.remove(self.tmp_file)
347 except:
348 # temp-file colition, some other process already
349 # removed the same file...
350 pass
353 # Show numerical exits as string in debug mode
355 if self.debug:
356 print 'Exitcode:',self.exit_code,
357 if self.exit_code==self.UNKNOWN:
358 print 'UNKNOWN'
359 elif self.exit_code==self.OK:
360 print 'OK'
361 elif self.exit_code==self.WARNING:
362 print 'WARNING'
363 elif self.exit_code==self.CRITICAL:
364 print 'CRITICAL'
365 else:
366 print 'undefined'
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
379 # Help texts
381 def doc_head():
382 print """
383 check_nmap plugin for Nagios
384 Copyright (c) 2000 Jacob Lundqvist (jaclu@galdrion.com)
385 License: GPL
386 Version: %s""" % _version_
389 def doc_syntax():
390 print """
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]"""
396 def doc_help():
397 'Help is displayed if run without params.'
398 doc_head()
399 doc_syntax()
400 print """
401 Options:
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
410 -V = Version info
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.
424 # Main
426 if __name__ == '__main__':
428 if len (sys.argv) < 2:
430 # No params given, show syntax and exit
432 doc_syntax()
433 sys.exit(-1)
435 nmap=CheckNmap(sys.argv[1:])
436 exit_code,exit_msg=nmap.Run()
439 # Give Nagios a msg and a code
441 print exit_msg
442 sys.exit(exit_code)