# The snmp Monitor does require Simon Leinen's snmp modules
# They are available at
#	http://www.switch.ch/misc/leinen/snmp/perl/index.html

package Monitor::snmp;

require 5.002;

use strict;
use SNMP_util;

@Monitor::snmp::ISA = qw(Monitor::Monitor);

#$Monitor::snmp::offlmib = "/usr/local/lib/bs/adm/snmptest";

&load_mib( undef, BigSister::common::find_file("mibs.txt" ) );

sub new
{
    my ($this, $host, $community ) = @_;
    my  $class = ref($this) || $this;
    my  $self = {};

    bless($self, $class);

    unless( $community ) {
	$community = "public";
    }
    $self->{"host"} = $host;
    $self->{"community"} = $community;
    $self->{"lastpoll"} = 0;
    # this is obsolete
    $self->{"freq"} = 10;

    $self->{"poll_fail"} = [];
    $self->{"htable"} = [];
    $self->{"status"} = [];
    $self->{"cache"} = [];
    $self->{"walks"} = [];
    $self->{"walk_checks"} = {};
    $self->{"walk_state"} = {};

    return($self);
}


sub load_mib {
    my( $self, $file ) = @_;

    open( IN, "<$file" );
    while( <IN> ) {
	chomp;
	next if( /^#/ );
	if( /^([^\s\t]+)[\s\t]+([^\s\t]+)$/ ) {
	    $SNMP_util::OIDS{$1} = $2;
	}
    }
    close IN;
}


sub poll_fail {
    my( $self, $item, $text, $state ) = @_;

    $state = 0 unless( defined $state );
    $text = "snmp" unless( defined $text );
    if( $item ) {
	push( @{$self->{"poll_fail"}} , [ $item, $text, $state ] );
    }
}




# deprecated
sub frequency {
    my( $self, $freq ) = @_;
    $self->{"freq"} = $freq;
    $self->{"freq"} = 4*60 unless( $self->{"freq"} );
}


sub get {
    my( $self, @oid ) = @_;

    if( $Monitor::snmp::offlmib ) {
	 my @ret = ();
	 print "snmpget/debug $Monitor::snmp::offlmib\n" if( $BigSister::common::dl>3 );
	 my $i;
	 foreach $i (@oid) {
	     push( @ret, undef );
	     open( _IN, "<$Monitor::snmp::offlmib" );
	     while( <_IN> ) {
		 chomp;
		 if( /^$i = (.*)/ ) {
		     close(_IN);
		     print "  snmpget/debug $i:$1\n" if( $BigSister::common::dl>4 );
		     $ret[$#ret] = $1;
		     last;
		 }
	     }
	     close( _IN );
	 }
	 return( @ret );
    }
    return( snmpget( $self->{"community"}."\@".$self->{"host"}, @oid ) );
}


sub walk {
    my( $self, $oid ) = @_;

    if( $Monitor::snmp::offlmib ) {
	 my( @ret );
	 print "snmpwalk/debug $Monitor::snmp::offlmib\n" if( $BigSister::common::dl>3 );
	 open( _IN, "<$Monitor::snmp::offlmib" );
	 while( <_IN> ) {
	     chomp;
	     if( /^$oid\.([^\s]+) = (.*)/ ) {
		 push( @ret, "$1:$2" );
		 print "  snmpwalk/debug $1:$2\n" if( $BigSister::common::dl>4 );
	     }
	 }
	 close( _IN );
	 return( @ret );
    }
    return( snmpwalk( $self->{"community"}."\@".$self->{"host"}, $oid ) );
}


sub add_walk {
    my( $self, $oid ) = @_;
    my( $walks ) = $self->{"walks"};

    push( @$walks, [ $oid ] ) unless (grep ($_[0] eq $oid, @$walks));
}


sub add_check {
    my( $self, $oid, $item, $name, $differential, $min1, $min2, $max1, $max2 ) = @_;
    my( $htable ) = $self->{"htable"};

    ($item = $self->{"host"}.".snmp") unless( $item );
    push( @$htable, [ $oid, $item, $name, $differential, $min1, $min2, $max1, $max2 ] );
}

sub add_walk_check {
    my( $self, $oid, $index, $item, $name, $differential, $min1, $min2, $max1, $max2 ) = @_;
    my( $walk_checks ) = $self->{"walk_checks"};

    ($index = "undef") unless ($index);
    ($item = $self->{"host"}.".snmp") unless( $item );
    if (!defined $walk_checks->{$oid}{$index}) {
	$walk_checks->{$oid}{$index} = [];
    }
    my $walk_check = \@{$walk_checks->{$oid}{$index}};
    push( @{$walk_checks->{$oid}{$index}}, [ $item, $name, $differential, $min1, $min2, $max1, $max2 ] );
}



sub check
{
    my ($self) = @_;
    my( $htable ) = $self->{"htable"};
    my( $walk_checks ) = $self->{"walk_checks"};
    my( $walk_state ) = $self->{"walk_state"};
    my( @oids, $check, $diff, $i, @noids );
    my( $now ) = time;
    my( @newval, @chkval, $oldstatus, $timediff );

    # the 'if' is deprecated
    if( ($timediff = $now - $self->{"lastpoll"}) >= $self->{"freq"} ) {
	$self->{"lastpoll"} = $now;
	$self->{"pollok"} = 0;
	$i = 0;
	@newval = ();
	@noids = ();
	foreach $check (@$htable) {
	    print "    snmp: getting oid $$check[0]\n" if( $BigSister::common::dl>6 );
	    push( @oids, $$check[0] );
	    push( @noids, $$check[0] );
	    $diff = 1 if( $$check[3] );
	    if( $i++ > 20 ) {
		push( @newval, $self->get( @noids ) );
		$i = 0;
		@noids = ();
	    }
	}
	push( @newval, $self->get( @noids ) ) if( @noids );
	@chkval = @newval;
	if( grep( defined $_, @chkval ) ) {
	    $self->{"pollok"} = 1;
	}
	$oldstatus = $self->{"status"};
	if( $diff ) {
	    for( $i=$#chkval; $i>=0; $i-- ) {
		$check = $$htable[$i];
		if( $$check[3] ) {
		    if( defined $$oldstatus[$i] ) {
			$chkval[$i] -= $$oldstatus[$i];
			if( $$check[3] eq "PERSEC" ) {
			    $chkval[$i] /= $timediff;
			}
		    }
		    else {
			$chkval[$i] = undef;
		    }
		}
	    }
	 }

	 my (%new_walk_state) = %$walk_state;
	 my $walks = $self->{"walks"};
	 foreach $check (@$walks) {
	     my( $oid ) = @$check;
	     my( @subs ) = $self->walk( $oid );
	     my $i;
	     print "    snmp: doing snmpwalk for $oid\n" if( $BigSister::common::dl>5 );
	     foreach $i (@subs) {
		 print "      snmp: sub = $i\n" if( $BigSister::common::dl>6 );
		 my $pos = index( $i, ":" );
		 next if( $pos<0 );
		 my $soid = substr( $i, 0, $pos );
		 my $val = substr( $i, $pos+1 );
		 $val =~ s/^\"//;
		 $val =~ s/\".*//;
		 $i = "snmp.".$oid.".".$soid;
		 
		 my $walk_check = $walk_checks->{$oid}{$soid};
		 ($walk_check = $walk_checks->{$oid}{"undef"}) unless ($walk_check);
		 my $dest;
		 foreach $dest (@{$walk_check}) {
		     my ($item, $name, $diff, $min1, $min2, $max1, $max2) = @$dest;
		     print "snmp: walk_check: $item, $name, $diff at $soid\n" if ($BigSister::common::dl>8);
		     $self->{"pollok"} = 1;
		     my $newval;
		     if ($diff) {
			 my $timespan = $now - $walk_state->{$oid}{$soid}[0];
			 if (defined $walk_state->{$oid}{$soid} && $timespan==0) {
			     print "snmpwalkcheck: $item, $name, $oid, $soid defined already but timespan 0\n";
			 }
			 if (defined $walk_state->{$oid}{$soid} && $timespan>0) {
			     $newval = $val - $walk_state->{$oid}{$soid}[1];
			     if ( $diff eq "PERSEC") {
				 $newval /= $timespan;
			     }
			 } else {
			     $newval = undef;
			 }
		     } else {
			 $newval = $val;
		     }
		     $name.=".$soid";
		     unless ( defined $newval ) {
			 $self->report( $item, $BigSister::common::status_codes{"purple"}, $name. " (undefined)" );
		     } else {
			 if( defined $min1 || defined $min2 || defined $max1 || defined $max2 ) {
			     if( (length($min1) && ($newval<$min1)) ) {
				 $self->report( $item, $BigSister::common::status_codes{"red"}, "$name <<($newval)" );
			     } elsif( (length($min2) && ($newval<$min2)) ) {
		    		 $self->report( $item, $BigSister::common::status_codes{"yellow"}, "$name <($newval)" );
			     } elsif( (length($max2) && ($newval>$max2)) ) {
		    		 $self->report( $item, $BigSister::common::status_codes{"red"}, "$name >>($newval)" );
			     } elsif( (length($max1) && ($newval>$max1)) ) {
		    		 $self->report( $item, $BigSister::common::status_codes{"yellow"}, "$name >($newval)" );
			     } else {
		    		 $self->report( $item, $BigSister::common::status_codes{"green"}, "$name =($newval)" );
			     }
			 }
		     }
		     $new_walk_state{$oid}{$soid} = [$now, $val];
		 }
		$self->set_variable( $self->{"host"}, "snmp.".$oid.".".$soid, $val );
	    }
	    $self->set_variable( $self->{"host"}, "snmp.".$oid, \@subs );
	}

	undef $self->{"walk_state"};
	$self->{"walk_state"} = \%new_walk_state;
	undef $self->{"cache"};
	$self->{"cache"} = \@chkval;
	undef $self->{"status"};
	$self->{"status"} = \@newval;
    }

    my ($pf);
    foreach $pf (@{$self->{"poll_fail"}}) {
	if( $self->{"pollok"} ) {
	    $self->report( $$pf[0], $BigSister::common::status_codes{"green"}, $$pf[1]." OK" );
	}
	else {
	    $self->report( $$pf[0], $$pf[2], $$pf[1]." FAILURE" );
	}
    }
	    
    my( $cache ) = $self->{"cache"};
    my( $status ) = $self->{"status"};
    my( $val );
    my( $oid, $item, $name, $differential, $min1, $min2, $max1, $max2 );
    for( $i=0; $i<=$#$htable; $i++ ) {
	$check = $$htable[$i];
	( $oid, $item, $name, $differential, $min1, $min2, $max1, $max2 ) = @$check;
	if( $BigSister::common::dl>3 ) {
	    print "snmp:$oid, $item, $name -> status: ".$$status[$i]." check: ".$$cache[$i]."\n";
	}
	unless( defined $$cache[$i] ) {
	    $self->report( $$check[1], $BigSister::common::status_codes{"purple"}, $$check[2]. " (undefined)" );
	    $self->set_variable( $self->{"host"}, "snmp.".$oid, undef );
	}
	else {
	    $val = $$cache[$i];
	    $self->set_variable( $self->{"host"}, "snmp.".$oid, $val );
	    if( defined $min1 || defined $min2 || defined $max1 || defined $max2 ) {
		if( (length($min1) && ($val<$min1)) ) {
		    $self->report( $item, $BigSister::common::status_codes{"red"}, "$name <<($val)" );
		}
		elsif( (length($min2) && ($val<$min2)) ) {
		    $self->report( $item, $BigSister::common::status_codes{"yellow"}, "$name <($val)" );
		}
		elsif( (length($max2) && ($val>$max2)) ) {
		    $self->report( $item, $BigSister::common::status_codes{"red"}, "$name >>($val)" );
		}
		elsif( (length($max1) && ($val>$max1)) ) {
		    $self->report( $item, $BigSister::common::status_codes{"yellow"}, "$name >($val)" );
		}
		else {
		    $self->report( $item, $BigSister::common::status_codes{"green"}, "$name = $val" );
		}
	    }
	}
    }
}

1;
