#!/usr/local/bin/perl 
#
# Check an akcp 
#
# $Header: /home/doke/work/nagios/RCS/check_akcp_sp2,v 1.12 2014/08/08 18:27:53 doke Exp $

use strict;
use warnings;
no warnings 'uninitialized';
no warnings 'redefine';
use Getopt::Long;
use Net::SNMP;
#use Time::HiRes qw( usleep gettimeofday );
use Data::Dumper;

use vars qw( $host $community $timeout $verbose $help $use_snmpv2c
    $prefered_maxmsgsize $mib2 $enterprises $akcp @crits
    @warns @unknowns @oks @ignores $rc $sep %name2ip %snmp_sessions );

$ENV{PATH} .= "/usr/local/bin:/usr/local/net-snmp/bin";

$host = '';
$community = "public";
$timeout = 10;
$verbose = 0;
$help = 0;

$use_snmpv2c = 0;   # akcp only does v1
$prefered_maxmsgsize = 1472;

$mib2 = '1.3.6.1.2.1';
$enterprises = '1.3.6.1.4.1';
$akcp = '1.3.6.1.4.1.3854';

sub usage {
    my( $rc ) = @_;
    print "Usage: $0 [options] -H <host> [-C <community>]
    -H s  hostname
    -C s  snmp community [$community]
    -t n  snmp timeout in seconds [$timeout]
    -v    verbose
    -h    help
";
    exit $rc;
    }

Getopt::Long::Configure ("bundling");
GetOptions(
    'H=s' => \$host,
    'C=s' => \$community,
    't=i' => \$timeout,
    'v+' => \$verbose,
    'h' => \$help,
    );
&usage( 0 ) if ( $help );

&usage( 1 ) if ( ! $host );


check_akcp();

$rc = 0;
$sep = '';
if ( $#crits >= 0 ) {
    $rc = 2;
    print "CRITICAL ", join( ", ", @crits );
    $sep = '; ';
    }
if ( $#warns >= 0 ) {
    $rc = 1 if ( $rc == 0 );
    print $sep, "Warning ", join( ", ", @warns );
    $sep = '; ';
    }
if ( $#unknowns >= 0 ) {
    $rc = 3 if ( $rc == 0 );
    print $sep, "Unknown ", join( ", ", @unknowns );
    }
if ( $rc == 0 || $verbose ) {
    print $sep, "Ok ", join( ", ", @oks );
    $sep = '; ';
    }
if ( $#ignores >= 0 ) {
    print $sep, "Ignoring ", join( ", ", @ignores );
    }
print "\n";
exit $rc;



##################


