#! /usr/bin/perl -w

## Michael Buschmann / Cisco
## Added support for ASR1k & ASR9k 10/19/17

## License: This nagios plugin comes with ABSOLUTELY NO WARRANTY. You may redistribute copies of
## the plugins under the terms of the GNU General Public License. For more information about these
## matters, see the GNU General Public License.

use strict;
use Switch ;
use List::Util qw[min max];
use Net::SNMP qw(:snmp);
use FindBin;
use lib "$FindBin::Bin";
use lib "/usr/local/nagios/libexec";
#use utils qw($TIMEOUT %ERRORS &print_revision &support);
use Nagios::Plugin qw(%ERRORS);
use Data::Dumper ;
my $TIMEOUT = 60;

use vars qw($PROGNAME);
use Getopt::Long;
use vars qw($opt_h $opt_V $opt_H $opt_C $opt_v $opt_o $opt_c $opt_w $opt_t $opt_p $opt_k $opt_u $opt_l $opt_d $opt_i $opt_authproto $opt_priv $opt_privproto);
use constant true  => "1" ;
use constant false => "0" ;
$PROGNAME = $0;
my $version = 1;
my $release = 0;
sub print_help ();
sub print_usage ();
sub verbose ;

$SIG{'ALRM'} = sub {
        print "uknown error at line 28 check timed out";
        exit $ERRORS{"UNKNOWN"};
};
alarm($TIMEOUT);


my $opt_d = 0 ;
Getopt::Long::Configure('bundling');
GetOptions
    ("h"   => \$opt_h, "help"         => \$opt_h,
     "u=s" => \$opt_u, "username=s"   => \$opt_u,
     "p=s" => \$opt_p, "password=s"   => \$opt_p,
                       "authprotocol=s" => \$opt_authproto,
     "k=s" => \$opt_k, "key=s"        => \$opt_k,
                       "priv=s"       => \$opt_priv,
                       "privprotocol=s" => \$opt_privproto,
     "V"   => \$opt_V, "version"      => \$opt_V,
     "v=s" => \$opt_v, "snmp=s"       => \$opt_v,
     "C=s" => \$opt_C, "community=s"  => \$opt_C,
     "w=s" => \$opt_w, "warning=s"    => \$opt_w,
     "c=s" => \$opt_c, "critical=s"   => \$opt_c,
     "H=s" => \$opt_H, "hostname=s"   => \$opt_H,
     "d=s" => \$opt_d, "debug=s"      => \$opt_d,
     "i"   => \$opt_i, "sysdescr"     => \$opt_i,
     "l"   => \$opt_l, "list"         => \$opt_l);

if ($opt_V) {
    print($PROGNAME.': $Revision: '.$version.'.'.$release."\n");
    exit $ERRORS{'OK'};
}


if ($opt_h) {
    print_usage();
    exit $ERRORS{'OK'};
}

$opt_H = shift unless ($opt_H);
(print_usage() && exit $ERRORS{'OK'}) unless ($opt_H);

#if(!$snmp_vars{hostname}){&usage_and_exit();}

my $snmp = "2";
if ($opt_v && $opt_v =~ /^[0-9]$/) {
    $snmp = $opt_v;
}

if ($snmp eq "3") {
    if (!$opt_u) {
        print "Option -u (--username) is required for snmpV3\n";
        exit $ERRORS{'OK'};
    }
    if (!$opt_p && !$opt_k) {
        print "Option -k (--key) or -p (--password) is required for snmpV3\n";
        exit $ERRORS{'OK'};
    }elsif ($opt_p && $opt_k) {
        print "Only option -k (--key) or -p (--password) is needed for snmpV3\n";
        exit $ERRORS{'OK'};
    }
}

($opt_C) || ($opt_C = shift) || ($opt_C = "public");

my $name = $0;
$name =~ s/\.pl.*//g;

