#!/usr/bin/perl -w 
# nagios: -epn
############################## check_snmp_transmode #########
# Date: 2015-07-29
# Neu erstellt aus check_snmp_env.pl
# - nur noch Transmode
# - reorg: nur noch Alarme
# Author  : Klaus Franken, kfranken@noris.de
#################################################################
#
# Help : ./check_snmp_transmode -h

use strict;
use warnings;
use Net::SNMP;
use Getopt::Long;
use Switch;
use Data::Dumper;

# Nagios specific

use lib "/usr/lib/nagios/plugins";
use utils qw(%ERRORS $TIMEOUT);
my $TIMEOUT = 42;

my @Nagios_state = ("UNKNOWN","OK","WARNING","CRITICAL"); # Nagios states coding

# SNMP Datas
# TRANSMODE
my $transmodeEnvMonMIB    = '1.3.6.1.4.1.8708.2'; # Transmode env base table
my %transmodeTables = (
    Alarm => $transmodeEnvMonMIB . '.1.2.1',	  # Transmode Alarm base table
    Port  => $transmodeEnvMonMIB . '.4.2.2.1.1',  # Transmode Port base table (wdm:)
    Port3 => $transmodeEnvMonMIB . '.46.2.2.1.1', # Transmode Port base table (wdm:) 3. Gen. Cards
    Board => $transmodeEnvMonMIB . '.11.2.3.1.1', # Transmode Board base table
    Power => $transmodeEnvMonMIB . '.11.2.4.1.1', # Transmode Power Supply base table
    Fan   => $transmodeEnvMonMIB . '.11.2.5.1.1', # Transmode Fan base table
);
my $transmodeAlarmHighestSeverity   = $transmodeTables{Alarm} . '.19.0';  # Transmode Alarm Highest Severity

my $transmodePortDesc   = $transmodeTables{Port} . '.2';  # Transmode Port Description
my $transmodePort3Desc  = $transmodeTables{Port3} . '.2'; # Transmode Port Description, 3. Gen. Cards
my $transmodeClientDesc = "1.3.6.1.4.1.8708.2.27.2.2.1.1.2"; # clientIfName (client:)
my $transmodeOADesc     = "1.3.6.1.4.1.8708.2.19.2.2.1.1.2"; # oaIfName (oa:)
my $transmodeOSCDesc    = "1.3.6.1.4.1.8708.2.20.2.2.1.1.2"; # (osc:)
my $transmodeAdminPortState = $transmodeTables{Port} . '.19'; # Transmode Port Admin State
my $transmodePortState  = $transmodeTables{Port} . '.20'; # Transmode Port Operational State
my $transmodePortSignal = $transmodeTables{Port} . '.21'; # Transmode Port Loss of Signal
my $transmodePortFrame  = $transmodeTables{Port} . '.31'; # Transmode Port Loss of Frame
my $transmodePortTx     = $transmodeTables{Port} . '.104'; # Transmode Port Loss of Frame
my $transmodePortRx     = $transmodeTables{Port} . '.105'; # Transmode Port Loss of Frame

my $transmodeBoardIndex = $transmodeTables{Board} . '.1'; # Transmode Board Index
my $transmodeBoardDesc  = $transmodeTables{Board} . '.2'; # Transmode Board Description
my $transmodeBoardAdminState = $transmodeTables{Board} . '.11'; # Transmode Board Admin State
my $transmodeBoardState = $transmodeTables{Board} . '.12'; # Transmode Board Operational State

my $transmodePowerIndex = $transmodeTables{Power} . '.1'; # Transmode Power Index
my $transmodePowerDesc  = $transmodeTables{Power} . '.2'; # Transmode Power Description
my $transmodePowerState = $transmodeTables{Power} . '.11'; # Transmode Power Operational State
my $transmodePowerAC    = $transmodeTables{Power} . '.13'; # Transmode Power AC
my $transmodePowerDC    = $transmodeTables{Power} . '.14'; # Transmode Power DC

