diff options
| author | Ethan Galstad <egalstad@users.sourceforge.net> | 2002-02-28 06:42:51 +0000 |
|---|---|---|
| committer | Ethan Galstad <egalstad@users.sourceforge.net> | 2002-02-28 06:42:51 +0000 |
| commit | 44a321cb8a42d6c0ea2d96a1086a17f2134c89cc (patch) | |
| tree | a1a4d9f7b92412a17ab08f34f04eec45433048b7 /contrib/check_nmap.py | |
| parent | 54fd5d7022ff2d6a59bc52b8869182f3fc77a058 (diff) | |
| download | monitoring-plugins-44a321cb8a42d6c0ea2d96a1086a17f2134c89cc.tar.gz | |
Initial revision
git-svn-id: https://nagiosplug.svn.sourceforge.net/svnroot/nagiosplug/nagiosplug/trunk@2 f882894a-f735-0410-b71e-b25c423dba1c
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 @@ | |||
| 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.20' | ||
| 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.20 2000-07-15 jaclu Updated params to correctly comply to plugin-standard | ||
| 29 | # moved support classes to utils.py | ||
| 30 | # 1.16 2000-07-14 jaclu made options and return codes more compatible with | ||
| 31 | # the plugin developer-guidelines | ||
| 32 | # 1.15 2000-07-14 jaclu added random string to temp-file name | ||
| 33 | # 1.14 2000-07-14 jaclu added check for error from subproc | ||
| 34 | # 1.10 2000-07-14 jaclu converted main part to class | ||
| 35 | # 1.08 2000-07-13 jaclu better param parsing | ||
| 36 | # 1.07 2000-07-13 jaclu changed nmap param to -P0 | ||
| 37 | # 1.06 2000-07-13 jaclu make sure tmp file is deleted on errors | ||
| 38 | # 1.05 2000-07-12 jaclu in debug mode, show exit code | ||
| 39 | # 1.03 2000-07-12 jaclu error handling on nmap output | ||
| 40 | # 1.01 2000-07-12 jaclu added license | ||
| 41 | # 1.00 2000-07-12 jaclu implemented timeout handling | ||
| 42 | # 0.20 2000-07-10 jaclu Initial release | ||
| 43 | |||
| 44 | |||
| 45 | import sys, os, string, whrandom | ||
| 46 | |||
| 47 | import tempfile | ||
| 48 | from getopt import getopt | ||
| 49 | |||
| 50 | # | ||
| 51 | # import generic Nagios-plugin stuff | ||
| 52 | # | ||
| 53 | import utils | ||
| 54 | |||
| 55 | # Where temp files should be placed | ||
| 56 | tempfile.tempdir='/usr/local/nagios/var' | ||
| 57 | |||
| 58 | # Base name for tempfile | ||
| 59 | tempfile.template='check_nmap_tmp.' | ||
| 60 | |||
| 61 | # location and possibly params for nmap | ||
| 62 | nmap_cmd='/usr/bin/nmap -P0' | ||
| 63 | |||
| 64 | |||
| 65 | |||
| 66 | |||
| 67 | |||
| 68 | |||
| 69 | # | ||
| 70 | # the class that does all the real work in this plugin... | ||
| 71 | # | ||
| 72 | # | ||
| 73 | class CheckNmap: | ||
| 74 | |||
| 75 | # Retcodes, so we are compatible with nagios | ||
| 76 | #ERROR= -1 | ||
| 77 | UNKNOWN= -1 | ||
| 78 | OK= 0 | ||
| 79 | WARNING= 1 | ||
| 80 | CRITICAL= 2 | ||
| 81 | |||
| 82 | |||
| 83 | def __init__(self,cmd_line=[]): | ||
| 84 | """Constructor. | ||
| 85 | arguments: | ||
| 86 | cmd_line: normaly sys.argv[1:] if called as standalone program | ||
| 87 | """ | ||
| 88 | self.tmp_file='' | ||
| 89 | self.host='' # host to check | ||
| 90 | self.timeout=10 | ||
| 91 | self.debug=0 # 1= show debug info | ||
| 92 | self.ports=[] # list of mandatory ports | ||
| 93 | self.opt_ports=[] # list of optional ports | ||
| 94 | self.ranges='' # port ranges for nmap | ||
| 95 | self.exit_code=0 # numerical exit-code | ||
| 96 | self.exit_msg='' # message to caller | ||
| 97 | |||
| 98 | self.ParseCmdLine(cmd_line) | ||
| 99 | |||
| 100 | def Run(self): | ||
| 101 | """Actually run the process. | ||
| 102 | This method should be called exactly once. | ||
| 103 | """ | ||
| 104 | |||
| 105 | # | ||
| 106 | # Only call check_host if cmd line was accepted earlier | ||
| 107 | # | ||
| 108 | if self.exit_code==0: | ||
| 109 | self.CheckHost() | ||
| 110 | |||
| 111 | self.CleanUp() | ||
| 112 | return self.exit_code,self.exit_msg | ||
| 113 | |||
| 114 | def Version(self): | ||
| 115 | return 'check_nmap %s' % _version_ | ||
| 116 | |||
| 117 | #----------------------------------------- | ||
| 118 | # | ||
| 119 | # class internal stuff below... | ||
| 120 | # | ||
| 121 | #----------------------------------------- | ||
| 122 | |||
| 123 | # | ||
| 124 | # Param checks | ||
| 125 | # | ||
| 126 | def param2int_list(self,s): | ||
| 127 | lst=string.split(string.replace(s,',',' ')) | ||
| 128 | try: | ||
| 129 | for i in range(len(lst)): | ||
| 130 | lst[i]=int(lst[i]) | ||
| 131 | except: | ||
| 132 | lst=[] | ||
| 133 | return lst | ||
| 134 | |||
| 135 | def ParseCmdLine(self,cmd_line): | ||
| 136 | try: | ||
| 137 | opt_list=getopt(cmd_line,'vH:ho:p:r:t:V',['debug','host=','help', | ||
| 138 | 'optional=','port=','range=','timeout','version']) | ||
| 139 | for opt in opt_list[0]: | ||
| 140 | if opt[0]=='-v' or opt[0]=='--debug': | ||
| 141 | self.debug=1 | ||
| 142 | elif opt[0]=='-H' or opt[0]=='--host': | ||
| 143 | self.host=opt[1] | ||
| 144 | elif opt[0]=='-h' or opt[0]=='--help': | ||
| 145 | doc_help() | ||
| 146 | self.exit_code=1 # request termination | ||
| 147 | break | ||
| 148 | elif opt[0]=='-o' or opt[0]=='--optional': | ||
| 149 | self.opt_ports=self.param2int_list(opt[1]) | ||
| 150 | elif opt[0]=='-p' or opt[0]=='--port': | ||
| 151 | self.ports=self.param2int_list(opt[1]) | ||
| 152 | elif opt[0]=='-r' or opt[0]=='--range': | ||
| 153 | r=string.replace(opt[1],':','-') | ||
| 154 | self.ranges=r | ||
| 155 | elif opt[0]=='-t' or opt[0]=='--timeout': | ||
| 156 | self.timeout=opt[1] | ||
| 157 | elif opt[0]=='-V' or opt[0]=='--version': | ||
| 158 | print self.Version() | ||
| 159 | self.exit_code=1 # request termination | ||
| 160 | break | ||
| 161 | else: | ||
| 162 | self.host='' | ||
| 163 | break | ||
| 164 | |||
| 165 | except: | ||
| 166 | # unknown param | ||
| 167 | self.host='' | ||
| 168 | |||
| 169 | if self.debug: | ||
| 170 | print 'Params:' | ||
| 171 | print '-------' | ||
| 172 | print 'host = %s' % self.host | ||
| 173 | print 'timeout = %s' % self.timeout | ||
| 174 | print 'ports = %s' % self.ports | ||
| 175 | print 'optional ports = %s' % self.opt_ports | ||
| 176 | print 'ranges = %s' % self.ranges | ||
| 177 | |||
| 178 | |||
| 179 | # | ||
| 180 | # a option that wishes us to terminate now has been given... | ||
| 181 | # | ||
| 182 | # This way, you can test params in debug mode and see what this | ||
| 183 | # program recognised by suplying a version param at the end of | ||
| 184 | # the cmd-line | ||
| 185 | # | ||
| 186 | if self.exit_code<>0: | ||
| 187 | sys.exit(self.UNKNOWN) | ||
| 188 | |||
| 189 | if self.host=='': | ||
| 190 | doc_syntax() | ||
| 191 | self.exit_code=self.UNKNOWN | ||
| 192 | self.exit_msg='UNKNOWN: bad params, try running without any params for syntax' | ||
| 193 | |||
| 194 | |||
| 195 | def CheckHost(self): | ||
| 196 | 'Check one host using nmap.' | ||
| 197 | # | ||
| 198 | # Create a tmp file for storing nmap output | ||
| 199 | # | ||
| 200 | # The tempfile module from python 1.5.2 is stupid | ||
| 201 | # two processes runing at aprox the same time gets | ||
| 202 | # the same tempfile... | ||
| 203 | # For this reason I use a random suffix for the tmp-file | ||
| 204 | # Still not 100% safe, but reduces the risk significally | ||
| 205 | # I also inserted checks at various places, so that | ||
| 206 | # _if_ two processes in deed get the same tmp-file | ||
| 207 | # the only result is a normal error message to nagios | ||
| 208 | # | ||
| 209 | r=whrandom.whrandom() | ||
| 210 | self.tmp_file=tempfile.mktemp('.%s')%r.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 | self.active_ports.append(int(p)) | ||
| 280 | except: | ||
| 281 | # failure due to strange output... | ||
| 282 | pass | ||
| 283 | |||
| 284 | if self.debug: | ||
| 285 | print 'Ports found by nmap: ',self.active_ports | ||
| 286 | # | ||
| 287 | # Filter out optional ports, we don't check status for them... | ||
| 288 | # | ||
| 289 | try: | ||
| 290 | for p in self.opt_ports: | ||
| 291 | self.active_ports.remove(p) | ||
| 292 | |||
| 293 | if self.debug and len(self.opt_ports)>0: | ||
| 294 | print 'optional ports removed:',self.active_ports | ||
| 295 | except: | ||
| 296 | # under extreame loads the remove(p) above failed for me | ||
| 297 | # a few times, this exception hanlder handles | ||
| 298 | # this bug-alike situation... | ||
| 299 | pass | ||
| 300 | |||
| 301 | opened=self.CheckOpen() | ||
| 302 | closed=self.CheckClosed() | ||
| 303 | |||
| 304 | if opened <>'': | ||
| 305 | self.exit_code=self.CRITICAL | ||
| 306 | self.exit_msg='PORTS CRITICAL - Open:%s Closed:%s'%(opened,closed) | ||
| 307 | elif closed <>'': | ||
| 308 | self.exit_code=self.WARNING | ||
| 309 | self.exit_msg='PORTS WARNING - Closed:%s'%closed | ||
| 310 | else: | ||
| 311 | self.exit_code=self.OK | ||
| 312 | self.exit_msg='PORTS ok - Only defined ports open' | ||
| 313 | |||
| 314 | |||
| 315 | # | ||
| 316 | # Compares requested ports on with actually open ports | ||
| 317 | # returns all open that should be closed | ||
| 318 | # | ||
| 319 | def CheckOpen(self): | ||
| 320 | opened='' | ||
| 321 | for p in self.active_ports: | ||
| 322 | if p not in self.ports: | ||
| 323 | opened='%s %s' %(opened,p) | ||
| 324 | return opened | ||
| 325 | |||
| 326 | # | ||
| 327 | # Compares requested ports with actually open ports | ||
| 328 | # returns all ports that are should be open | ||
| 329 | # | ||
| 330 | def CheckClosed(self): | ||
| 331 | closed='' | ||
| 332 | for p in self.ports: | ||
| 333 | if p not in self.active_ports: | ||
| 334 | closed='%s %s' % (closed,p) | ||
| 335 | return closed | ||
| 336 | |||
| 337 | |||
| 338 | def CleanUp(self): | ||
| 339 | # | ||
| 340 | # If temp file exists, get rid of it | ||
| 341 | # | ||
| 342 | if self.tmp_file<>'' and os.path.isfile(self.tmp_file): | ||
| 343 | try: | ||
| 344 | os.remove(self.tmp_file) | ||
| 345 | except: | ||
| 346 | # temp-file colition, some other process already | ||
| 347 | # removed the same file... | ||
| 348 | pass | ||
| 349 | |||
| 350 | # | ||
| 351 | # Show numerical exits as string in debug mode | ||
| 352 | # | ||
| 353 | if self.debug: | ||
| 354 | print 'Exitcode:',self.exit_code, | ||
| 355 | if self.exit_code==self.UNKNOWN: | ||
| 356 | print 'UNKNOWN' | ||
| 357 | elif self.exit_code==self.OK: | ||
| 358 | print 'OK' | ||
| 359 | elif self.exit_code==self.WARNING: | ||
| 360 | print 'WARNING' | ||
| 361 | elif self.exit_code==self.CRITICAL: | ||
| 362 | print 'CRITICAL' | ||
| 363 | else: | ||
| 364 | print 'undefined' | ||
| 365 | # | ||
| 366 | # Check if invalid exit code | ||
| 367 | # | ||
| 368 | if self.exit_code<-1 or self.exit_code>2: | ||
| 369 | self.exit_msg=self.exit_msg+' - undefined exit code (%s)' % self.exit_code | ||
| 370 | self.exit_code=self.UNKNOWN | ||
| 371 | |||
| 372 | |||
| 373 | |||
| 374 | |||
| 375 | |||
| 376 | # | ||
| 377 | # Help texts | ||
| 378 | # | ||
| 379 | def doc_head(): | ||
| 380 | print """ | ||
| 381 | check_nmap plugin for Nagios | ||
| 382 | Copyright (c) 2000 Jacob Lundqvist (jaclu@galdrion.com) | ||
| 383 | License: GPL | ||
| 384 | Version: %s""" % _version_ | ||
| 385 | |||
| 386 | |||
| 387 | def doc_syntax(): | ||
| 388 | print """ | ||
| 389 | Usage: check_ports [-v|--debug] [-H|--host host] [-V|--version] [-h|--help] | ||
| 390 | [-o|--optional port1,port2,port3 ...] [-r|--range range] | ||
| 391 | [-p|--port port1,port2,port3 ...] [-t|--timeout timeout]""" | ||
| 392 | |||
| 393 | |||
| 394 | def doc_help(): | ||
| 395 | 'Help is displayed if run without params.' | ||
| 396 | doc_head() | ||
| 397 | doc_syntax() | ||
| 398 | print """ | ||
| 399 | Options: | ||
| 400 | -h = help (this screen ;-) | ||
| 401 | -v = debug mode, show some extra output | ||
| 402 | -H host = host to check (name or IP#) | ||
| 403 | -o ports = optional ports that can be open (one or more), | ||
| 404 | no warning is given if optional port is closed | ||
| 405 | -p ports = ports that should be open (one or more) | ||
| 406 | -r range = port range to feed to nmap. Example: :1024,2049,3000:7000 | ||
| 407 | -t timeout = timeout in seconds, default 10 | ||
| 408 | -V = Version info | ||
| 409 | |||
| 410 | This plugin attempts to verify open ports on the specified host. | ||
| 411 | |||
| 412 | If all specified ports are open, OK is returned. | ||
| 413 | If any of them are closed, WARNING is returned (except for optional ports) | ||
| 414 | If other ports are open, CRITICAL is returned | ||
| 415 | |||
| 416 | If possible, supply an IP address for the host address, | ||
| 417 | as this will bypass the DNS lookup. | ||
| 418 | """ | ||
| 419 | |||
| 420 | |||
| 421 | # | ||
| 422 | # Main | ||
| 423 | # | ||
| 424 | if __name__ == '__main__': | ||
| 425 | |||
| 426 | if len (sys.argv) < 2: | ||
| 427 | # | ||
| 428 | # No params given, show syntax and exit | ||
| 429 | # | ||
| 430 | doc_syntax() | ||
| 431 | sys.exit(-1) | ||
| 432 | |||
| 433 | nmap=CheckNmap(sys.argv[1:]) | ||
| 434 | exit_code,exit_msg=nmap.Run() | ||
| 435 | |||
| 436 | # | ||
| 437 | # Give Nagios a msg and a code | ||
| 438 | # | ||
| 439 | print exit_msg | ||
| 440 | sys.exit(exit_code) | ||