my ($session, $error);
if ($snmp eq "1" || $snmp eq "2") {
    ($session, $error) = Net::SNMP->session(-hostname => $opt_H, -community => $opt_C, -version => $snmp, -maxmsgsize => "5000", -timeout => 5, -retries => 2);
}elsif ($opt_p && $opt_priv && $opt_authproto && $opt_privproto) {
    ($session, $error) = Net::SNMP->session(-hostname => $opt_H, -version => $snmp,  -username => $opt_u, -authpassword => $opt_p, -authprotocol => $opt_authproto, -privpassword => $opt_priv, -privprotocol => $opt_privproto, -maxmsgsize => "5000", -timeout => 5, -retries => 2);
}elsif ($opt_p && $opt_priv) {
    ($session, $error) = Net::SNMP->session(-hostname => $opt_H, -version => $snmp,  -username => $opt_u, -authpassword => $opt_p, -privpassword => $opt_priv, -maxmsgsize => "5000", -timeout => 5, -retries => 2);
}elsif ($opt_p && $opt_authproto) {
    ($session, $error) = Net::SNMP->session(-hostname => $opt_H, -version => $snmp,  -username => $opt_u, -authpassword => $opt_p, -authprotocol => $opt_authproto, -maxmsgsize => "5000", -timeout => 5, -retries => 2);
}elsif ($opt_p) {
    ($session, $error) = Net::SNMP->session(-hostname => $opt_H, -version => $snmp,  -username => $opt_u, -authpassword => $opt_p, -maxmsgsize => "5000", -timeout => 5, -retries => 2);
}elsif ($opt_k) {
    ($session, $error) = Net::SNMP->session(-hostname => $opt_H, -version => $snmp, -username => $opt_u, -authkey => $opt_k, -maxmsgsize => "5000", -timeout => 5, -retries => 2);
}
# check that session opened
if (!defined($session)) {
    print("SNMP Session : $error\n");
    exit $ERRORS{'UNKNOWN'};
}


my $loglevel = $opt_d ;
my $result ;
my $label ;
my $oid ;
my $unit = "";
my $return_result ;
my $output ;
my $total_connection = 0;
my $key ;
my $value ;
my @perfparse ;
# parse sysDescr
my $outlabel_oid;
if ($opt_i) {
    # sysdescr
    $outlabel_oid = ".1.3.6.1.2.1.1.1.0" ;
}else {
    # sysname
    $outlabel_oid = ".1.3.6.1.2.1.1.5.0" ;
}

verbose("get sysdescr ($outlabel_oid)", "5") ;
my $sysdescr = $session->get_request(-varbindlist => [$outlabel_oid]) ;
if (!defined($sysdescr)) {
    print("DEVICE UNKNOWN: SNMP get_request : ".$session->error()."\n");
    exit $ERRORS{'UNKNOWN'};
}
verbose(" sysdescr is ".$sysdescr->{$outlabel_oid}, "5") ;
my $outlabel = $sysdescr->{$outlabel_oid}.": " ;


my $sysObjectID = '1.3.6.1.2.1.1.2.0';
my %power_status = ();
my $return_code = 0;

# ASR Sensor OIDs
my $entPhysicalDescr = '1.3.6.1.2.1.47.1.1.1.1.2';
my $entSensorValue_volts = '1.3.6.1.4.1.9.9.91.1.1.1.1.4';
my $entSensorStatus = '1.3.6.1.4.1.9.9.91.1.1.1.1.5';
my $entPhysicalName = '1.3.6.1.2.1.47.1.1.1.1.7';

$result = $session->get_request(-varbindlist => [$sysObjectID]) ;
if (!defined($result)) {
    print("UNKNOWN: SNMP get_request : ".$session->error()."\n");
    exit $ERRORS{'UNKNOWN'};
}

my $systemOID = $result->{$sysObjectID};
my $model = '';

if ($systemOID eq '1.3.6.1.4.1.9.1.1041') {
    $model = '3945';
} elsif ( $systemOID eq '1.3.6.1.4.1.9.1.1054') {
    $model = 'sm';
} elsif ( $systemOID eq '1.3.6.1.4.1.9.1.1525' or $systemOID eq '1.3.6.1.4.1.9.1.2144') {
    $model = 'asr1k';
} elsif ( $systemOID eq '1.3.6.1.4.1.9.1.1017' or $systemOID eq '1.3.6.1.4.1.9.12.3.1.3.1507' or $systemOID eq '1.3.6.1.4.1.9.1.1018') {
    $model = 'asr9k';
} else {
    print "UNKNOWN: Unknonwn SNMP sysObjectID: $systemOID";
    exit $ERRORS{'UNKNOWN'};
}

my %ps_current_readings = ();
my %sensor_status = ();
my %get_modules = ();
my %module_names = ();
my %ps_volts = ();

## Only support ASRs
if ($model eq 'asr1k' ) {
    %get_modules = get_ps_fan_mods(1, [$entPhysicalDescr]);
    %module_names = get_mod_names(1, [$entPhysicalName]);
} elsif ($model eq 'asr9k') {
    %ps_current_readings = get_sensor_volts(1, [$entSensorValue_volts]);
    %sensor_status = get_sensor_status(1, [$entSensorStatus]);
    %get_modules = get_ps_fan_mods(1, [$entPhysicalDescr]);
    %module_names = get_mod_names(1, [$entPhysicalName]);
}

