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) | ||