my $transmodeFanIndex  = $transmodeTables{Fan} . '.1'; # Transmode Fan Index
my $transmodeFanDesc   = $transmodeTables{Fan} . '.2'; # Transmode Fan Description
my $transmodeFanState  = $transmodeTables{Fan} . '.7'; # Transmode Fan Operational State
my $transmodeFanFailed = $transmodeTables{Fan} . '.9'; # Transmode Fan Failed

my $transmodeAlarmEntry = '1.3.6.1.4.1.8708.2.1.2.2.1.1';

# Globals

my $Version='6.6';

my $o_host = 	undef; 		# hostname
my $o_community = undef; 	# community
my $o_port = 	161; 		# port
my $o_help=	undef; 		# wan't some help ?
my $o_verb=	undef;		# verbose mode
my $o_version=	undef;		# print version
my $o_timeout=  undef; 		# Timeout (Default 5)
my $o_version2= undef;          # use snmp v2c
my $o_domain= 	undef;          # use IPv6

# SNMPv3 specific
my $o_login=	undef;		# Login for snmpv3
my $o_passwd=	undef;		# Pass for snmpv3
my $v3protocols=undef;	# V3 protocol list.
my $o_authproto='md5';		# Auth protocol
my $o_privproto='des';		# Priv protocol
my $o_privpass= undef;		# priv password

# Transmode specific
my $o_env         = undef;
my $o_allports    = undef;
my $o_allalarms   = undef;
my $o_noportcheck = undef;
my $o_check_ports =	undef;

# functions

sub p_version { print "check_snmp_env version : $Version\n"; }

sub print_usage {
    print "Usage: $0 [-v] [-6] -H <host> -C <snmp_community> [-2] | (-l login -x passwd [-X pass -L <authp>,<privp>])  [-p <port>] [-t <timeout_per_SNMP_request>] [-V] [-all] [--env] [--port <port> [--port <port2> ...]] [--allports] [--noportcheck]\n";
}

sub isnnum { # Return true if arg is not a number
  my $num = shift;
  if ( $num =~ /^(\d+\.?\d*)|(^\.\d+)$/ ) { return 0 ;}
  return 1;
}

sub help {
   print "\nSNMP environmental Monitor for Nagios version ",$Version,"\n";
   print "GPL Licence, (c)2006-2007 Patrick Proy\n";
   print "GPL Licence, (c)2015 Klaus.Franken\@noris.de\n\n";
   print_usage();
   print <<EOT;

Options:
-v, --verbose
   print extra debugging information 
-h, --help
   print this help message
-6, --use-ipv6
   Use IPv6 connection
-H, --hostname=HOST
   name or IP address of host to check
-C, --community=COMMUNITY NAME
   community name for the host's SNMP agent (implies v1 protocol)
-2, --v2c
   Use snmp v2c
-l, --login=LOGIN ; -x, --passwd=PASSWD
   Login and auth password for snmpv3 authentication 
   If no priv password exists, implies AuthNoPriv 
-X, --privpass=PASSWD
   Priv password for snmpv3 (AuthPriv protocol)
-L, --protocols=<authproto>,<privproto>
   <authproto> : Authentication protocol (md5|sha : default md5)
   <privproto> : Priv protocole (des|aes : default des) 
-P, --snmpport=PORT
   SNMP port (Default 161)
-t, --timeout=INTEGER
   timeout for SNMP in seconds (Default: 5)
-V, --version
   prints version number
-e, --env
    check Environent alarams (fan, power (everything not beeing an port))
-a, --allports
    check all ports
-p, --port
    check only given port(s)
-A, --all
    check env and all ports (default)
-n, --noportcheck
    do not check that given ports exists (enabled by default)
EOT
}

# For verbose output
sub verb { my $t=shift; print $t,"\n" if defined($o_verb) ; }