#print Dumper(\%get_modules), "\n";
#print Dumper(\%module_names), "\n";
#print Dumper(\%sensor_status), "\n";

alarm(0);

my $fan_message = '';
my $ps_message ='';

if ($model eq 'asr1k' ) {
## No Fan check support for the 1k
## This script will only check DC out and AC in, warning and critical are set for AC in only, a default check for DC out is static
## We expect 12VDC output, anything < dc_out_check VDC is critical

    my $dc_out_check=10 ;
    my $current_index_id;
    my $ps_current;
    my $status;
    my $sensor_id;
    my $m_name ;
    my $name_id;
    my $sensor_volts;
    my $dc_out;
    my $volts_in;

    ### Get a list of power supplies
    for my $modules (keys %get_modules) {
        my $mod_id = $get_modules{$modules}{'id'};
        my $mod_type = $get_modules{$modules}{'1.3.6.1.2.1.47.1.1.1.1.2'};
        $mod_id =~ s/^.//;
        ### Get the power number
        for my $mod_name (keys %module_names) {
            $m_name = $module_names{$mod_name}{'1.3.6.1.2.1.47.1.1.1.1.7'};
            $m_name =~ s/\///g;
            $m_name =~ s/\d$//;
            $name_id =  $module_names{$mod_name}{'id'};
            $name_id =~ s/^.//;
            ### Get PS Input Voltage
            if (($name_id eq $mod_id) && ($m_name =~ m/Vin/)) {
                $m_name =~ s/\s+//g;
                $m_name =~ s/^Vin//g;
                my $entSensorValue_volts = "1.3.6.1.4.1.9.9.91.1.1.1.1.4.$mod_id";
                my $ps_volts = $session->get_request(-varbindlist => [ $entSensorValue_volts ]);
                ($key,$value) = %{$ps_volts};
                $sensor_volts = $value;
                $volts_in=$sensor_volts;
                ### The power supply led warning will come on with anything less than 70V input
                $ps_message .= "$m_name-volts_in=$volts_in,";
                if ($volts_in < $opt_w) {
                    $return_code = $ERRORS{"WARNING"};
                } elsif ($volts_in < $opt_c) {
                    $return_code = $ERRORS{"CRITICAL"};
                }
            ### Get PS Output VDC
            } elsif (($name_id eq $mod_id) && ($m_name =~ m/Vout/)) {
                $m_name =~ s/\s+//g;
                $m_name =~ s/Vout//g;
                my $entSensorValue_volts = "1.3.6.1.4.1.9.9.91.1.1.1.1.4.$mod_id";
                my $ps_volts = $session->get_request(-varbindlist => [ $entSensorValue_volts ]);
                ($key,$value) = %{$ps_volts};
                $sensor_volts = $value;
                $sensor_volts =~ s/..\K(?=.)/./sg;
                $dc_out=$sensor_volts;
                ### Static set DC check, anything less then dc_out_check will be critical
                $ps_message .= "$m_name-dc_volts_out=$dc_out,";
                if ($dc_out < $dc_out_check)  {
                    $return_code = $ERRORS{"CRITICAL"};
                }
            }
        }
    }

} elsif ( $model eq 'asr9k' ) {
    my $current_index_id;
    my $ps_current;
    my $status;
    my $sensor_id;
    my $m_name ;
    my $name_id;
        ### Get a list of Modules
        for my $modules (keys %get_modules) {
            my $mod_id = $get_modules{$modules}{'id'};
            my $mod_type = $get_modules{$modules}{'1.3.6.1.2.1.47.1.1.1.1.2'};
            $mod_id =~ s/^.//;
            ### Get Module status ; (1) OK - Clear ; (2) unavailable - Error ; (3) non-operational - Critical
            for my $s_status (keys %sensor_status) {
                $status = $sensor_status{$s_status}{'1.3.6.1.4.1.9.9.91.1.1.1.1.5'};
                $sensor_id =  $sensor_status{$s_status}{'id'};
                if ($sensor_id eq $mod_id) {
                    for my $mod_name (keys %module_names) {
                        $m_name = $module_names{$mod_name}{'1.3.6.1.2.1.47.1.1.1.1.7'};
                        $m_name =~ s/speed//;
                        $m_name =~ s/current//;
                        $m_name =~ s/\//_/g;
                        $name_id =  $module_names{$mod_name}{'id'};
                        $name_id =~ s/^.//;
                        ### Get PS 'Current' readings
                        if (($name_id eq $mod_id) && ($mod_type eq "Power Module Current Sensor")) {
                            for my $current_hash (keys %ps_current_readings) {
                                $ps_current = $ps_current_readings{$current_hash}{'1.3.6.1.4.1.9.9.91.1.1.1.1.4'};
                                $ps_current =~ s/..$//;
                                $ps_current =~ s/.\K(?=.)/./sg;
                                $current_index_id =  $ps_current_readings{$current_hash}{'id'};
                                ### Match current ID with PS ID
                                if ($name_id eq $current_index_id) {
                                    $ps_message .= "$m_name-amps=$ps_current,";
                                    if (($ps_current < $opt_w) || ($status != 1)) {
                                        $return_code = $ERRORS{"WARNING"};
                                    } elsif (($ps_current < $opt_c) || ($status != 1)) {
                                        $return_code = $ERRORS{"CRITICAL"};
                                    }
                                }
                            }
                        } elsif (($name_id eq $mod_id) && ($mod_type eq "Fan Speed Sensor")) {
                            $fan_message .= "$m_name-oper-status=$status,";
                            if ($status != 1) {
                                $return_code = $ERRORS{"CRITICAL"};
                            }
                        }
                    }
                }
            }
        }
}else{
    print "Sorry this may be an unsupported device, confirm this is a Cisco ASR\n";
    $return_code = $ERRORS{"UNKNOWN"};
}