sub check_akcp { 
    my ( @oids, $result, $table, $i, $j, $status, $temp, $mesg, $nports,
	$nsensors );

    # get the sysDescr, sysName, and sysUpTime
    my $sysDescr_oid = "$mib2.1.1.0";
    my $sysUpTime_oid = "$mib2.1.3.0";
    my $sysName_oid = "$mib2.1.5.0";
    @oids = ( $sysDescr_oid, $sysUpTime_oid, $sysName_oid );
    $result = snmp_get( $host, $community, "sysDescr, sysUptime and sysName", \@oids );
    if ( ! defined( $result ) ) {
	push @warns, "couldn't get sysDescr";
	return;
	}
    my $sysDescr = $result->{ $sysDescr_oid };
    my $sysUpTime = $result->{ $sysUpTime_oid };
    my $sysName = $result->{ $sysName_oid };
    if ( $verbose ) {
	print "sysDescr = $sysDescr\n";
	print "sysUpTime = $sysUpTime\n";
	print "sysName = $sysName\n";
	}

    $sysName =~ s/^([\w\d-]+)\..*/$1/;  # strip off the domain name for brevity
    push @oks, $sysName;
    push @oks, "up " . &ticks_to_str( $sysUpTime );

    # They usually need some time to settle after booting. 
    # Don't bother doing anything else if it's only been up for 5 minutes 
    if ( $sysUpTime < ( 5 * 60 * 100 ) ) { 
	push @warns, sprintf( "rebooted at %s, sysUpTime %u", 
	    scalar( localtime( time() - ( $sysUpTime / 100 ) ) ),
	    $sysUpTime );
	return;
	}

    $nports = 0;
    $nsensors = 0;

    # hhmsSensorArrayTempDescription = 1.3.6.1.4.1.3854.1.2.2.1.16.1.1
    # hhmsSensorArrayTempDegree = 1.3.6.1.4.1.3854.1.2.2.1.16.1.3
    # hhmsSensorArrayTempStatus = 1.3.6.1.4.1.3854.1.2.2.1.16.1.4
    #     noStatus(1),
    #     normal(2),
    #     highWarning(3),
    #     highCritical(4),
    #     lowWarning(5),
    #     lowCritical(6),
    #     sensorError(7)
    # hhmsSensorArrayTempOnline = 1.3.6.1.4.1.3854.1.2.2.1.16.1.5
    #     online(1), offline(2)
    # hhmsSensorArrayTempDegreeType = 1.3.6.1.4.1.3854.1.2.2.1.16.1.12
    #     fahr(0), celsius(1)

#   |  +-- -RW- String    sensorProbeTempDescription(1)
#   |  |        Textual Convention: DisplayString
#   |  +-- -RW- String    sensorProbeTempLocation(2)
#   |  |        Textual Convention: DisplayString
#   |  +-- -R-- INTEGER   sensorProbeTempDegree(3)
#   |  +-- -R-- EnumVal   sensorProbeTempStatus(4)
#   |  |        Values: noStatus(1), normal(2), highWarning(3), highCritical(4), lowWarning(5), lowCritical(6), sensorError(7)
#   |  +-- -R-- EnumVal   sensorProbeTempOnline(5)
#   |  |        Values: online(1), offline(2)
#   |  +-- -RW- EnumVal   sensorProbeTempGoOnline(6)
#   |  |        Values: goOnline(1), goOffline(2)
#   |  +-- -RW- INTEGER   sensorProbeTempHighWarning(7)
#   |  +-- -RW- INTEGER   sensorProbeTempHighCritical(8)
#   |  +-- -RW- INTEGER   sensorProbeTempLowWarning(9)
#   |  +-- -RW- INTEGER   sensorProbeTempLowCritical(10)
#   |  +-- -RW- INTEGER   sensorProbeTempRearm(11)
#   |  +-- -RW- EnumVal   sensorProbeTempDegreeType(12)
#   |  |        Values: fahr(0), celsius(1)
#   |  +-- -RW- EnumVal   sensorProbeTempSensorType(13)
#   |  |        Values: type1(0), type2(1)
#   |  +-- -R-- INTEGER   sensorProbeTempDegreeRaw(14)
#   |  +-- -RW- EnumVal   sensorProbeTempEmailTrapLimit(16)
#   |  |        Values: disable(0), enable(1)
#   |  +-- -RW- String    sensorProbeTempEmailTrapSchedule(17)
#   |  |        Textual Convention: DisplayString
#   |  +-- -RW- INTEGER   sensorProbeTempEmailTrapInterval(18)



    $table = snmp_table( $host, $community, 'sensorProbeTempTable', 
	"$akcp.1.2.2.1.16", [ 1, 3, 4, 5, 12, 14 ] );
    if ( $table ) { 
	my $rows = scalar( @$table ) - 1;
	foreach $i ( 0 .. $rows ) {
	    $nports++;

	    $verbose && print "port $i $table->[ $i ][ 1 ], $table->[ $i ][ 4 ], $table->[ $i ][ 5 ], $table->[ $i ][ 12 ], $table->[ $i ][ 14 ]\n";

	    # first see if sensor is online
	    next if ( $table->[ $i ][ 5 ] != 1 );
	    $nsensors++;

	    if ( defined $table->[ $i ][ 14 ] ) { 
		$temp = $table->[ $i ][ 14 ] / 10.0;
		}
	    else { 
		$temp = $table->[ $i ][ 3 ];
		}

	    $mesg = sprintf "%s %0.1f%s", 
		$table->[ $i ][ 1 ], $temp,
		( ( $table->[ $i ][ 12 ] ) ? "C" : "F" );

	    $status = $table->[ $i ][ 4 ];
	    if ( $status == 2 ) { 
		push @oks, $mesg;
		}
	    elsif ( $status == 4 || $status == 6 ) { 
		push @crits, $mesg;
		}
	    elsif ( $status == 3 || $status == 5 ) { 
		push @warns, $mesg;
		}
	    elsif ( $status == 1 ) { 
		push @warns, "$mesg noStatus";
		}
	    elsif ( $status == 7 ) { 
		push @warns, "$mesg sensorError";
		}
	    else { 
		push @unknowns, "$mesg unknownStatus";
		}
	    }
	}
    $verbose && print "nports $nports\n";


#   +--sensorProbeHumidityEntry(1)
#   |  |  Index: sensorProbeHumidityIndex
#   |  |
#   |  +-- -RW- String    sensorProbeHumidityDescription(1)
#   |  |        Textual Convention: DisplayString
#   |  +-- -RW- String    sensorProbeHumidityLocation(2)
#   |  |        Textual Convention: DisplayString
#   |  +-- -R-- INTEGER   sensorProbeHumidityPercent(3)
#   |  |        Range: -32768..32767
#   |  +-- -R-- EnumVal   sensorProbeHumidityStatus(4)
#   |  |        Values: noStatus(1), normal(2), highWarning(3), highCritical(4), lowWarning(5), lowCritical(6), sensorError(7)
#   |  +-- -R-- EnumVal   sensorProbeHumidityOnline(5)
#   |  |        Values: online(1), offline(2)
#   |  +-- -RW- EnumVal   sensorProbeHumidityGoOnline(6)
#   |  |        Values: goOnline(1), goOffline(2)
#   |  +-- -RW- INTEGER   sensorProbeHumidityHighWarning(7)
#   |  +-- -RW- INTEGER   sensorProbeHumidityHighCritical(8)
#   |  +-- -RW- INTEGER   sensorProbeHumidityLowWarning(9)
#   |  +-- -RW- INTEGER   sensorProbeHumidityLowCritical(10)
#   |  +-- -RW- INTEGER   sensorProbeHumidityRearm(11)
#   |  |        Values: testNoStatus(1), testNormal(2), testHighWarning(3), testHighCritical(4), testLowWarning(5), testLowCritical(6), sensorError(7)
#   |  +-- -R-- INTEGER   sensorProbeHumidityRaw(13)
#   |  +-- -RW- INTEGER   sensorProbeHumidityLowVoltage(14)
#   |  |        Range: 0..2
#   |  +-- -RW- INTEGER   sensorProbeHumidityHighVoltage(15)

    # raw humidity is nonsense, don't bother

    $table = snmp_table( $host, $community, 'sensorProbeHumidityTable', 
	"$akcp.1.2.2.1.17", [ 1, 3, 4, 5 ] );
    if ( $table ) { 
	my $rows = scalar( @$table ) - 1;
	foreach $i ( 0 .. $rows ) {

	    $verbose && print "port $i $table->[ $i ][ 1 ], $table->[ $i ][ 4 ], $table->[ $i ][ 5 ], $table->[ $i ][ 3 ]\n";

	    # first see if sensor is online
	    next if ( $table->[ $i ][ 5 ] != 1 );
	    $nsensors++;

	    $mesg = sprintf "%s %d%%", 
		$table->[ $i ][ 1 ], $table->[ $i ][ 3 ];

	    $status = $table->[ $i ][ 4 ];
	    if ( $status == 2 ) { 
		push @oks, $mesg;
		}
	    elsif ( $status == 4 || $status == 6 ) { 
		push @crits, $mesg;
		}
	    elsif ( $status == 3 || $status == 5 ) { 
		push @warns, $mesg;
		}
	    elsif ( $status == 1 ) { 
		push @warns, "$mesg noStatus";
		}
	    elsif ( $status == 7 ) { 
		push @warns, "$mesg sensorError";
		}
	    else { 
		push @unknowns, "$mesg unknownStatus";
		}
	    }
	}


#+--sensorProbeTemperatureArrayPort1(1)
#   |
#   +-- -R-- INTEGER   sensorProbeTemperatureArrayPort1Number(1)
#   |
#   +--sensorProbeTemperatureArrayPort1Table(2)
#      |
#      +--sensorProbeTemperatureArrayPort1Entry(1)
#         |  Index: sensorProbeTemperatureArrayPort1Index
#         |
#         +-- -R-- INTEGER   sensorProbeTemperatureArrayPort1Index(1)
#         |        Range: 0..7
#         +-- -RW- String    sensorProbeTemperatureArrayPort1Description(2)
#         |        Textual Convention: DisplayString
#         +-- -R-- INTEGER   sensorProbeTemperatureArrayPort1Value(3)
#         +-- -R-- EnumVal   sensorProbeTemperatureArrayPort1Status(4)
#         |        Values: noStatus(1), normal(2), highWarning(3), highCritical(4), lowWarning(5), lowCritical(6), sensorError(7)
#         +-- -R-- EnumVal   sensorProbeTemperatureArrayPort1Online(5)
#         |        Values: online(1), offline(2)
#         +-- -RW- EnumVal   sensorProbeTemperatureArrayPort1GoOnline(6)
#         |        Values: goOnline(1), goOffline(2)
#         +-- -RW- INTEGER   sensorProbeTemperatureArrayPort1HighWarning(7)
#         +-- -RW- INTEGER   sensorProbeTemperatureArrayPort1HighCritical(8)
#         +-- -RW- INTEGER   sensorProbeTemperatureArrayPort1LowWarning(9)
#         +-- -RW- INTEGER   sensorProbeTemperatureArrayPort1LowCritical(10)
#         +-- -RW- INTEGER   sensorProbeTemperatureArrayPort1Rearm(11)
#         +-- -RW- EnumVal   sensorProbeTemperatureArrayPort1DegreeType(12)
#         |        Values: fahr(0), celsius(1)
#         +-- -R-- INTEGER   sensorProbeTemperatureArrayPort1DegreeRaw(14)
#         +-- -RW- INTEGER   sensorProbeTemperatureArrayPort1Offset(15)
#         +-- -RW- String    sensorProbeTemperatureArrayPort1URL(16)
#         |        Textual Convention: DisplayString
#         +-- -RW- EnumVal   sensorProbeTemperatureArrayPort1OpenURL(17)
#         |        Values: cur-window(0), new-window(2)
#         +-- -RW- EnumVal   sensorProbeTemperatureArrayPort1DatacollectType(18)
#         |        Values: average(1), highest(2), lowest(3)


    # temp_array .1.3.6.1.4.1.3854.1.2.2.1.19.33.$port.table(2).entry(1).value(3).$inst


    $table = snmp_table_akcp( $host, $community, 'sensorProbeTemperatureArraySensor', 
	"$akcp.1.2.2.1.19.33", $nports, [ 2, 4, 5, 12, 14 ] );
    #$verbose && print "table dump: ", Dumper( $table ), "\n";
    if ( $table ) { 
	my $rows = scalar( @$table ) - 1;
	#$verbose && print "rows $rows\n";
	foreach $i ( 0 .. $rows ) {
	    next unless ( $table->[ $i ] );
	    my $cols = scalar( @{$table->[$i]} ) - 1;
	    #$verbose && print "cols $cols\n";
	    next if ( $cols < 0 );
	    foreach $j ( 0 .. $cols ) {

		if ( $verbose ) { 
		    my $k;
		    print "$i.$j";
		    foreach $k ( 1 .. 14 ) { 
			if ( defined $table->[ $i ][ $j ][ $k ] ) { 
			    print ", ", $table->[ $i ][ $j ][ $k ];
			    }
			}
		    print "\n";
		    }

		# first see if sensor is online
		next if ( $table->[ $i ][ $j ][ 5 ] != 1 );
		$nsensors++;

		$mesg = sprintf "%s %0.1f%s", 
		    $table->[ $i ][ $j ][ 2 ], $table->[ $i ][ $j ][ 14 ] / 10.0,
		    ( ( $table->[ $i ][ $j ][ 12 ] ) ? "C" : "F" );

		$status = $table->[ $i ][ $j ][ 4 ];
		if ( $status == 2 ) { 
		    push @oks, $mesg;
		    }
		elsif ( $status == 4 || $status == 6 ) { 
		    push @crits, $mesg;
		    }
		elsif ( $status == 3 || $status == 5 ) { 
		    push @warns, $mesg;
		    }
		elsif ( $status == 1 ) { 
		    push @warns, "$mesg noStatus";
		    }
		elsif ( $status == 7 ) { 
		    push @warns, "$mesg sensorError";
		    }
		else { 
		    push @unknowns, "$mesg unknownStatus";
		    }
		}
	    }
	}


    # there's no humidity array?



    # sensorProbeSwitchTable
    #    sensorProbeSwitchDescription(1)
    #    sensorProbeSwitchLocation(2)
    #    sensorProbeSwitchStatus(3)
    #    sensorProbeSwitchOnline(4)
    #    sensorProbeSwitchGoOnline(5)
    #    sensorProbeSwitchDirection(6)
    #    sensorProbeSwitchNormalState(7)

    $table = snmp_table( $host, $community, 'sensorProbeTempTable', 
	"$akcp.1.2.2.1.18", [ 1, 3, 4, 6 ] );
    if ( $table ) { 
	my $rows = scalar( @$table ) - 1;
	foreach $i ( 0 .. $rows ) {
	    $nports++;

	    $verbose && print "port $i $table->[ $i ][ 1 ], $table->[ $i ][ 3 ], $table->[ $i ][ 4 ], $table->[ $i ][ 6 ]\n";

	    # first see if sensor is online
	    next if ( $table->[ $i ][ 4 ] != 1 );

	    # and it's an input
	    next if ( $table->[ $i ][ 6 ] != 0 );

	    $nsensors++;

	    $status = $table->[ $i ][ 3 ];

	    $mesg = $table->[ $i ][ 1 ];

	    if ( $status == 2 ) {  # normal
		push @oks, "$mesg ok";
		}
	    elsif ( $status == 4 || $status == 6 ) {  # highcritical(4), lowcritical(6)
		push @crits, "$mesg bad";
		}
	    elsif ( $status == 3 || $status == 5 ) { 
		push @warns, "$mesg bad";
		}
	    elsif ( $status == 1 ) { 
		push @warns, "$mesg noStatus";
		}
	    elsif ( $status == 7 ) { 
		push @warns, "$mesg sensorError";
		}
	    else { 
		push @unknowns, "$mesg unknownStatus";
		}
	    }
	}
    $verbose && print "nports $nports\n";

    if ( $nsensors < 1 ) { 
	push @warns, "no sensors found";
	}
    }