sub check_this_obj {
    my $obj_id = shift;

    if ($o_allalarms ) {
        return(1);
    }

    $obj_id =~ m/^(\w*):(.*)/;
    my $objname = $1 // '';
    my $portname = $2 // '';
    $objname =~ s/client\w*/client/; # 
    $objname =~ s/line\w*/line/;
    my $obj_typ='env';
    my @port_praefixe = qw/wdm client osc oa passive line port/;
    if ( grep { $objname eq $_ } @port_praefixe ) {
        $obj_typ='port';
    }

    my @ports = @$o_check_ports;
    if (grep( { $portname eq $_ } @ports)) {
        return(1)
    }
    if ($obj_typ eq 'port' and $o_allports) {
        return(1);
    }
    if ($obj_typ eq 'env' and $o_env) {
        return(1);
    }
    return(0);
}

sub check_ports_exist {
    my $session = shift;
    my @ports = @_;

    my $portdescr   = undef;
    my $portdescr3  = undef;
    my $clientdescr = undef;
    my $oadescr     = undef;
    my $oscdescr    = undef;

    my %port2id;
    verb("searching Ports");
    SEARCH_PORT:
    for my $port ( @ports ) {
        $port2id{$port} = '';

        # suche in 'wdmIfName'
        verb("  checking port $port in $transmodePortDesc");
        if (not $portdescr) {
            $portdescr =
                ( Net::SNMP->VERSION lt 4 )
                ? $session->get_table($transmodePortDesc )
                : $session->get_table( Baseoid => $transmodePortDesc);
            verb(" portdescr   ($transmodePortDesc): " . Dumper($portdescr));
        }
        for my $id (keys(%$portdescr)) {
            my $portname = $$portdescr{$id};
            $portname =~ s/^(\w)*://;
            if ($port eq $portname) {
                verb("   GEFUNDEN: $port: $id");
                $port2id{$port} = $id;
                next SEARCH_PORT;
            }
        }

        # suche in 'wdmIfName' 3. Gen
        verb("  checking port $port in $transmodePort3Desc");
        if (not $portdescr3) {
            $portdescr3 =
                ( Net::SNMP->VERSION lt 4 )
                ? $session->get_table($transmodePort3Desc )
                : $session->get_table( Baseoid => $transmodePort3Desc);
            verb(" portdescr   ($transmodePort3Desc): " . Dumper($portdescr3));
        }
        for my $id (keys(%$portdescr3)) {
            my $portname = $$portdescr3{$id};
            $portname =~ s/^(\w)*://;
            if ($port eq $portname) {
                verb("   GEFUNDEN: $port: $id");
                $port2id{$port} = $id;
                next SEARCH_PORT;
            }
        }

        # suche in 'clientIfName'
        if (not $clientdescr) {
            $clientdescr =
                ( Net::SNMP->VERSION lt 4 )
                ? $session->get_table($transmodeClientDesc )
                : $session->get_table( Baseoid => $transmodeClientDesc);
            verb(" clientdescr ($transmodeClientDesc): " . Dumper($clientdescr));
        }
        for my $id (keys(%$clientdescr)) {
            my $portname = $$clientdescr{$id};
            $portname =~ s/^(\w)*://;
            if ($port eq $portname) {
                verb("   GEFUNDEN: $port: $id");
                $port2id{$port} = $id;
                next SEARCH_PORT;
            }
        }
        # suche in 'oaIfName'
        if (not $oadescr) {
            $oadescr =
                ( Net::SNMP->VERSION lt 4 )
                ? $session->get_table($transmodeOADesc )
                : $session->get_table( Baseoid => $transmodeOADesc);
            verb(" oadescr ($transmodeOADesc): " . Dumper($oadescr));
        }
        for my $id (keys(%$oadescr)) {
            my $portname = $$oadescr{$id};
            $portname =~ s/^(\w)*://;
            if ($port eq $portname) {
                verb("   GEFUNDEN: $port: $id");
                $port2id{$port} = $id;
                next SEARCH_PORT;
            }
        }
        # suche in 'oscIfName'
        if (not $oscdescr) {
            $oscdescr =
                ( Net::SNMP->VERSION lt 4 )
                ? $session->get_table($transmodeOSCDesc )
                : $session->get_table( Baseoid => $transmodeOSCDesc);
            verb(" oscdescr ($transmodeOSCDesc): " . Dumper($oscdescr));
        }
        for my $id (keys(%$oscdescr)) {
            my $portname = $$oscdescr{$id};
            $portname =~ s/^(\w)*://;
            if ($port eq $portname) {
                verb("   GEFUNDEN: $port: $id");
                $port2id{$port} = $id;
                next SEARCH_PORT;
            }
        }
    }
    my $status_text='';
    my $status_unknown=0;
    # alle Ports gefunden?
    for my $port(keys(%port2id)) {
        #verb("Port $port: $port2id{$port}");
        if (! $port2id{$port}) {
            verb("UNKNOWN: Port '$port' nicht gefunden");
            $status_text .= "UNKNOWN: Port '$port' nicht gefunden. ";
            $status_unknown=1;
        }
    }
    return($status_unknown, $status_text);
}

