1#!/usr/local/bin/perl -w
2############################## check_snmp_css.pl #################
3# Version : 1.0.1
4# Date : 27 Sept 2006
5# Author  : Patrick Proy ( patrick at proy.org)
6# Help : http://www.manubulon.com/nagios/
7# Licence : GPL - http://www.fsf.org/licenses/gpl.txt
8# Changelog :
9# Contributors :
10#################################################################
11#
12# Help : ./check_snmp_css.pl -h
13#
14
15use strict;
16use Net::SNMP;
17use Getopt::Long;
18
19# Nagios specific
20
21use lib "/usr/local/libexec/nagios";
22use utils qw(%ERRORS $TIMEOUT);
23#my $TIMEOUT = 15;
24#my %ERRORS=('OK'=>0,'WARNING'=>1,'CRITICAL'=>2,'UNKNOWN'=>3,'DEPENDENT'=>4);
25
26# SNMP Datas
27
28my $css_svc_table = 	"1.3.6.1.4.1.9.9.368.1.15.2.1"; # Svc table
29my $css_svc_name =		"1.3.6.1.4.1.9.9.368.1.15.2.1.1"; #  Service Name / apSvcName
30my $css_svc_index =		"1.3.6.1.4.1.9.9.368.1.15.2.1.2"; #  apSvcIndex
31my $css_svc_enable =	"1.3.6.1.4.1.9.9.368.1.15.2.1.12"; #  apSvcEnable
32my $css_svc_state=		"1.3.6.1.4.1.9.9.368.1.15.2.1.17"; #  apSvcState : suspended(1),  down(2), alive(4), dying(5)
33my $css_svc_maxconn =	"1.3.6.1.4.1.9.9.368.1.15.2.1.19"; #  Max connexions / apSvcMaxConnections
34my $css_svc_conn =		"1.3.6.1.4.1.9.9.368.1.15.2.1.20"; #  apSvcConnections
35my $css_svc_avgresp =	"1.3.6.1.4.1.9.9.368.1.15.2.1.65"; #  apSvcAvgResponseTime : average response time
36my $css_svc_maxresp =	"1.3.6.1.4.1.9.9.368.1.15.2.1.66"; #  apSvcPeakAvgResponseTime : peak response time
37my @css_svc_state_txt=	("","suspended","down","","alive","dying");
38my @css_svc_state_nag=	(3,2,2,3,0,2);
39
40# Globals
41
42my $Version='1.0';
43
44my $o_host = 	undef; 		# hostname
45my $o_community = undef; 	# community
46my $o_port = 	161; 		# port
47my $o_help=	undef; 		# wan't some help ?
48my $o_verb=	undef;		# verbose mode
49my $o_version=	undef;		# print version
50my $o_timeout=  undef; 		# Timeout (Default 5)
51my $o_perf=     undef;          # Output performance data
52my $o_version2= undef;          # use snmp v2c
53#Specific
54my $o_dir=		"/var/tmp/";		# Directory to store temp file in it.
55my $o_dir_set= undef;		# defined if names and index must be read form file.
56my $o_name=		undef;		# service name (regexp)
57my $o_warn_number=	undef;		# minimum number of service before warning
58my $o_crit_number=	undef;		# minimum number of service before critical
59my $o_warn_conn=	undef;		# % of max connexions for warning level
60my $o_crit_conn=	undef;		# % of max connexions for critical level
61my $o_warn_resp=	undef;		# average response time for warning level
62my $o_crit_resp=	undef;		# average response time for critical level
63my @o_levels=		undef;
64# SNMPv3 specific
65my $o_login=	undef;		# Login for snmpv3
66my $o_passwd=	undef;		# Pass for snmpv3
67my $v3protocols=undef;	# V3 protocol list.
68my $o_authproto='md5';		# Auth protocol
69my $o_privproto='des';		# Priv protocol
70my $o_privpass= undef;		# priv password
71
72# functions
73
74sub p_version { print "check_snmp_css version : $Version\n"; }
75
76sub print_usage {
77    print "Usage: $0 [-v] -H <host> -C <snmp_community> [-2] | (-l login -x passwd [-X pass -L <authp>,<privp>]) -n <name> [-d directory] [-w <num>,<resp>,<conn> -c <num>,<resp>,<conn>]  [-p <port>] [-f] [-t <timeout>] [-V]\n";
78}
79
80sub isnnum { # Return true if arg is not a number
81  my $num = shift;
82  if ( $num =~ /^-?(\d+\.?\d*)|(^\.\d+)$/ ) { return 0 ;}
83  return 1;
84}
85
86sub set_status { # return worst status with this order : OK, unknwonw, warning, critical
87  my $new_status=shift;
88  my $cur_status=shift;
89  if (($cur_status == 0)|| ($new_status==$cur_status)){ return $new_status; }
90  if ($new_status==3) { return $cur_status; }
91  if ($new_status > $cur_status) {return $new_status;}
92  return $cur_status;
93}
94
95sub is_pattern_valid { # Test for things like "<I\s*[^>" or "+5-i"
96 my $pat = shift;
97 if (!defined($pat)) { $pat=" ";} # Just to get rid of compilation time warnings
98 return eval { "" =~ /$pat/; 1 } || 0;
99}
100
101sub round ($$) {
102    sprintf "%.$_[1]f", $_[0];
103}
104
105sub help {
106   print "\nSNMP Cisco CSS monitor for Nagios version ",$Version,"\n";
107   print "(c)2004-2006 Patrick Proy\n\n";
108   print_usage();
109   print <<EOT;
110-v, --verbose
111   print extra debugging information
112-h, --help
113   print this help message
114-H, --hostname=HOST
115   name or IP address of host to check
116-n, --name=<name>
117   regexp to select service
118-w, --warning=<num>,<resp>,<conn>
119   Optional. Warning level for
120   - minimum number of active & alive service
121   - average response time
122   - number of connexions
123   For no warnings, put -1 (ex : -w5,-1,3).
124   When using negative numbers, dont put space after "-w"
125-d, --dir=<directory to put file>
126   Directory where the temp file with index, created by check_snmp_css_main.pl, can be found
127   If no directory is set, /tmp will be used
128-c, --critical=<num>,resp>,<conn>
129   Optional. Critical levels (-1 for no critical levels)
130   See warning levels.
131-C, --community=COMMUNITY NAME
132   community name for the host's SNMP agent (implies v1 protocol)
133-2, --v2c
134   Use snmp v2c
135-l, --login=LOGIN ; -x, --passwd=PASSWD
136   Login and auth password for snmpv3 authentication
137   If no priv password exists, implies AuthNoPriv
138-X, --privpass=PASSWD
139   Priv password for snmpv3 (AuthPriv protocol)
140-L, --protocols=<authproto>,<privproto>
141   <authproto> : Authentication protocol (md5|sha : default md5)
142   <privproto> : Priv protocole (des|aes : default des)
143-P, --port=PORT
144   SNMP port (Default 161)
145-f, --perfparse
146   Perfparse compatible output
147-t, --timeout=INTEGER
148   timeout for SNMP in seconds (Default: 5)
149-V, --version
150   prints version number
151EOT
152}
153
154# For verbose output
155sub verb { my $t=shift; print $t,"\n" if defined($o_verb) ; }
156
157sub check_options {
158    Getopt::Long::Configure ("bundling");
159    GetOptions(
160	'v'		=> \$o_verb,		'verbose'		=> \$o_verb,
161	'h'     => \$o_help,    	'help'        	=> \$o_help,
162	'H:s'   => \$o_host,		'hostname:s'	=> \$o_host,
163	'p:i'   => \$o_port,   		'port:i'		=> \$o_port,
164	'C:s'   => \$o_community,	'community:s'	=> \$o_community,
165	'l:s'	=> \$o_login,		'login:s'		=> \$o_login,
166	'x:s'	=> \$o_passwd,		'passwd:s'		=> \$o_passwd,
167	'X:s'	=> \$o_privpass,	'privpass:s'	=> \$o_privpass,
168	'L:s'	=> \$v3protocols,	'protocols:s'	=> \$v3protocols,
169	't:i'   => \$o_timeout,     'timeout:i'		=> \$o_timeout,
170	'V'		=> \$o_version,		'version'		=> \$o_version,
171	'2'     => \$o_version2,	'v2c'			=> \$o_version2,
172	'f'     => \$o_perf,		'perfparse'		=> \$o_perf,
173	'n:s'	=> \$o_name,		'name:s'		=> \$o_name,
174	'w:s'	=> \$o_warn_conn,	'warning:s'		=> \$o_warn_conn,
175	'c:s'	=> \$o_crit_conn,	'critical:s'		=> \$o_crit_conn,
176	'd:s'	=> \$o_dir_set,		'dir:s'		=> \$o_dir_set
177	);
178    # Basic checks
179	if (defined($o_timeout) && (isnnum($o_timeout) || ($o_timeout < 2) || ($o_timeout > 60)))
180	  { print "Timeout must be >1 and <60 !\n"; print_usage(); exit $ERRORS{"UNKNOWN"}}
181	if (!defined($o_timeout)) {$o_timeout=5;}
182    if (defined ($o_help) ) { help(); exit $ERRORS{"UNKNOWN"}};
183    if (defined($o_version)) { p_version(); exit $ERRORS{"UNKNOWN"}};
184    if ( ! defined($o_host) ) # check host and filter
185	{ print_usage(); exit $ERRORS{"UNKNOWN"}}
186    # check snmp information
187    if ( !defined($o_community) && (!defined($o_login) || !defined($o_passwd)) )
188	  { print "Put snmp login info!\n"; print_usage(); exit $ERRORS{"UNKNOWN"}}
189	if ((defined($o_login) || defined($o_passwd)) && (defined($o_community) || defined($o_version2)) )
190	  { print "Can't mix snmp v1,2c,3 protocols!\n"; print_usage(); exit $ERRORS{"UNKNOWN"}}
191	if (defined ($v3protocols)) {
192	  if (!defined($o_login)) { print "Put snmp V3 login info with protocols!\n"; print_usage(); exit $ERRORS{"UNKNOWN"}}
193	  my @v3proto=split(/,/,$v3protocols);
194	  if ((defined ($v3proto[0])) && ($v3proto[0] ne "")) {$o_authproto=$v3proto[0];	}	# Auth protocol
195	  if (defined ($v3proto[1])) {$o_privproto=$v3proto[1];	}	# Priv  protocol
196	  if ((defined ($v3proto[1])) && (!defined($o_privpass))) {
197	    print "Put snmp V3 priv login info with priv protocols!\n"; print_usage(); exit $ERRORS{"UNKNOWN"}}
198	}
199	if (!defined($o_name) || ! (is_pattern_valid($o_name)))
200		{print "Need a service name!\n"; print_usage(); exit $ERRORS{"UNKNOWN"}}
201	if (defined($o_warn_conn)) {
202		@o_levels=split(/,/,$o_warn_conn);
203		if (defined($o_levels[0])) {
204			if (isnnum($o_levels[0])) {print "Need number for warning!\n"; print_usage(); exit $ERRORS{"UNKNOWN"}}
205			if ($o_levels[0] != -1 ) {$o_warn_number=$o_levels[0];}
206		}
207		if (defined($o_levels[1])) {
208			if (isnnum($o_levels[1])) {print "Need number for warning!\n"; print_usage(); exit $ERRORS{"UNKNOWN"}}
209			if ($o_levels[1] != -1 ) {$o_warn_conn=$o_levels[1];} else {$o_warn_conn=undef;}
210		} else {$o_warn_conn=undef;}
211		if (defined($o_levels[2]) ) {
212			if (isnnum($o_levels[2])) {print "Need number for warning!\n"; print_usage(); exit $ERRORS{"UNKNOWN"}}
213			if ($o_levels[2] != -1 ) {$o_warn_resp=$o_levels[2];}
214		}
215	}
216	if (defined($o_crit_conn)) {
217		@o_levels=split(/,/,$o_crit_conn);
218		if (defined($o_levels[0]) ) {
219			if (isnnum($o_levels[0])) {print "Need number for critical!\n"; print_usage(); exit $ERRORS{"UNKNOWN"}}
220			if ($o_levels[0] != -1 ) {
221				$o_crit_number=$o_levels[0];
222				if (defined($o_warn_number) && ($o_crit_number>=$o_warn_number))
223					{print "critical must be < warning!\n"; print_usage(); exit $ERRORS{"UNKNOWN"}}
224			}
225		}
226		if (defined($o_levels[1]) ) {
227			if (isnnum($o_levels[1])) {print "Need number for critical!\n"; print_usage(); exit $ERRORS{"UNKNOWN"}}
228			if ($o_levels[1] != -1 ) {
229				$o_crit_conn=$o_levels[1];
230				if (defined($o_warn_conn) && ($o_warn_conn>=$o_crit_conn))
231					{print "critical must be > warning!\n"; print_usage(); exit $ERRORS{"UNKNOWN"}}
232			} else {$o_crit_conn=undef;}
233		} else {$o_crit_conn=undef;}
234		if (defined($o_levels[2]) ) {
235			if (isnnum($o_levels[2])) {print "Need number for critical!\n"; print_usage(); exit $ERRORS{"UNKNOWN"}}
236			if ($o_levels[2] != -1 ) {
237				$o_crit_resp=$o_levels[1];
238				if (defined($o_warn_resp) && ($o_warn_resp>=$o_crit_resp))
239					{print "critical must be > warning!\n"; print_usage(); exit $ERRORS{"UNKNOWN"}}
240			}
241		}
242	}
243	if (defined($o_dir_set)) {
244	    if ($o_dir_set ne "") {$o_dir=$o_dir_set;}
245		verb("Tmp directory : $o_dir");
246	}
247}
248
249########## MAIN #######
250
251check_options();
252
253# Check gobal timeout if snmp screws up
254if (defined($TIMEOUT)) {
255  verb("Alarm at $TIMEOUT + 5");
256  alarm($TIMEOUT+5);
257} else {
258  verb("no global timeout defined : $o_timeout + 10");
259  alarm ($o_timeout+10);
260}
261
262# Connect to host
263my ($session,$error);
264if ( defined($o_login) && defined($o_passwd)) {
265  # SNMPv3 login
266  verb("SNMPv3 login");
267    if (!defined ($o_privpass)) {
268  verb("SNMPv3 AuthNoPriv login : $o_login, $o_authproto");
269    ($session, $error) = Net::SNMP->session(
270      -hostname   	=> $o_host,
271      -version		=> '3',
272      -username		=> $o_login,
273      -authpassword	=> $o_passwd,
274      -authprotocol	=> $o_authproto,
275      -timeout          => $o_timeout
276    );
277  } else {
278    verb("SNMPv3 AuthPriv login : $o_login, $o_authproto, $o_privproto");
279    ($session, $error) = Net::SNMP->session(
280      -hostname   	=> $o_host,
281      -version		=> '3',
282      -username		=> $o_login,
283      -authpassword	=> $o_passwd,
284      -authprotocol	=> $o_authproto,
285      -privpassword	=> $o_privpass,
286	  -privprotocol => $o_privproto,
287      -timeout          => $o_timeout
288    );
289  }
290} else {
291	if (defined ($o_version2)) {
292		# SNMPv2 Login
293		verb("SNMP v2c login");
294		  ($session, $error) = Net::SNMP->session(
295		 -hostname  => $o_host,
296		 -version   => 2,
297		 -community => $o_community,
298		 -port      => $o_port,
299		 -timeout   => $o_timeout
300		);
301  	} else {
302	  # SNMPV1 login
303	  verb("SNMP v1 login");
304	  ($session, $error) = Net::SNMP->session(
305		-hostname  => $o_host,
306		-community => $o_community,
307		-port      => $o_port,
308		-timeout   => $o_timeout
309	  );
310	}
311}
312if (!defined($session)) {
313   printf("ERROR opening session: %s.\n", $error);
314   exit $ERRORS{"UNKNOWN"};
315}
316$session->max_msg_size(10000);
317
318########### Cisco CSS checks ##############
319
320my (@index,@svcname)=(undef,undef);
321my ($numsvc,$numoid,$numoid2)=0;
322my (@oid,@oid_list,@oid_list2)=undef;
323my $resultat = undef;
324# Get load table by snmp or file
325if (defined($o_dir_set)) {
326	my $file_name=$o_dir."/Nagios_css_".$o_host;
327	my $file_lock=$file_name.".lock";
328
329	# Check for lock file during 3 seconds max and quit if sill here.
330	my $file_timeout=0;
331	while (-e $file_lock) {
332		sleep(1);
333		if ($file_timeout==3) {
334		  print "Lock file remaining for more than 3 sec : UNKNOWN\n";
335		  exit $ERRORS{"UNKNOWN"};
336		}
337		$file_timeout++;
338	}
339	# Open file for reading.
340	open(FILE,"< ".$file_name);
341	while (<FILE>) {
342		my @file_line=split(/:/,$_);
343		if ((defined ($file_line[1])) && ($file_line[1] =~ /$o_name/)) { # select service by name
344			chomp($file_line[1]);
345			$svcname[$numsvc]=$file_line[1];
346			my $key = $file_line[0];
347			verb ("Found : $svcname[$numsvc]");
348			$index[$numsvc++]=$key;
349			# Build oid for snmpget
350			$oid_list[$numoid++]=$css_svc_enable.$key;
351			$oid_list[$numoid++]=$css_svc_state.$key;
352			$oid_list2[$numoid2++]=$css_svc_maxconn.$key;
353			$oid_list2[$numoid2++]=$css_svc_conn.$key;
354			$oid_list2[$numoid2++]=$css_svc_avgresp.$key;
355		}
356	}
357	close (FILE);
358} else {
359	$resultat = (Net::SNMP->VERSION < 4) ?
360			  $session->get_table($css_svc_name)
361			: $session->get_table(Baseoid => $css_svc_name);
362
363	if (!defined($resultat)) {
364	   printf("ERROR: Description table : %s.\n", $session->error);
365	   $session->close;
366	   exit $ERRORS{"UNKNOWN"};
367	}
368
369
370	# Get name data & index
371
372	foreach my $key ( keys %$resultat) {
373		verb("OID : $key, Desc : $$resultat{$key}");
374		if ($$resultat{$key} =~ /$o_name/) { # select service by name
375			$svcname[$numsvc]=$$resultat{$key};
376			$key =~ s/$css_svc_name//;
377			verb ("Found : $svcname[$numsvc]");
378			$index[$numsvc++]=$key;
379			# Build oid for snmpget
380			$oid_list[$numoid++]=$css_svc_enable.$key;
381			$oid_list[$numoid++]=$css_svc_state.$key;
382			$oid_list2[$numoid2++]=$css_svc_maxconn.$key;
383			$oid_list2[$numoid2++]=$css_svc_conn.$key;
384			$oid_list2[$numoid2++]=$css_svc_avgresp.$key;
385		}
386	}
387}
388# Check if a least one service found
389if ($numsvc == 0) {
390	print "No service matching ",$o_name," found : CRITICAL\n";
391	exit $ERRORS{"CRITICAL"};
392}
393
394$resultat = undef;
395$resultat = (Net::SNMP->VERSION < 4) ?
396          $session->get_request(@oid_list)
397        : $session->get_request(-varbindlist => \@oid_list);
398
399if (!defined($resultat)) {
400   printf("ERROR: Status get : %s.\n", $session->error);
401   $session->close;
402   exit $ERRORS{"UNKNOWN"};
403}
404my $resultat2 = undef;
405$resultat2 = (Net::SNMP->VERSION < 4) ?
406          $session->get_request(@oid_list2)
407        : $session->get_request(-varbindlist => \@oid_list2);
408
409if (!defined($resultat2)) {
410   printf("ERROR: Conn get : %s.\n", $session->error);
411   $session->close;
412   exit $ERRORS{"UNKNOWN"};
413}
414
415my $output="";
416my $output_perf="";
417my $numsvc_ok=0;
418my $output_done=0;
419my $global_status=0;
420
421for (my $i=0;$i<$numsvc;$i++) {
422	my $key=$index[$i];
423	if ($$resultat{$css_svc_enable.$key} == 0 ) {
424		# service disabled
425		if ($output ne "") { $output.=", ";}
426		$output .= $svcname[$i] . " : Disabled";
427	} else {
428		if ($css_svc_state_nag[$$resultat{$css_svc_state.$key}] != 0) {
429			# state not OK
430			if ($output ne "") { $output.=", ";}
431			$output .= $svcname[$i] . " : " . $css_svc_state_txt[$$resultat{$css_svc_state.$key}];
432		} else {
433			$numsvc_ok++;
434			$output_done=0;
435			# state OK
436			my $prctconn = round(($$resultat2{$css_svc_conn.$key}/$$resultat2{$css_svc_maxconn.$key}) * 100,0);
437			my $resptime = $$resultat2{$css_svc_avgresp.$key};
438			if (defined ($o_warn_conn) && ($prctconn>$o_warn_conn)) {
439				if ($output ne "") { $output.=", ";}
440				$output .= $svcname[$i]. ":" . $prctconn ."%, ".$resptime."ms";
441				set_status(1,$global_status);$output_done=1;
442			}
443			if (defined ($o_crit_conn) && ($prctconn>$o_crit_conn)) {
444				if ($output_done==0) {
445					$output .= $svcname[$i]. ":" . $prctconn ."%, ".$resptime."ms";
446					$output_done=1;
447				}
448				set_status(2,$global_status);
449			}
450			if (defined ($o_warn_resp) && ($prctconn>$o_warn_resp)) {
451				if ($output_done==0) {
452					$output .= $svcname[$i]. ":" . $prctconn ."%, ".$resptime."ms";
453					$output_done=1;
454				}
455				set_status(1,$global_status);
456			}
457			if (defined ($o_crit_resp) && ($prctconn>$o_crit_resp)) {
458				if ($output_done==0) {
459					$output .= $svcname[$i]. ":" . $prctconn ."%, ".$resptime."ms";
460					$output_done=1;
461				}
462				set_status(2,$global_status);
463			}
464		}
465	}
466}
467
468
469$output .= " ".$numsvc_ok."/".$numsvc." services OK";
470
471if (($global_status == 2) || ((defined ($o_crit_number)) && ($numsvc_ok<=$o_crit_number)) || ($numsvc_ok==0) ) {
472	print $output," : CRITICAL\n";
473	exit $ERRORS{"CRITICAL"}
474}
475if (($global_status == 1) || ((defined ($o_warn_number)) && ($numsvc_ok<=$o_warn_number))) {
476	print $output," : WARNING\n";
477	exit $ERRORS{"WARNING"}
478}
479print $output," : OK\n";
480exit $ERRORS{"OK"};
481