#!/usr/bin/perl
#
# Copyright (c) 2009 Dmitry Smirnov <dsmirnov@bemagold.ru>
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
#
##########################################################
#
# Perfdata returned as follows:
#
# RIVERBED:HEALTH=<string>;OUTLAN=<number>;OUTWAN=<number>;INLAN=<number>;INWAN=<number>
#
#
use strict;
use Error qw(:try);
use Getopt::Std;
use Net::SNMP qw(ticks_to_time);
use Data::Dumper;

use constant RB_OID       => '1.3.6.1.4.1.17163.1.1';
use constant RB_OS_OID	  => '1.3.6.1.4.1.2021';
use constant SNMP_TIMEOUT => 5;
use constant PEER_BULK	  => 5;
use constant HEALTH_OK	  => 10000;
my @module = undef;
my $DEBUG =0;
my $o_login=    undef;          # Login for snmpv3
my $o_passwd=   undef;          # Pass for snmpv3
my $v3protocols=undef;  		# V3 protocol list.
my $o_authproto='sha';          # Auth protocol
my $o_privproto='des';          # Priv protocol
my $o_privpass= undef;          # priv password
my $o_version2= undef;          # use snmp v2c

sub main {

    my $status = ['OK', 'WARNING', 'CRITIC', 'UNKNOWN'];
    my $code   = 3;
    my $text   = "";
    
    my ($snmp, $error, $param);
    
    @Error::SNMP::ISA = qw(Error::Simple);
    @Error::MissingParam::ISA = qw(Error::Simple);
	
    try {
	
	$param = getParam();
	print "Get param\n" if($DEBUG==3);
	$DEBUG = $param->{'debug'};
	print "Main\n" if($DEBUG==3);
	print "Param get\n" if($DEBUG==3); 
	use Data::Dumper;
	print Dumper(\$param) if($DEBUG==3);
	print "SNMP v".$param->{'snmpv'}."\n" if($DEBUG==3);
	
	if ( $param->{'snmpv'} eq "2" ) {
		($snmp, $error) = Net::SNMP->session(
			-community => $param->{'community'} || 'public',
			-hostname  => $param->{'host'},
			-version   => 2,
			-timeout   => SNMP_TIMEOUT,
		);
	} else {
		if ( $param->{'snmpv'} eq "3" ) {
			if ( defined($param->{'o_login'}) && defined($param->{'o_pass'})) {
				# SNMPv3 login
				print("SNMPv3 login") if($DEBUG==3);
				print("SNMPv3 AuthNoPriv login : $o_login, $o_authproto") if($DEBUG==3);
				($snmp, $error) = Net::SNMP->session(
				  -hostname         => $param->{'host'},
				  -version          => '3',
				  -username         => $param->{'o_login'},
				  -authpassword     => $param->{'o_pass'},
				  -authprotocol     => $param->{'o_authproto'},
				  -timeout          => SNMP_TIMEOUT,
				  -translate => [
						-timeticks => 0x0   # Turn off so sysUpTime is numeric
					]
				 );

			} else {
				print("Unknow version of SNMP") if($DEBUG==3);
					
			}
		}
	}
	print "DUMP start\n" if($DEBUG==3);
    use Data::Dumper;
	print Dumper(\$error) if($DEBUG==3);
	print Dumper(\$snmp) if($DEBUG==3);
	print "DUMP end\n" if($DEBUG==3);
	throw Error::SNMP($error)
	    if ($error);
	print "Before status\n" if($DEBUG==3);
    use Data::Dumper;
	print Dumper(\$param) if($DEBUG==3);
	($code, $text) = getRiverbedStatus($snmp, $param);
	print Dumper(\$text) if($DEBUG==2);
    } catch Error::MissingParam with {
    
	print << "MSG";
Steelhead Riverbed status/peer connectivity check script
Copyright (c) 2009 Dmitry Smirnov <dsmirnov\@bemagold.ru>

Usage: $0 -H <hostname> [-c <community>] [-p <peer1,peer2,peerN>]

Options:
    -H  Hostname or IP address of the Riverbed Steelhead to check
    -c  SNMP read community (default is 'public')
    -p  Comma-separated list of peer hostnames or IP addresses
	to check for connectivity (optional)
	
Example:
    $0 -H 10.1.2.3 -p riverbed-moscow,riverbed-magadan
    
MSG
	exit(3);
    
    } catch Error::SNMP with {
    
	my $e = shift;
	($code, $text) = (2, 'SNMP Error: ' . $e->text);
    
    } catch Error::Simple with {
    
	($code, $text) = (2, shift);
	
    } finally {

	$snmp->close()
	    if ($snmp);

    };

    print $status->[$code] . ": $text\n";
    exit($code);
}