# walk a table 
# With the optional columns parameter, just walk those selected columns out of it.  
# Return a pointer to a 2d array.  
sub snmp_table {
    my( $dev, $community, $name, $baseoid, $columns ) = @_;
    my( $result, $rows, $oid, $val, $col, $row, $data, @oids, $maxmsgsize,
	$session );

    $verbose && print "walking $name table $baseoid, ", join( ', ', @$columns ), "\n";

    $session = snmp_session( $dev, $community );
    if ( ! defined( $session ) ) {
        return undef;
	}
    #$session->debug( 0xff );

    if (  defined $columns ) { 
	foreach $col ( @$columns ) { 
	    $verbose && print "< $baseoid.1.$col\n";
	    push @oids, "$baseoid.1.$col";
	    }
	}
    else { 
	push @oids, "$baseoid.1";
	}

    $data = [];

    foreach $oid ( @oids ) { 
	foreach $maxmsgsize ( 1472, 1600, 1800, 2048, 4096, 9000 ) { 
	    $session->max_msg_size( $maxmsgsize );
	    $verbose > 1 && print "snmp get_entries tring maxmsgsize $maxmsgsize\n";

	    # get_entries has a bug where index 0 stops the result
	    # it thinks it's a duplicate?
	    #$result = $session->get_entries( -columns => \@oids );

	    # get_table doesn't do this stupidity
	    $result = $session->get_table( -baseoid => $oid );

	    if ( defined( $result ) ) {
		last;
		}
	    elsif ( $session->error() =~ m/Requested table is empty/ ) {
		# ok
		#return $data;
		last;
		}
	    elsif ( $session->error() =~ m/Message size exceeded buffer maxMsgSize/ ) {
		# retry with different size
		$verbose && print "snmp get_entries failed with maxmsgsize $maxmsgsize\n";
		next;
		}
	    else { 
		$verbose && print "snmp get_entries failed with error ", $session->error(), "\n";
		last;
		}
	    # else try a bigger maxmessage size
	    }

	if ( ! defined( $result ) ) {
	    #push @crits, sprintf "error walking $name table on %s: %s",
		#$session->hostname, $session->error();
	    next;
	    }

	foreach $oid ( sort keys %$result ) {
	    $val = $result->{ $oid };
	    $verbose > 1 && print "> $oid = $val\n";
	    next if ( $val eq 'endOfMibView' );
	    if ( $oid =~ m/.*\.(\d+)\.(\d+)$/ ) {
		$col = $1; $row = $2;
		$data->[$row][$col] = $val;
		}
	    }
	}

    if ( scalar( @$data ) < 1 ) {
        $verbose && printf "no rows in $name table on %s: %s",
            $session->hostname, $session->error();
        push @unknowns, sprintf "no rows in $name table on %s: %s",
            $session->hostname, $session->error();
	return undef;
        }

    return $data;
    }







