diff options
Diffstat (limited to 'contrib/check_nmap.py')
-rw-r--r-- | contrib/check_nmap.py | 441 |
1 files changed, 0 insertions, 441 deletions
diff --git a/contrib/check_nmap.py b/contrib/check_nmap.py deleted file mode 100644 index 481a62bf..00000000 --- a/contrib/check_nmap.py +++ /dev/null | |||
@@ -1,441 +0,0 @@ | |||
1 | #!/usr/bin/python | ||
2 | # Change the above line if python is somewhere else | ||
3 | |||
4 | # | ||
5 | # check_nmap | ||
6 | # | ||
7 | # Program: nmap plugin for Nagios | ||
8 | # License: GPL | ||
9 | # Copyright (c) 2000 Jacob Lundqvist (jaclu@galdrion.com) | ||
10 | # | ||
11 | _version_ = '1.21' | ||
12 | # | ||
13 | # | ||
14 | # Description: | ||
15 | # | ||
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. | ||
21 | # | ||
22 | # Requirements: | ||
23 | # python | ||
24 | # nmap | ||
25 | # | ||
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 | ||
44 | |||
45 | |||
46 | import sys, os, string, random | ||
47 | |||
48 | import tempfile | ||
49 | from getopt import getopt | ||
50 | |||
51 | # | ||
52 | # import generic Nagios-plugin stuff | ||
53 | # | ||
54 | import utils | ||
55 | |||
56 | # Where temp files should be placed | ||
57 | tempfile.tempdir='/usr/local/nagios/var' | ||
58 | |||
59 | # Base name for tempfile | ||
60 | tempfile.template='check_nmap_tmp.' | ||
61 | |||
62 | # location and possibly params for nmap | ||
63 | nmap_cmd='/usr/bin/nmap -P0' | ||
64 | |||
65 | |||
66 | |||
67 | |||
68 | |||
69 | |||
70 | # | ||
71 | # the class that does all the real work in this plugin... | ||
72 | # | ||
73 | # | ||
74 | class CheckNmap: | ||
75 | |||
76 | # Retcodes, so we are compatible with nagios | ||
77 | #ERROR= -1 | ||
78 | UNKNOWN= -1 | ||
79 | OK= 0 | ||
80 | WARNING= 1 | ||
81 | CRITICAL= 2 | ||
82 | |||
83 | |||
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 | ||
98 | |||
99 | self.ParseCmdLine(cmd_line) | ||
100 | |||
101 | def Run(self): | ||
102 | """Actually run the process. | ||
103 | This method should be called exactly once. | ||
104 | """ | ||
105 | |||
106 | # | ||
107 | # Only call check_host if cmd line was accepted earlier | ||
108 | # | ||
109 | if self.exit_code==0: | ||
110 | self.CheckHost() | ||
111 | |||
112 | self.CleanUp() | ||
113 | return self.exit_code,self.exit_msg | ||
114 | |||
115 | def Version(self): | ||
116 | return 'check_nmap %s' % _version_ | ||
117 | |||
118 | #----------------------------------------- | ||
119 | # | ||
120 | # class internal stuff below... | ||
121 | # | ||
122 | #----------------------------------------- | ||
123 | |||
124 | # | ||
125 | # Param checks | ||
126 | # | ||
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 | ||
135 | |||
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 | ||
165 | |||
166 | except: | ||
167 | # unknown param | ||
168 | self.host='' | ||
169 | |||
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 | |||
179 | |||
180 | # | ||
181 | # a option that wishes us to terminate now has been given... | ||
182 | # | ||
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 | ||
186 | # | ||
187 | if self.exit_code<>0: | ||
188 | sys.exit(self.UNKNOWN) | ||
189 | |||
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' | ||
194 | |||
195 | |||
196 | def CheckHost(self): | ||
197 | 'Check one host using nmap.' | ||
198 | # | ||
199 | # Create a tmp file for storing nmap output | ||
200 | # | ||
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 | ||
209 | # | ||
210 | self.tmp_file=tempfile.mktemp('.%s') % random.randint(0,100000) | ||
211 | if self.debug: | ||
212 | print 'Tmpfile is: %s'%self.tmp_file | ||
213 | # | ||
214 | # If a range is given, only run nmap on this range | ||
215 | # | ||
216 | if self.ranges<>'': | ||
217 | global nmap_cmd # needed, to avoid error on next line | ||
218 | # since we assigns to nmap_cmd :) | ||
219 | nmap_cmd='%s -p %s' %(nmap_cmd,self.ranges) | ||
220 | # | ||
221 | # Prepare a task | ||
222 | # | ||
223 | t=utils.Task('%s %s' %(nmap_cmd,self.host)) | ||
224 | # | ||
225 | # Configure a time-out handler | ||
226 | # | ||
227 | th=utils.TimeoutHandler(t.Kill, time_to_live=self.timeout, | ||
228 | debug=self.debug) | ||
229 | # | ||
230 | # Fork of nmap cmd | ||
231 | # | ||
232 | t.Run(detach=0, stdout=self.tmp_file,stderr='/dev/null') | ||
233 | # | ||
234 | # Wait for completition, error or timeout | ||
235 | # | ||
236 | nmap_exit_code=t.Wait(idlefunc=th.Check, interval=1) | ||
237 | # | ||
238 | # Check for timeout | ||
239 | # | ||
240 | if th.WasTimeOut(): | ||
241 | self.exit_code=self.CRITICAL | ||
242 | self.exit_msg='CRITICAL - Plugin timed out after %s seconds' % self.timeout | ||
243 | return | ||
244 | # | ||
245 | # Check for exit status of subprocess | ||
246 | # Must do this after check for timeout, since the subprocess | ||
247 | # also returns error if aborted. | ||
248 | # | ||
249 | if nmap_exit_code <> 0: | ||
250 | self.exit_code=self.UNKNOWN | ||
251 | self.exit_msg='nmap program failed with code %s' % nmap_exit_code | ||
252 | return | ||
253 | # | ||
254 | # Read output | ||
255 | # | ||
256 | try: | ||
257 | f = open(self.tmp_file, 'r') | ||
258 | output=f.readlines() | ||
259 | f.close() | ||
260 | except: | ||
261 | self.exit_code=self.UNKNOWN | ||
262 | self.exit_msg='Unable to get output from nmap' | ||
263 | return | ||
264 | |||
265 | # | ||
266 | # Store open ports in list | ||
267 | # scans for lines where first word contains '/' | ||
268 | # and stores part before '/' | ||
269 | # | ||
270 | self.active_ports=[] | ||
271 | try: | ||
272 | for l in output: | ||
273 | if len(l)<2: | ||
274 | continue | ||
275 | s=string.split(l)[0] | ||
276 | if string.find(s,'/')<1: | ||
277 | continue | ||
278 | p=string.split(s,'/')[0] | ||
279 | if string.find(l,'open')>1: | ||
280 | self.active_ports.append(int(p)) | ||
281 | except: | ||
282 | # failure due to strange output... | ||
283 | pass | ||
284 | |||
285 | if self.debug: | ||
286 | print 'Ports found by nmap: ',self.active_ports | ||
287 | # | ||
288 | # Filter out optional ports, we don't check status for them... | ||
289 | # | ||
290 | try: | ||
291 | for p in self.opt_ports: | ||
292 | self.active_ports.remove(p) | ||
293 | |||
294 | if self.debug and len(self.opt_ports)>0: | ||
295 | print 'optional ports removed:',self.active_ports | ||
296 | except: | ||
297 | # under extreame loads the remove(p) above failed for me | ||
298 | # a few times, this exception hanlder handles | ||
299 | # this bug-alike situation... | ||
300 | pass | ||
301 | |||
302 | opened=self.CheckOpen() | ||
303 | closed=self.CheckClosed() | ||
304 | |||
305 | if opened <>'': | ||
306 | self.exit_code=self.CRITICAL | ||
307 | self.exit_msg='PORTS CRITICAL - Open:%s Closed:%s'%(opened,closed) | ||
308 | elif closed <>'': | ||
309 | self.exit_code=self.WARNING | ||
310 | self.exit_msg='PORTS WARNING - Closed:%s'%closed | ||
311 | else: | ||
312 | self.exit_code=self.OK | ||
313 | self.exit_msg='PORTS ok - Only defined ports open' | ||
314 | |||
315 | |||
316 | # | ||
317 | # Compares requested ports on with actually open ports | ||
318 | # returns all open that should be closed | ||
319 | # | ||
320 | def CheckOpen(self): | ||
321 | opened='' | ||
322 | for p in self.active_ports: | ||
323 | if p not in self.ports: | ||
324 | opened='%s %s' %(opened,p) | ||
325 | return opened | ||
326 | |||
327 | # | ||
328 | # Compares requested ports with actually open ports | ||
329 | # returns all ports that are should be open | ||
330 | # | ||
331 | def CheckClosed(self): | ||
332 | closed='' | ||
333 | for p in self.ports: | ||
334 | if p not in self.active_ports: | ||
335 | closed='%s %s' % (closed,p) | ||
336 | return closed | ||
337 | |||
338 | |||
339 | def CleanUp(self): | ||
340 | # | ||
341 | # If temp file exists, get rid of it | ||
342 | # | ||
343 | if self.tmp_file<>'' and os.path.isfile(self.tmp_file): | ||
344 | try: | ||
345 | os.remove(self.tmp_file) | ||
346 | except: | ||
347 | # temp-file colition, some other process already | ||
348 | # removed the same file... | ||
349 | pass | ||
350 | |||
351 | # | ||
352 | # Show numerical exits as string in debug mode | ||
353 | # | ||
354 | if self.debug: | ||
355 | print 'Exitcode:',self.exit_code, | ||
356 | if self.exit_code==self.UNKNOWN: | ||
357 | print 'UNKNOWN' | ||
358 | elif self.exit_code==self.OK: | ||
359 | print 'OK' | ||
360 | elif self.exit_code==self.WARNING: | ||
361 | print 'WARNING' | ||
362 | elif self.exit_code==self.CRITICAL: | ||
363 | print 'CRITICAL' | ||
364 | else: | ||
365 | print 'undefined' | ||
366 | # | ||
367 | # Check if invalid exit code | ||
368 | # | ||
369 | if self.exit_code<-1 or self.exit_code>2: | ||
370 | self.exit_msg=self.exit_msg+' - undefined exit code (%s)' % self.exit_code | ||
371 | self.exit_code=self.UNKNOWN | ||
372 | |||
373 | |||
374 | |||
375 | |||
376 | |||
377 | # | ||
378 | # Help texts | ||
379 | # | ||
380 | def doc_head(): | ||
381 | print """ | ||
382 | check_nmap plugin for Nagios | ||
383 | Copyright (c) 2000 Jacob Lundqvist (jaclu@galdrion.com) | ||
384 | License: GPL | ||
385 | Version: %s""" % _version_ | ||
386 | |||
387 | |||
388 | def doc_syntax(): | ||
389 | print """ | ||
390 | Usage: check_nmap.py [-v|--debug] [-H|--host host] [-V|--version] [-h|--help] | ||
391 | [-o|--optional port1,port2,port3 ...] [-r|--range range] | ||
392 | [-p|--port port1,port2,port3 ...] [-t|--timeout timeout]""" | ||
393 | |||
394 | |||
395 | def doc_help(): | ||
396 | 'Help is displayed if run without params.' | ||
397 | doc_head() | ||
398 | doc_syntax() | ||
399 | print """ | ||
400 | Options: | ||
401 | -h = help (this screen ;-) | ||
402 | -v = debug mode, show some extra output | ||
403 | -H host = host to check (name or IP#) | ||
404 | -o ports = optional ports that can be open (one or more), | ||
405 | no warning is given if optional port is closed | ||
406 | -p ports = ports that should be open (one or more) | ||
407 | -r range = port range to feed to nmap. Example: :1024,2049,3000:7000 | ||
408 | -t timeout = timeout in seconds, default 10 | ||
409 | -V = Version info | ||
410 | |||
411 | This plugin attempts to verify open ports on the specified host. | ||
412 | |||
413 | If all specified ports are open, OK is returned. | ||
414 | If any of them are closed, WARNING is returned (except for optional ports) | ||
415 | If other ports are open, CRITICAL is returned | ||
416 | |||
417 | If possible, supply an IP address for the host address, | ||
418 | as this will bypass the DNS lookup. | ||
419 | """ | ||
420 | |||
421 | |||
422 | # | ||
423 | # Main | ||
424 | # | ||
425 | if __name__ == '__main__': | ||
426 | |||
427 | if len (sys.argv) < 2: | ||
428 | # | ||
429 | # No params given, show syntax and exit | ||
430 | # | ||
431 | doc_syntax() | ||
432 | sys.exit(-1) | ||
433 | |||
434 | nmap=CheckNmap(sys.argv[1:]) | ||
435 | exit_code,exit_msg=nmap.Run() | ||
436 | |||
437 | # | ||
438 | # Give Nagios a msg and a code | ||
439 | # | ||
440 | print exit_msg | ||
441 | sys.exit(exit_code) | ||