sub getParam {
    my %opts;
	getopts('H:c:p:l:L:x:V:d:m:', \%opts);
	#print Dumper(\%opts) if($opts{'d'}); 
    throw Error::MissingParam()
	unless ($opts{'H'});
   {
	host      	=> $opts{'H'}, 
	community 	=> $opts{'c'} || 'public', 
	peers     	=> $opts{'p'},
	o_login	  	=> $opts{'l'},
	o_pass	  	=> $opts{'x'},
	snmpv	  	=> $opts{'V'},
	o_authproto	=> $opts{'L'},
	debug		=> $opts{'d'},
	module		=> $opts{'m'}
   };

}
 
sub getSNMPVars {
    my ($snmp, $vars) = @_;
    my $vals = {};

    my $varsHash = {map {$vars->{$_} => $_} keys %$vars};

    use Data::Dumper;
	print "getSNMPVars=\n".Dumper(values %$vars)."\n" if($DEBUG==2);
	print "SS1=\n".(values %$vars)."\n" if($DEBUG==2);
	throw Error::SNMP($snmp->error)
	#unless (defined($snmp->get_request(
	#    -varbindlist => [values %$vars],
	#)));
	unless (defined($snmp->get_request(
	    -varbindlist => [values %$vars],
	)));

	print Dumper(\$snmp) if($DEBUG==3);
	
    foreach ($snmp->var_bind_names()) {
	$vals->{$varsHash->{$_}} = 
	    $snmp->var_bind_list()->{$_};
    }	    

    $vals;
}

sub getSNMPTables {
    my ($snmp, $tables, $breakFunc) = @_;
    my ($baseOid, $idx);

    my $tableVals = {map {$tables->{$_} => {'into' => $_, 'idx' => 0, 'done' => 0}} keys %$tables};
    my $data      = {map {$_ => []} keys %$tables};
    
    while (1) {

	my @oids = map {"$_.$tableVals->{$_}->{'idx'}"} 
		       (grep {!$tableVals->{$_}->{'done'}} 
		  	keys %$tableVals);

	throw Error::SNMP($snmp->error)
	    unless (defined($snmp->get_bulk_request(
		-varbindlist     => \@oids,
		-maxrepetitions  => PEER_BULK,
	    )));

	foreach (keys %$tableVals) {
	    $tableVals->{$_}->{'done'} = 1;
	}

	foreach ($snmp->var_bind_names()) {

	    ($baseOid, $idx) = (/^(.+?)\.(\d+)$/);

	    next unless ($tableVals->{$baseOid});
	    
	    $tableVals->{$baseOid}->{'done'} = 0;
	    $tableVals->{$baseOid}->{'idx'}  = $idx
		unless ($tableVals->{$baseOid}->{'idx'} > $idx);
	    
	    push(@{$data->{$tableVals->{$baseOid}->{'into'}}}, 
		 $snmp->var_bind_list()->{$_});
	}
	
	# Break if lists exhausted or the break function fires 
	last if (scalar(grep {$tableVals->{$_}->{'done'}} keys %$tableVals) ==
		 scalar(keys %$tableVals));
		 
	last if ($breakFunc &&
		 $breakFunc->($data));
    }
    
    $data;
}