# walk an akcp sensorProbeOtherSensor sub-table
# With the optional columns parameter, just walk those selected columns out of it.  
# Return a pointer to a 3d array.  
sub snmp_table_akcp {
    my( $dev, $community, $name, $baseoid, $nports, $columns ) = @_;
    my( $result, $rows, $oid, $val, $col, $port, $row, $data, @oids,
	$maxmsgsize, $session );

    $verbose && print "snmp_table_akcp( $dev, $community, $name, $baseoid, $nports, ( @$columns ) )\n";
    $verbose && print "walking $name table $baseoid, ", join( ', ', @$columns ), "\n";

    $session = snmp_session( $dev, $community );
    if ( ! defined( $session ) ) {
        return undef;
	}

    if ( defined $columns ) { 
	foreach $port ( 1 .. $nports ) {    # depends on model!
	    foreach $col ( @$columns ) { 
		$verbose && print "<$baseoid.$port.2.1.$col\n";
		push @oids, "$baseoid.$port.2.1.$col";
		}
	    }	
	}
    else { 
	push @oids, $baseoid;
	}

    $data = [];

    foreach $oid ( @oids ) { 
	$verbose && print "walking $name table $oid\n";
	foreach $maxmsgsize ( 1472, 1600, 1800, 2048, 4096, 9000 ) { 
	    $session->max_msg_size( $maxmsgsize );
	    $verbose > 1 && print "snmp get_entries tring maxmsgsize $maxmsgsize\n";

	    # get_entries has a bug where index 0 stops the result
	    # it thinks it's a duplicate?
	    #$result = $session->get_entries( -columns => \@oids );

	    # get_table doesn't do this stupidity
	    $result = $session->get_table( -baseoid => $oid );

	    if ( defined( $result ) ) {
		last;
		}
	    elsif ( $session->error() =~ m/Requested table is empty/ ) {
		# ok
		#return $data;
		last;
		}
	    elsif ( $session->error() =~ m/Message size exceeded buffer maxMsgSize/ ) {
		# retry with different size
		$verbose && print "snmp get_entries failed with maxmsgsize $maxmsgsize\n";
		last;
		}
	    else { 
		$verbose && print "snmp get_entries failed with error ", $session->error(), "\n";
		last;
		}
	    # else try a bigger maxmessage size
	    }
	if ( ! defined( $result ) ) {
	    #push @crits, sprintf "error walking $name table on %s: %s",
		#$session->hostname, $session->error();
	    return undef;
	    }

	foreach $oid ( sort keys %$result ) {
	    $val = $result->{ $oid };
	    $verbose > 1 && print ">$oid = $val\n";
	    next if ( $val eq 'endOfMibView' );
	    if ( $oid =~ m/.*\.(\d+)\.2\.1\.(\d+)\.(\d+)$/ ) {
		( $port, $col, $row ) = ( $1, $2, $3 );
		$data->[$port][$row][$col] = $val;
		}
	    }
	}

    if ( scalar( @$data ) < 1 ) {
        push @unknowns, sprintf "no rows in $name table on %s: %s",
            $session->hostname, $session->error();
	return undef;
        }

    return $data;
    }