sub check_options {
    Getopt::Long::Configure ("bundling");
    GetOptions(
   	    'v'	            => \$o_verb,		'verbose'	    => \$o_verb,
        'h'             => \$o_help,    	'help'        	=> \$o_help,
        'H:s'           => \$o_host,		'hostname:s'	=> \$o_host,
        'P:i'           => \$o_port,   		'snmpport:i'	=> \$o_port,
        'C:s'           => \$o_community,	'community:s'	=> \$o_community,
	    'l:s'	        => \$o_login,		'login:s'	    => \$o_login,
	    'x:s'       	=> \$o_passwd,		'passwd:s'	    => \$o_passwd,
	    'X:s'	        => \$o_privpass,	'privpass:s'	=> \$o_privpass,
	    'L:s'	        => \$v3protocols,	'protocols:s'	=> \$v3protocols,   
        't:i'           => \$o_timeout,     'timeout:i'     => \$o_timeout,
	    'V'	            => \$o_version,		'version'	    => \$o_version,
	    '6'             => \$o_domain,      'use-ipv6'      => \$o_domain,
	    '2'             => \$o_version2,    'v2c'           => \$o_version2,
        'e'             => \$o_env,         'env'           => \$o_env,
        'a'             => \$o_allports,    'allports'      => \$o_allports,
        'p:s@'          => \$o_check_ports, 'port:s@'       => \$o_check_ports,
        'A'             => \$o_allalarms ,  'all'           => \$o_allalarms ,
        'n'             => \$o_noportcheck, 'noportcheck'   => \$o_noportcheck,
	);

    # Basic checks
	if (defined($o_timeout) && (isnnum($o_timeout) || ($o_timeout < 2) || ($o_timeout > 60))) { 
        print "Timeout must be >1 and <60 !\n"; print_usage(); exit $ERRORS{"UNKNOWN"}
    }
	if (!defined($o_timeout)) {$o_timeout=5;}
    if (defined ($o_help) ) { help(); exit $ERRORS{"UNKNOWN"}};
    if (defined($o_version)) { p_version(); exit $ERRORS{"UNKNOWN"}};
    if ( ! defined($o_host) ) {
        # check host and filter
        print_usage(); 
        exit $ERRORS{"UNKNOWN"}
    }
    # Check IPv6 
    if (defined ($o_domain)) {$o_domain="udp/ipv6"} else {$o_domain="udp/ipv4"}
    # check snmp information
    if ( !defined($o_community) && (!defined($o_login) || !defined($o_passwd)) ) { 
        print "Put snmp login info!\n"; print_usage(); 
        exit $ERRORS{"UNKNOWN"}
    }
	if ((defined($o_login) || defined($o_passwd)) && (defined($o_community) || defined($o_version2)) ) { 
        print "Can't mix snmp v1,2c,3 protocols!\n"; print_usage(); 
        exit $ERRORS{"UNKNOWN"}
    }
	if (defined ($v3protocols)) {
	    if (!defined($o_login)) { 
            print "Put snmp V3 login info with protocols!\n"; 
            print_usage(); 
            exit $ERRORS{"UNKNOWN"}
        }
	    my @v3proto=split(/,/,$v3protocols);
	    if ((defined ($v3proto[0])) && ($v3proto[0] ne "")) {$o_authproto=$v3proto[0];	}	# Auth protocol
	    if (defined ($v3proto[1])) {$o_privproto=$v3proto[1];	}	# Priv  protocol
	    if ((defined ($v3proto[1])) && (!defined($o_privpass))) {
	        print "Put snmp V3 priv login info with priv protocols!\n"; 
            print_usage(); 
            exit $ERRORS{"UNKNOWN"}
        }
	}

    if (not defined($o_env) and not defined($o_allports) and not defined($o_check_ports)) {
        $o_allalarms=1;
    }
}