if ($model eq 'asr1k' ) {
    if ($return_code == 2) {
        print "Power Critical: $ps_message";
        print " | ";
        print "$ps_message";
        exit 2;

    } elsif ($return_code == 1) {
        print "Power Warning: $ps_message";
        print " | ";
        print "$ps_message ";
        exit 1;

    } else {
        print "Power OK: $ps_message";
        print " | ";
        print "$ps_message";
        exit 0;
    }

} elsif ($model eq 'asr9k' ) {
    if ($return_code == 2) {
        print "Fan or Power Critical: $ps_message $fan_message";
        print " | ";
        print "$fan_message";
        exit 2;

    } elsif ($return_code == 1) {
        print "Fan or Power Warning: $ps_message $fan_message";
        print " | ";
        print "$ps_message $fan_message";
        exit 1;

    } else {
        print "Fans and Power OK: $ps_message";
        print " | ";
        print "$ps_message";
        exit 0;
    }
}

sub print_usage () {
    print "Usage:";
    print "$PROGNAME\n";
    print "   -H (--hostname)   Hostname to query - (required)\n";
    print "   -C (--community)  SNMP read community (defaults to public,\n";
    print "                     used with SNMP v1 and v2c\n";
    print "   -v (--snmp_version)  1 for SNMP v1\n";
    print "                        2 for SNMP v2c (deafult)\n";
    print "                        3 for SNMP v3\n";
    print "   -k (--key)        snmp V3 key\n";
    print "   -p (--password)   snmp V3 password\n";
    print "   -u (--username)   snmp v3 username \n";
    print "   --authprotocol    snmp v3 authprotocol md5|sha \n";
    print "   --priv            snmp V3 priv password\n";
    print "   --privprotocol    snmp v3 privprotocol des|aes \n";

    print "   -V (--version)    Plugin version\n";
    print "   -h (--help)       usage help\n\n" ;
    print "   -l (--list)       list probes\n";
    print "   -i (--sysdescr)   use sysdescr instead of sysname for label display\n";
    print "\n" ;
    print "   -d (--debug)      debug level (1 -> 15)";
    print "\n";
}

sub verbose {
    my $message = $_[0];
    my $messagelevel = $_[1] ;


    if ($messagelevel <= $loglevel) {
        print "$message\n" ;
    }
}

sub get_sensor_volts {
    my $num_indexes = shift @_;
    my (@columns) = @_ ;
    verbose("get entries", "10") ;
    if ($snmp == 1) {
        $result = $session->get_entries(-columns => @columns) ;
    }else {
        $result = $session->get_entries(-columns => @columns) ;
    }
    if (!defined($result)) {
        print("UNKNOWN: SNMP get_entries : ".$session->error()."\n");
        exit $ERRORS{'UNKNOWN'};
    }
    my %snmp_values = %{$result} ;
    my $id;
    my %snmp_value_return;
    while(($key,$value) = each(%snmp_values)) {
        if ( $num_indexes eq 1 ) {
            $id = $key ;
            $id =~ s/.*\.([0-9]+)$/$1/;
            $key =~ s/(.*)\.[0-9]*/$1/ ;
            verbose("key=$key, id=$id, value=$value", "15") ;
            $snmp_value_return{$id}{$key} = $value;
            $snmp_value_return{$id}{"id"} = $id ;
        } else {
            $id = $key ;
            verbose("key=$key, id=$id, value=$value", "15") ;
            $snmp_value_return{$id}{$key} = $value;
            $snmp_value_return{$id}{"id"} = $id ;
        }
    }
    return(%snmp_value_return) ;
}