sub snmp_walk {
    my( $dev, $community, $name, $baseoid ) = @_;
    my( $session, $result );

    $verbose && print "snmp_walk $dev $name\n";

    $session = snmp_session( $dev, $community );
    if ( ! defined( $session ) ) {
        return undef;
	}
    $result = $session->get_table( -baseoid => $baseoid );
    #print "session error ", $session->error(), "\n";
    if ( ! defined( $result )
            && $session->error() !~ m/Requested table is empty/ ) {
	#warn sprintf( "error walking $name table on %s: %s",
        #    $session->hostname, $session->error() );
        #push @unknowns, sprintf( "error walking $name table on %s: %s",
        #    $session->hostname, $session->error() );
	return undef;
        }
    return $result;
    }



sub snmp_get {
    my( $dev, $community, $name, $oids ) = @_;
    my( $session, $result, $oid, $val );

    $verbose && print "snmp_get $dev $name\n";

    $session = snmp_session( $dev, $community );
    if ( ! defined( $session ) ) {
        return undef;
	}
    $result = $session->get_request( -varbindlist => $oids );
    if ( ! defined( $result ) ) {
        #warn "snmpget error: ", $session->error(), "\n";
        return undef;
        }

    foreach $oid ( @$oids ) {
	if ( ! exists $result->{ $oid } ) {
	    #warn "snmpget error: requested oid not in response\n";
	    $verbose && print "    $oid: no response\n";
	    $result->{ $oid } = undef;
	    }
	elsif ( $result->{ $oid } eq 'noSuchInstance' )  {
	    $verbose && print "    $oid: noSuchInstance\n";
	    $result->{ $oid } = undef;
	    }
	elsif ( $result->{ $oid } eq 'noSuchObject' )  {
	    $verbose && print "    $oid: noSuchObject\n";
	    $result->{ $oid } = undef;
	    }
	elsif ( $verbose ) {
	    $val = $result->{ $oid };
	    $val =~ tr/\040-\176//cd;
	    print "    $oid: $val\n";
	    }
	}

    return $result;
    }