sub getRiverbedStatusSystem($) {
#        my($crit) = @_;
#        my($resp);
#        my $uptime = '';
#        print "-- Uptime check\n" if($DEBUG==3);
#        print "-- Uptime critical: $crit\n" if($DEBUG==3);
#        snmp_connect();
#        print "   Retrieving Uptime SNMP OIDs\n$sysUpTime\n" if($DEBUG==3);
#        $resp = $snmp->get_request(
#                -varbindlist=>[
#                        $sysUpTime
#                ],
#                );
    my ($snmp, $param, $module) = @_;
	use Data::Dumper;
	print "In System module\n".Dumper(\$param) if($DEBUG==2); 
	print "Module = ".Dumper($module) if($DEBUG==2); 
	print "RB_OID=".RB_OID."\n" if($DEBUG==2) ;
    my $vals = getSNMPVars($snmp, {
		'temp'						=> RB_OID . '.2.9.0',
		'systemclock'				=> RB_OID . '.2.1.0',
		'uptime'					=> RB_OID . '.2.4.0',
		'cpuLoad1'					=> RB_OID . '.5.1.1.0',
		'cpuLoad5'					=> RB_OID . '.5.1.2.0',
		'cpuLoad15'					=> RB_OID . '.5.1.3.0',
		'cpuUtilProcent'			=> RB_OID . '.5.1.4.0',
		'optimizedConnections'		=> RB_OID . '.5.2.1.0',
		'passthroughConnections'	=> RB_OID . '.5.2.2.0',
		'establishedConnections'	=> RB_OID . '.5.2.5.0',
		'halfOpenedConnections'		=> RB_OID . '.5.2.3.0',
		'halfClosedConnections'		=> RB_OID . '.5.2.4.0',
		'activeConnectionsCounts'	=> RB_OID . '.5.2.6.0',
		'totalConnections'			=> RB_OID . '.5.2.7.0',
		'bwPassThroughIn'			=> RB_OID . '.5.3.3.1.0',
		'bwPassThroughOut'			=> RB_OID . '.5.3.3.2.0',
    });
    
	
	#if ($module =~ /^cpu?(\((\d+)c?,(\d+)c?,(\d+)c?,(\d+)c?,(\d+)c?,(\d+)c?,(\d+)c?\))?/i ) {
	if ($module =~ /^cpu?(\((\d+)c?,(\d+)c?,(\d+)c?,(\d+)c?,(\d+)c?,(\d+)c?\))?/i ) {
		print "Arguments1=".Dumper($1) if($DEBUG==2);
		my $numberwc = split /,/, $1;
		print "Arguments2=".Dumper($numberwc) if($DEBUG==2);
		
		if ($numberwc != 6 ) {
			return (3,"Please provide 6 parameters separeted by `,` for each Load CPU check cpu(WL1,CL1,WL5,CL5,WL15,CL15). Example: system:cpu(10,CL120,30,40,50,60).","");
		}
		
		my $warning_l1 = $2;
		my $critic_l1 = $3;
		my $warning_l5 = $4;
		my $critic_l5 = $5;
		my $warning_l15 = $6;
		my $critic_l15 = $7;
		
		my $PERFDATA_WCMM_L1 = ";".$warning_l1.";".$critic_l1.";;";
		my $PERFDATA_WCMM_L5 = ";".$warning_l5.";".$critic_l5.";;";
		my $PERFDATA_WCMM_L15 = ";".$warning_l15.";".$critic_l15.";;";
		print "Warning/Critical values:\n".$PERFDATA_WCMM_L1."\n".$PERFDATA_WCMM_L5."\n".$PERFDATA_WCMM_L15."\n" if($DEBUG==2);
		print "CPU usage $2,$3\n" if($DEBUG==2);
		print Dumper($vals) if($DEBUG==2);
		my $OUTPUT_PREFIX = "";
		my $OUTPUT = "";
		my $STATUS = 0;
		my $cpuLoad1 = $vals->{'cpuLoad1'};
		my $cpuLoad5 = $vals->{'cpuLoad5'};
		my $cpuLoad15 = $vals->{'cpuLoad15'};
		
		#Performing analysing for warning and critical for each Load[1,5,15]
		#Load 1
		if ( $cpuLoad1 >= $warning_l1 and $cpuLoad1 <$critic_l1) {
			$OUTPUT_PREFIX = "Warning-Load1(>".$warning_l1.")";
			if($STATUS != 2) {
				$STATUS = 1;
			}
		} elsif ( $cpuLoad1 >= $critic_l1 ) {
			$OUTPUT_PREFIX = "Critical-Load1(>".$critic_l1.")";
			$STATUS = 2;
		} else { 
			$OUTPUT_PREFIX = $OUTPUT_PREFIX."";
		}
				
		#Load 5
		if ( $cpuLoad5 >= $warning_l5 and $cpuLoad5 <$critic_l5) {
			$OUTPUT_PREFIX = $OUTPUT_PREFIX." Warning-Load5(>".$warning_l5.")";
			if($STATUS != 2) {
				$STATUS = 1;
			}
		} elsif ( $cpuLoad5 >= $critic_l5 ) {
			$OUTPUT_PREFIX = $OUTPUT_PREFIX." Critical-Load5(>".$critic_l5.")";
			$STATUS = 2;
		} else { 
			$OUTPUT_PREFIX = $OUTPUT_PREFIX."";
		}
		
		#Load 15 {
		if ( $cpuLoad15 >= $warning_l15 and $cpuLoad15 <$critic_l15) {
			$OUTPUT_PREFIX = $OUTPUT_PREFIX." Warning-Load15(>".$warning_l15.")";
			if($STATUS != 2) {
				$STATUS = 1;
			}
		} elsif ( $cpuLoad15 >= $critic_l15 ) {
			$OUTPUT_PREFIX = $OUTPUT_PREFIX." Critical-Load15(>".$critic_l15.")";
			$STATUS = 2;
		} else { 
			$OUTPUT_PREFIX = $OUTPUT_PREFIX."";
		}
		
		print "Output Prefix:".$OUTPUT_PREFIX."\n" if($DEBUG==2);
		$OUTPUT = $OUTPUT_PREFIX.". CPU-Load usage: Load1=".$vals->{'cpuLoad1'}." Load5=".$vals->{'cpuLoad5'}." Load15=".$vals->{'cpuLoad15'};
		$OUTPUT = $OUTPUT." UtilProcent=".$vals->{'cpuUtilProcent'}."%";
		my $PERFDATA = "CPU_Load1=".$vals->{'cpuLoad1'}.$PERFDATA_WCMM_L1." CPU_Load5=".$vals->{'cpuLoad5'}.$PERFDATA_WCMM_L5." CPU_Load15=".$vals->{'cpuLoad15'}.$PERFDATA_WCMM_L15;
	    return ($STATUS,$OUTPUT,$PERFDATA);
		
	}
	
	if ($module =~ /^connections?(\((\d+)c?,(\d+)c?\))?/i ) {
		my $warning = $2;
		my $critic = $3;
		my $PERFDATA_WCMM = ";".$warning.";".$critic.";;";
		print "Connection usage $2,$3\n" if($DEBUG==2);
		print Dumper($vals) if($DEBUG==2);
		
		my $optimizedConnections = $vals->{'optimizedConnections'};
		my $passthroughConnections = $vals->{'passthroughConnections'};
		my $establishedConnections = $vals->{'establishedConnections'};
		my $halfOpenedConnections = $vals->{'halfOpenedConnections'};
		my $halfClosedConnections = $vals->{'halfClosedConnections'};
		my $activeConnectionsCounts = $vals->{'activeConnectionsCounts'};
		my $totalConnections = $vals->{'totalConnections'};
		
		my $OUTPUT = "Connections usage: optimized=".$optimizedConnections." passthrough=".$passthroughConnections." established=".$establishedConnections." halfOpened=".$halfOpenedConnections." halfClosed=".$halfClosedConnections." active=".$activeConnectionsCounts." total=".$totalConnections ;
		my $PERFDATA = "optimized=".$optimizedConnections.$PERFDATA_WCMM." passthrough=".$passthroughConnections.$PERFDATA_WCMM." established=".$establishedConnections.$PERFDATA_WCMM." halfOpened=".$halfOpenedConnections.$PERFDATA_WCMM." halfClosed=".$halfClosedConnections.$PERFDATA_WCMM." active=".$activeConnectionsCounts.$PERFDATA_WCMM." total=".$totalConnections.$PERFDATA_WCMM;
		#"CPU_Load1=".$vals->{'cpuLoad1'}.$PERFDATA_WCMM."CPU_Load5=".$vals->{'cpuLoad5'}.$PERFDATA_WCMM.";CPU_Load15=".$vals->{'cpuLoad15'}.$PERFDATA_WCMM;
	    return (0,$OUTPUT,$PERFDATA);
	}
	
	if ($module =~ /^temp?(\((\d+)c?,(\d+)c?\))?/i ) {
		my $warning = $2;
		my $critic = $3;
		my $PERFDATA_WCMM = ";".$warning.";".$critic.";;";
		print "Temp  $2,$3\n" if($DEBUG==2);
		print Dumper($vals) if($DEBUG==2);
		my $temp = $vals->{'temp'};
		my $STATUS = 0;
		my $OUTPUT = "";
		#WARNING
		if ( $temp >= $warning and $temp <$critic) {
			$OUTPUT = "Temp=".$vals->{'temp'}." is more than ".$warning;
			$STATUS = 1;
		} elsif ( $temp >= $critic ) {
			$OUTPUT = "Temp=".$vals->{'temp'}." is more than ".$critic;
			$STATUS = 2;
		} else { $OUTPUT = "Temp=".$vals->{'temp'}; }
		my $PERFDATA = "Temp=".$vals->{'temp'}.$PERFDATA_WCMM;
	    return ($STATUS,$OUTPUT,$PERFDATA);
	}
	
	if ($module =~ /^uptime?(\((\d+)c?,(\d+)c?\))?/i ) {
		my $warning = $2;
		my $critic = $3;
		my $PERFDATA_WCMM = ";".$warning.";".$critic.";;";
		print "Uptime  $2,$3\n" if($DEBUG==2);
		print Dumper($vals)."\n" if($DEBUG==2);
		print "System clock=\n" if($DEBUG==2);
		use Date::Format;
		use Time::Local;
		my @remote_date = unpack 'n C6 a C2',(pack "H*", (substr $vals->{'systemclock'}, 2));
		print "	remote_date=\n".Dumper(@remote_date)."\n" if($DEBUG==2);
		
		my $remote_timestamp = timelocal($remote_date[5],$remote_date[4],$remote_date[3],
                                 $remote_date[2],$remote_date[1]-1, $remote_date[0]);
		my $o_tzoff = 0;
		my $remote_timestamp = $remote_timestamp + ($o_tzoff * 60);
		my $remote_timestring = time2str("%Y-%m-%e %r %z %Z", $remote_timestamp);
		my $datetime = sprintf "%04d-%02d-%02d %02d:%02d:%02d.%d %s%02d:%02d", @remote_date;
		print $remote_timestring."\n" if($DEBUG==2);
		print $datetime."\n" if($DEBUG==2);
		my $OUTPUT = "";
		my $STATUS = 0;
		my $uptime = $vals->{'uptime'};
		#CRITICAL
        if ( $uptime <= $critic ) {
            $OUTPUT = "Uptime: ".ticks_to_time($uptime)." ($uptime sec). Current server date_time $remote_timestring. Less than ".ticks_to_time($critic)." ($critic sec)";
            $STATUS = 2;
        } else {		
			$OUTPUT = "Uptime: ".ticks_to_time($uptime)." ($uptime sec). Current server date_time $remote_timestring";
		}
		my $PERFDATA = undef;
	    return ($STATUS,$OUTPUT,$PERFDATA);
	}

}