sub get_sensor_status {
    my $num_indexes = shift @_;
    my (@columns) = @_ ;
    verbose("get entries", "10") ;
    if ($snmp == 1) {
        $result = $session->get_entries(-columns => @columns) ;
    }else {
        $result = $session->get_entries(-columns => @columns) ;
    }
    if (!defined($result)) {
        print("UNKNOWN: SNMP get_entries : ".$session->error()."\n");
        exit $ERRORS{'UNKNOWN'};
    }
    my %snmp_values = %{$result} ;
    my $id;
    my %snmp_value_return;
    while(($key,$value) = each(%snmp_values)) {
        if ( $num_indexes eq 1 ) {
            $id = $key ;
            $id =~ s/.*\.([0-9]+)$/$1/;
            $key =~ s/(.*)\.[0-9]*/$1/ ;
            verbose("key=$key, id=$id, value=$value", "15") ;
            $snmp_value_return{$id}{$key} = $value;
            $snmp_value_return{$id}{"id"} = $id ;
        } else {
            $id = $key ;
            verbose("key=$key, id=$id, value=$value", "15") ;
            $snmp_value_return{$id}{$key} = $value;
            $snmp_value_return{$id}{"id"} = $id ;
        }
    }
    return(%snmp_value_return) ;
}

sub get_ps_fan_mods {
    my $num_indexes = shift @_;
    my (@columns) = @_ ;
    verbose("get entries", "10") ;
    if ($snmp == 1) {
        $result = $session->get_entries(-columns => @columns) ;
    }else {
        $result = $session->get_entries(-columns => @columns) ;
    }
    if (!defined($result)) {
        print("UNKNOWN: SNMP get_entries : ".$session->error()."\n");
        exit $ERRORS{'UNKNOWN'};
    }
    my %snmp_values = %{$result} ;
    my %snmp_value_return;
    my %get_mod_name_return;
    my $id;
    if ($model eq 'asr1k' ) {
    while (my ($key, $value) = each %snmp_values) {
            ### Find Power Module and Fan indexes
            if ($value =~ /^Vin $/ || $value =~ /^Vout$/){
                $id = $key ;
                $id =~ s/.*((\.[0-9]+){$num_indexes})$/$1/;
                $key =~ s/(.*)(\.[0-9]+){$num_indexes}$/$1/;
                verbose("key=$key, id=$id, value=$value", "15") ;
                $get_mod_name_return{$id}{$key} = $value;
                $get_mod_name_return{$id}{"id"} = $id ;
                }
            }
    } elsif ($model eq 'asr9k' ) {
    while (my ($key, $value) = each %snmp_values) {
            ### Find Power Module and Fan indexes
            if (($value =~ m/Power Module Current Sensor/i) or ($value =~ m/Fan Speed Sensor/i)){
                $id = $key ;
                $id =~ s/.*((\.[0-9]+){$num_indexes})$/$1/;
                $key =~ s/(.*)(\.[0-9]+){$num_indexes}$/$1/;
                verbose("key=$key, id=$id, value=$value", "15") ;
                $get_mod_name_return{$id}{$key} = $value;
                $get_mod_name_return{$id}{"id"} = $id ;
                }
            }
    }
    return(%get_mod_name_return);
}

sub get_mod_names {
    my $num_indexes = shift @_;
    my (@columns) = @_ ;
    verbose("get entries", "10") ;
    if ($snmp == 1) {
        $result = $session->get_entries(-columns => @columns) ;
    }else {
        $result = $session->get_entries(-columns => @columns) ;
    }
    if (!defined($result)) {
        print("UNKNOWN: SNMP get_entries : ".$session->error()."\n");
        exit $ERRORS{'UNKNOWN'};
    }
    my %snmp_values = %{$result} ;
    my %snmp_value_return;
    my %get_mod_name_return;
    my $id;
    while (my ($key, $value) = each %snmp_values) {
            ### Find all Sensors
            #if ($value =~ m/Power Module Current Sensor/i) {
                $id = $key ;
                $id =~ s/.*((\.[0-9]+){$num_indexes})$/$1/;
                $key =~ s/(.*)(\.[0-9]+){$num_indexes}$/$1/;
                verbose("key=$key, id=$id, value=$value", "15") ;
                $get_mod_name_return{$id}{$key} = $value;
                $get_mod_name_return{$id}{"id"} = $id ;

               # }
            }
    return(%get_mod_name_return);
}