########## MAIN #######

check_options();

# Check gobal timeout if snmp screws up
if (defined($TIMEOUT)) {
    verb("Alarm at $TIMEOUT");
    alarm($TIMEOUT);
}

$SIG{'ALRM'} = sub {
    print "No answer from host within $TIMEOUT seconds\n";
    exit $ERRORS{"UNKNOWN"};
};

# Connect to host
my ($session,$error);
if ( defined($o_login) && defined($o_passwd)) {
    # SNMPv3 login
    verb("SNMPv3 login");
    if (!defined ($o_privpass)) {
        verb("SNMPv3 AuthNoPriv login : $o_login, $o_authproto");
        ($session, $error) = Net::SNMP->session(
            -domain		=> $o_domain,
            -hostname   	=> $o_host,
            -version		=> '3',
            -username		=> $o_login,
            -authpassword	=> $o_passwd,
            -authprotocol	=> $o_authproto,
            -timeout          => $o_timeout
        );  
    } else {
        verb("SNMPv3 AuthPriv login : $o_login, $o_authproto, $o_privproto");
        ($session, $error) = Net::SNMP->session(
            -domain		=> $o_domain,
            -hostname   	=> $o_host,
            -version		=> '3',
            -username		=> $o_login,
            -authpassword	=> $o_passwd,
            -authprotocol	=> $o_authproto,
            -privpassword	=> $o_privpass,
            -privprotocol => $o_privproto,
            -timeout          => $o_timeout
        );
    }
} else {
    if (defined ($o_version2)) {
		# SNMPv2 Login
		verb("SNMP v2c login");
		($session, $error) = Net::SNMP->session(
            -domain    => $o_domain,
            -hostname  => $o_host,
            -version   => 2,
            -community => $o_community,
            -port      => $o_port,
            -timeout   => $o_timeout
		);
    } else {
	    # SNMPV1 login
	    verb("SNMP v1 login");
	    ($session, $error) = Net::SNMP->session(
            -domain    => $o_domain,
            -hostname  => $o_host,
            -community => $o_community,
            -port      => $o_port,
            -timeout   => $o_timeout
	    );
	}
}
if (!defined($session)) {
    printf("ERROR opening session: %s.\n", $error);
    exit $ERRORS{"UNKNOWN"};
}

my $exit_val=undef;

# NEUE PROGRAMM LOGIK
my $status_unknown = 0;
my $status_critical = 0;
my $status_warn = 0;
my $status_text = '';
my $status_text_critical = '';
my $status_text_warn = '';
my $status_text_unknown = '';
my $status_text_ok = '';
my %port2id;

if (not defined($o_noportcheck)) {
    ($status_unknown, $status_text_unknown) = check_ports_exist($session, @$o_check_ports);
}

verb("reading Alarms");
my $alarmentry =
    ( Net::SNMP->VERSION lt 4 )
    ? $session->get_table($transmodeAlarmEntry )
    : $session->get_table( Baseoid => $transmodeAlarmEntry);