sub getRiverbedStatusHealth {
    my ($snmp, $param) = @_;

    my $vals = getSNMPVars($snmp, {
	'outlan'     => RB_OID . '.5.3.1.1.0',
	'outwan'     => RB_OID . '.5.3.1.2.0',
	'inlan'      => RB_OID . '.5.3.1.3.0',
	'inwan'      => RB_OID . '.5.3.1.4.0',
	'healthenum' => RB_OID . '.2.7.0',
	'healthstr'  => RB_OID . '.2.2.0',
	'model'      => RB_OID . '.1.1.0',
    });
    
    return ($vals->{'healthenum'} != HEALTH_OK ? 2 : 0,
	    "Riverbed $vals->{'model'} status: $vals->{'healthstr'}",
	    "OUTLAN=$vals->{'outlan'};OUTWAN=$vals->{'outwan'};" .
	    "INLAN=$vals->{'inlan'};INWAN=$vals->{'inwan'};");
}

sub getRiverbedStatusPeers {
    my ($snmp, $param) = @_;

    my $havePeers = $param->{'peers'} ? {map {$_ => 0} (split(/\s*,\s*/,lc($param->{'peers'})))}
				      : {};

    return (0, '', '') 
	unless scalar(keys %$havePeers);
				      
    my $isAllPeersFound = sub {
	my $data = shift;

	my ($ips, $hosts) = ($data->{'peers_ip'}   || [],
			     $data->{'peers_host'} || []);
			     
	my $reverseIP = sub {
	    /^(\d+)\.(\d+)\.(\d+)\.(\d+)$/;
	    ($_, "$4.$3.$2.$1");
	};
			     
	foreach my $peer (map {$reverseIP->($_)} @$ips, @$hosts) {
	    $havePeers->{lc($peer)} = 1
		if (exists $havePeers->{lc($peer)});
	}

        scalar(grep {$havePeers->{$_} == 0} keys %$havePeers) == 0;
    };

    my $data = getSNMPTables($snmp, {
	'peers_ip'   => RB_OID . '.2.6.1.1.2',
	'peers_host' => RB_OID . '.2.6.1.1.4',
    }, $isAllPeersFound);

    my $all = $isAllPeersFound->($data);
    my @peers = $all ? keys %$havePeers 
		     : grep {$havePeers->{$_} == 0} keys %$havePeers;
    my $peers  = join(', ', @peers);
    my $plural = scalar(@peers) > 1 ? 's' : '';

    return ($all ? 0 : 2,
	    "peer${plural} $peers" . ($all ? " connected" : " MISSING"),
	    '');    
}