sub snmp_session { 
    my( $dev, $community ) = @_;
    my( $ip, $session, $error );
    
    if ( $dev =~ m/^\d[\d\.]+$/ ) { 
        $ip = $dev;
        }
    else { 
        $ip = name2ip( $dev );
	if ( ! $ip ) { 
	    print "unable to resolve $dev to an ip\n";
	    return undef;
	    }
        }

    if ( exists $snmp_sessions{ "$ip,$community" } ) { 
        return $snmp_sessions{ "$ip,$community" };
        }

    ( $session, $error ) = Net::SNMP->session(
        -version => $use_snmpv2c ? 'snmpv2c' : 'snmpv1',
        -hostname => $ip,
        -community => $community,
        -timeout => $timeout,
        -retries => 3,
        #-debug => 0x02
        );
    if ( ! defined( $session ) ) {
        push @unknowns, "snmp setup error: $error\n";
        return undef;
        }
    $session->translate( [ '-octetstring' => 0, '-timeticks' => 0 ] );

    $snmp_sessions{ "$ip,$community" } = $session;
    return $session;
    }




sub name2ip { 
    my( $name ) = @_;
    my( $ip, $a, $b, $c, $d );

    if ( defined( $name2ip{ $name } ) ) {  
        $ip = $name2ip{ $name };
        }
    elsif ( $name =~ m/^\s*(\d+\.\d+\.\d+\.\d+)\s*$/ ) { 
        $ip = $1;
        $name2ip{ $name } = $ip;
        $name2ip{ $1 } = $ip;
        }
    else { 
        $ip = gethostbyname( $name );
        if ( ! $ip ) { 
            return undef;
            }
        ($a,$b,$c,$d) = unpack( 'C4', $ip );
        $ip = "$a.$b.$c.$d";
        $name2ip{ $name } = $ip;
        }
    return $ip;
    }





sub ticks_to_str { 
    my( $ticks ) = @_;
    my( @intervals, @letters, $interval, $str, $i, $n, $started );

    @intervals = ( 
	60480000,
	8640000,
	360000,
	6000,
	100,
	);

    @letters = ( 
	'w ',
	'd ',
	'h ',
	'm ',
	's',
	);

    $str = '';
    for ( $i = 0; $i < 5; $i++ ) { 
	$interval = $intervals[ $i ];
	if ( $ticks >= $interval || $started ) { 
	    $n = int( $ticks / $interval );
	    $ticks -= $n * $interval;
	    $str .= sprintf( "%u%s", $n, $letters[ $i ] );
	    $started = 1;   # show days in 3weeks 0days 3hours
	    }
	}
    return $str;
    }