$alarmentry //= {}; # wenn es keine Alarame gibt ist es undef
verb(" alarmentry ($transmodeAlarmEntry): " . Dumper($alarmentry));

my %ports_ok = %port2id; # nur zur Ausgabe, welche Ports keinen Fehler haben
for my $alarmkey (keys($alarmentry)) {
    if ($alarmkey =~ /^$transmodeAlarmEntry\.4\.(\d+)$/) {
        my $alarm_index = $1;
        my $alarm_objname = $$alarmentry{"$transmodeAlarmEntry.4.$alarm_index"};
        my $alarm_severety = $$alarmentry{"$transmodeAlarmEntry.10.$alarm_index"};
        my $alarm_text = $$alarmentry{"$transmodeAlarmEntry.9.$alarm_index"};

        if (check_this_obj($alarm_objname)) {
            $alarm_text = '' unless defined($alarm_text);
            $alarm_severety = '' unless defined($alarm_severety);
            verb("Alarm fuer Object: $alarm_index, $alarm_objname, $alarm_severety, $alarm_text");
            switch ($alarm_severety) {
                case [6] {
                    verb("  => CRITICAL");
                    $status_critical++;
                    $status_text_critical .= "$alarm_text on $alarm_objname, ";
                    $ports_ok{$alarm_objname}=0;
                }
                case [4, 5] {
                    verb("  => WARNING");
                    $status_warn++;
                    $status_text_warn     .= "$alarm_text on $alarm_objname, ";
                    $ports_ok{$alarm_objname}=0;
                }
                case [1, 3] {
                    # vermutlich gibt es diese Meldung nie
                    verb("  => OK");
                }
                else {
                    verb("  => UNKNOWN");
                    $status_unknown++;
                    $status_text_unknown  .= "$alarm_text on $alarm_objname, ";
                    $ports_ok{$alarm_objname}=0;
                }
            }
        } else {
            verb("Alarm fuer ignoriertes Object: $alarm_index, $alarm_objname, $alarm_severety, $alarm_text");
        }
    }
}

# Ende
if ($status_critical or $status_warn or $status_unknown) {
    $status_text = 
        $status_text_critical 
        . $status_text_warn 
        . $status_text_unknown 
}
if (@$o_check_ports) {
    my $count = @$o_check_ports;
    $status_text .= "$count port";
    $status_text .= "s" if $count > 1;
    $status_text .= " checked, ";
}
if ($o_env) {
    $status_text .= "environment checked, ";
}
if ($o_allports) {
    $status_text .= "all ports checked, ";
}
if ($o_allalarms) {
    $status_text .= "all alarms checked, ";
}

$status_text =~ s/, $//;
if      ($status_critical) { 
        print "CRITICAL: $status_text\n";
        exit $ERRORS{"CRITICAL"};
} elsif ($status_warn) {
        print "WARNING: $status_text\n";
        exit $ERRORS{"WARNING"};
} elsif ($status_unknown) {
        print "UNKNOWN: $status_text\n";
        exit $ERRORS{"UNKNOWN"};
}
print "OK: $status_text\n";
exit $ERRORS{"OK"};


__END__

Neue Programmlogik:

- wenn --ports und nicht --noportcheck:
  checke, dass es Ports auch gibt (Check ist teuer))
- wenn --all: --env und --allports
- lies alle Alarme
-- Alarmtyp bestimmen: env oder port
--- Annahme: Port beginnen mit "wdm:" oder "client:" oder die anderen in sub check_this_obj()
-- wenn --env und typ = env: bearbeite Alarm
-- wenn --allport und typ = port: bearbeite Alarm
-- wenn --ports und type = port und aktuell Port in Ports: bearbeite Alarm
- bearbeite Alarm: 
-- bestimme maximalen status (OK, UNKNOWN, WARNING, CRITICAL)
-- Text: alle Meldungen, kritischster zuerst