sub getRiverbedStatus {
    my ($snmp, $param) = @_;
    my ($code, $output, $perf);
    my (@outputAll, @perfAll);
	print "getRiverbedStatus\n" if($DEBUG==3);
    use Data::Dumper;
	print "Param = ".Dumper(\$param) if($DEBUG==2); 
	# Break on first error to save traffic.
	@module = split(":",$param->{'module'});
	print "Module = ".Dumper(\@module[0]) if($DEBUG==2);
	print "Sub Module = ".Dumper(\@module[1]) if($DEBUG==2);
#	if (@module == undef) {
#		@module={0,$param->{'module'}};
#		print "Module (if undefined) = ".Dumper(\@module[0]) if($DEBUG==2);
#	}
	if (@module[0] eq "system") { 
		foreach my $func (\&getRiverbedStatusSystem) {
			($code, $output, $perf) = $func->($snmp, $param, @module[1]);
			print "System Module\n".Dumper($func) if($DEBUG==2);
			push @outputAll, $output
				if ($output);
				
			push @perfAll, $perf
				if ($perf);

			last if ($code);
		};
	} elsif ((@module[0] eq "health") || (@module[0] eq undef)) {
		foreach my $func (\&getRiverbedStatusHealth, \&getRiverbedStatusPeers) {
			print Dumper($func) if($DEBUG==3);
			($code, $output, $perf) = $func->($snmp, $param);
			
			push @outputAll, $output
				if ($output);
				
			push @perfAll, $perf
				if ($perf);
			print "perf=".Dumper($perf)."\n"	 if($DEBUG==2);
			last if ($code);
		};
	} else {
		$code=2;
		$output="Please provide correct cheking part";
		$perf="";
		push @outputAll, $output
			if ($output);
				
		push @perfAll, $perf
			if ($perf);
	}
	 print "perfAll_Final=".@perfAll."\n"	 if($DEBUG==2);
	 
     return ($code, join(', ', @outputAll) . '|' . join(', ', @perfAll)) if (@perfAll);
	 return ($code, join(', ', @outputAll)) if (!@perfAll);
}

&main();

