#!/usr/bin/perl -wT
#
# check_ifoperstatus.pl - nagios plugin
#
# Copyright (C) 2000 Christoph Kron,
# Modified 5/2002 to conform to updated Nagios Plugin Guidelines
# Added support for named interfaces per Valdimir Ivaschenko (S. Ghosh)
# Added SNMPv3 support (10/2003)
#
# 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.
#
#
# Report bugs to:  nagiosplug-help@lists.sourceforge.net
#
# 11.01.2000 Version 1.0
# $Id: check_ifoperstatus.pl,v 1.5 2003/11/05 22:59:41 sghosh Exp $
#
# Patches from Guy Van Den Bergh to warn on ifadminstatus down interfaces
# instead of critical.
#
# Primary MIB reference - RFC 2863

require 5.004;
use POSIX;
use strict;
use lib utils.pm ;
use Plugin;
use Plugin::Parameter qw($helpparameterlist $versionparameterlist $hostnameparameter $communityparameter
			 $snmpversionparameter $authprotocolparameter $userparameter $authpasswordparameter
			 $privpasswordparameter $snmpv3seclevelparameter $snmpcontextparameter
			 $warningparameter $portparameter $timeoutparameter);

use vars qw($hostname $community $snmp_version $seclevel $secname $context $authpass
	    $authproto $privpass $snmpkey $ifDescr $lastc $port $ifXTable $ifName
	    $dormantWarn $maxmsgsize $timeout $PROGNAME);

use utils qw(%ERRORS &usage);

use Net::SNMP;

$hostnameparameter->binding(\$hostname);
$communityparameter->binding(\$community);
$snmpversionparameter->binding(\$snmp_version);
$snmpv3seclevelparameter->binding(\$seclevel);
$userparameter->name("secname");
$userparameter->flags([ 'U', 'secname']);
$userparameter->description("username for SNMPv3 context");
$userparameter->binding(\$secname);
$snmpcontextparameter->binding(\$context);
$authpasswordparameter->binding(\$authpass);
$authprotocolparameter->binding(\$authproto);
$privpasswordparameter->flags([ 'X', 'privpass']);
$privpasswordparameter->binding(\$privpass);
my $keyparameter = new Plugin::Parameter(-name => "ifIndex", -flags => [ 'k', 'key' ],
					 -optional => "yes", -valueoptional => "no", -type => "INTEGER",
					 -default => 0,
					 -binding => \$snmpkey,
					 -checker => \&Plugin::Parameter::_check_integer,
					 -description => "SNMP IfIndex value");
my $descrparameter = new Plugin::Parameter(-name => "ifDescr", -flags => [ 'd', 'descr' ],
					   -optional => "yes", -valueoptional => "no", -type => "SNMPIFDESC",
					   -binding => \$ifDescr,
					   -description => "SNMP ifDescr value");
my $lastchangeparameter = new Plugin::Parameter(-name => "lastchange", -flags => [ 'l', 'lastchange' ],
						-optional => "yes", -valueoptional => "no", -type => "STRING",
						-binding => \$lastc,
						-description => "***** What is this *****");
$portparameter->binding(\$port);
$portparameter->default("snmp");
my $ifmibparameter = new Plugin::Parameter(-name => "ifmib", -flags => [ 'I', 'ifmib' ],
					   -optional => "yes", -valueoptional => "yes", -type => "NONE",
					   -binding => \$ifXTable,
					   -description => "Agent support IFMIB ifXTable. 'Do not use this if you do not know what it is'");
my $nameparameter = new Plugin::Parameter(-name => "name", -flags => [ 'n', 'name' ],
					  -optional => "yes", -valueoptional => "no", -type => "NAME",
					  -binding => \$ifName,
					  -description => "the value should match the returned ifName, implies use of ifmib flag");
$warningparameter->binding(\$dormantWarn);
$warningparameter->type("STRING");
$warningparameter->checker(sub { my ($opt, $parameter, $plugin) = @_;
				 $$opt =~ m/^(i|w|c)$/;
				 $$opt = $1;
			       });
$warningparameter->default("c");
$warningparameter->optional("yes");
my $maxmsgsizeparameter = new Plugin::Parameter(-name => "maxmsgsize", -flags => [ 'M', 'maxmsgsize' ],
						-optional => "yes", -valueoptional => "no", -type => "INTEGER",
						-default => 1472,
						-binding => \$maxmsgsize,
						-description => "Maximum message size - useful only for v1 and v2c queries");
$timeoutparameter->binding(\$timeout);

my $plugin = new Plugin(-revision => '$Revision: 1.5 $',
			-copyright => "2000 Christoph Kron, 2004 Howard Wilkinson <howard\@cohtech.com>",
			-shortcomment => "check_ifoperstatus plugin for Nagios monitors operational status of a particular network interface in the target host",
			-longcomment => "Note: either -k or -d must be specified and -d is much more network intensive.  Use it sparingly or not at all.  -n is used to match against a much more descriptive ifName value in the IfXTable to verify that the snmpkey has not changed to some other network interface after a reboot.",
			-checker => sub { my ($plugin) = @_;
					  # Check the version against options
					  if ($snmp_version =~ m/3/) {
					    # Can't do this check as community has a default
					    #if ($community) {
					    #  $plugin->usage();
					    #  usage("$PROGNAME UNKNOWN: Community specified for SNMPV3 - inconsistent\n");
					    #}
					    unless (defined $seclevel && defined $secname) {
					      $plugin->usage();
					      usage("$PROGNAME UNKNOWN: Seclevel and Security name must be defined\n");
					    }
					    if ($seclevel =~ m/^auth(No)?Priv$/i) {
					      unless (defined $authpass) {
						$plugin->usage();
						usage("$PROGNAME UNKNOWN: Authentication Password required\n");
					      }
					      if ($seclevel =~ m/^authPriv$/i) {
						unless (defined $privpass) {
						  $plugin->usage();
						  usage("$PROGNAME UNKNOWN: Privacy password required\n");
						}
					      }
					    }
					  } else {
					    # Can't do this check as seclevel has a default
					    #if ($seclevel || $authproto || $secname || $authpass || $privpass || $context) {
					    #  $plugin->usage();
					    #  usage("$PROGNAME UNKNOWN: SNMPv1 and SNMPv2c only require commmunity\n");
					    #}
					  }
					  if ($snmpkey && $ifDescr) {
					    $plugin->usage();
					    usage("$PROGNAME UNKNOWN: Cannot specify both key and descr\n");
					  }
					  if ($ifName) {
					    $ifXTable = 1;
					  }
					  if ( ! utils::is_hostname($hostname)) {
					    $plugin->usage();
					    usage("$PROGNAME UNKNOWN: Invalid hostname\n");
					  }
					  unless ($snmpkey > 0 || defined $ifDescr){
					    $plugin->usage();
					    usage("$PROGNAME UNKNOWN: Either a valid snmpkey key (-k) or a ifDescr (-d) must be provided)\n");
					  }
					},
			-parameterlists => [ [ $hostnameparameter,
					       $communityparameter,
					       $snmpversionparameter,
					       $snmpv3seclevelparameter,
					       $userparameter,
					       $snmpcontextparameter,
					       $authpasswordparameter,
					       $authprotocolparameter,
					       $privpasswordparameter,
					       $keyparameter,
					       $portparameter,
					       $descrparameter,
					       $lastchangeparameter,
					       $ifmibparameter,
					       $nameparameter,
					       $warningparameter,
					       $maxmsgsizeparameter,
					       $timeoutparameter ],
					     $helpparameterlist,
					     $versionparameterlist ]);


sub setupsnmp();

my %ifOperStatus = 	('1','up',
			 '2','down',
			 '3','testing',
			 '4','unknown',
			 '5','dormant',
			 '6','notPresent',
			 '7','lowerLayerDown');  # down due to the state of lower layer interface(s)

my $state = "UNKNOWN";
my $answer = "";

my @snmpoids;
my $sysUptime        = '1.3.6.1.2.1.1.3.0';
my $snmpIfDescr      = '1.3.6.1.2.1.2.2.1.2';
my $snmpIfAdminStatus = '1.3.6.1.2.1.2.2.1.7';
my $snmpIfOperStatus = '1.3.6.1.2.1.2.2.1.8';
my $snmpIfName       = '1.3.6.1.2.1.31.1.1.1.1';
my $snmpIfLastChange = '1.3.6.1.2.1.2.2.1.9';
my $snmpIfAlias      = '1.3.6.1.2.1.31.1.1.1.18';
my $snmpLocIfDescr   = '1.3.6.1.4.1.9.2.2.1.1.28';

### Validate Arguments

$plugin->init();

$plugin->start_timeout($timeout, "No snmp response from $hostname (alarm)");

my ($session, $error) = setupsnmp();

## map ifdescr to ifindex - should look at being able to cache this value

if (defined $ifDescr) {
  # escape "/" in ifdescr - very common in the Cisco world
  $ifDescr =~ s/\//\\\//g;
  my $status=fetch_ifdescr();  # if using on device with large number of interfaces
                               # recommend use of SNMP v2 (get-bulk)
  if ($status==0) {
    $state = "UNKNOWN";
    printf "$PROGNAME $state: could not retrive ifdescr snmpkey - $status-$snmpkey\n";
    $session->close;
    exit $ERRORS{$state};
  }
}

## Main function

$snmpIfAdminStatus = $snmpIfAdminStatus . "." . $snmpkey;
$snmpIfOperStatus = $snmpIfOperStatus . "." . $snmpkey;
$snmpIfDescr = $snmpIfDescr . "." . $snmpkey;
$snmpIfName	= $snmpIfName . "." . $snmpkey ;
$snmpIfAlias = $snmpIfAlias . "." . $snmpkey ; 

push(@snmpoids,$snmpIfAdminStatus);
push(@snmpoids,$snmpIfOperStatus);
push(@snmpoids,$snmpIfDescr);
push(@snmpoids,$snmpIfName) if (defined $ifXTable) ;
push(@snmpoids,$snmpIfAlias) if (defined $ifXTable) ;

my $response = $session->get_request(@snmpoids);

if (!defined $response) {
  $answer=$session->error;
  $session->close;
  $state = 'WARNING';
  print ("$state: SNMP error: $answer\n");
  exit $ERRORS{$state};
}
$answer = sprintf("host '%s', %s(%s) is %s\n", 
		  $hostname, 
		  $response->{$snmpIfDescr},
		  $snmpkey, 
		  $ifOperStatus{$response->{$snmpIfOperStatus}});

$plugin->stop_timeout();

## Check to see if ifName match is requested and it matches - exit if no match
## not the interface we want to monitor
if ( defined $ifName && not ($response->{$snmpIfName} eq $ifName) ) {
  $state = 'UNKNOWN';
  $answer = "Interface name ($ifName) doesn't match snmp value ($response->{$snmpIfName}) (index $snmpkey)";
  print ("$state: $answer");
  exit $ERRORS{$state};
}
## define the interface name
if (defined $ifXTable) {
  $ifName = $response->{$snmpIfName} ." - " .$response->{$snmpIfAlias} ; 
}else{
  $ifName = $response->{$snmpIfDescr} ;
}

## if AdminStatus is down - some one made a consious effort to change config
##
if ( not ($response->{$snmpIfAdminStatus} == 1) ) {
  $state = 'WARNING';
  $answer = "Interface $ifName (index $snmpkey) is administratively down.";
} elsif ( $response->{$snmpIfOperStatus} == 2 ) { ## Check operational status
  $state = 'CRITICAL';
  $answer = "Interface $ifName (index $snmpkey) is down.";
} elsif ( $response->{$snmpIfOperStatus} == 5 ) {
  if (defined $dormantWarn ) {
    if ($dormantWarn eq "w") {
      $state = 'WARNNG';
      $answer = "Interface $ifName (index $snmpkey) is dormant.";
    }elsif($dormantWarn eq "c") {
      $state = 'CRITICAL';
      $answer = "Interface $ifName (index $snmpkey) is dormant.";
    }elsif($dormantWarn eq "i") {
      $state = 'OK';
      $answer = "Interface $ifName (index $snmpkey) is dormant.";
    }
  }else{
    # dormant interface  - but warning/critical/ignore not requested
    $state = 'CRITICAL';
    $answer = "Interface $ifName (index $snmpkey) is dormant.";
  }
} elsif ( $response->{$snmpIfOperStatus} == 6 ) {
  $state = 'CRITICAL';
  $answer = "Interface $ifName (index $snmpkey) notPresent - possible hotswap in progress.";
} elsif ( $response->{$snmpIfOperStatus} == 7 ) {
  $state = 'CRITICAL';
  $answer = "Interface $ifName (index $snmpkey) down due to lower layer being down.";
} elsif ( $response->{$snmpIfOperStatus} == 3 || $response->{$snmpIfOperStatus} == 4  ) {
  $state = 'CRITICAL';
  $answer = "Interface $ifName (index $snmpkey) down (testing/unknown).";
} else {
  $state = 'OK';
  $answer = "Interface $ifName (index $snmpkey) is up.";
}

print ("$PROGNAME $state: $answer");
exit $ERRORS{$state};


### subroutines

sub fetch_ifdescr {
  if (!defined ($response = $session->get_table($snmpIfDescr))) {
    $answer=$session->error;
    $session->close;
    $state = 'CRITICAL';
    printf ("$state: SNMP error with snmp version $snmp_version ($answer)\n");
    $session->close;
    exit $ERRORS{$state};
  }

  foreach my $key ( keys %{$response}) {
    if ($response->{$key} =~ /^$ifDescr$/) {
      $key =~ /.*\.(\d+)$/;
      $snmpkey = $1;
    }
  }
  unless (defined $snmpkey) {
    $session->close;
    $state = 'CRITICAL';
    printf "$state: Could not match $ifDescr on $hostname\n";
    exit $ERRORS{$state};
  }

  return $snmpkey;
}

sub setupsnmp() {
  my %snmpArgs = ( -hostname => $hostname,
		   -port => $port,
		   -version => $snmp_version,
		   -timeout => $timeout );
  if ( $snmp_version =~ /[12]/ ) {
    $snmpArgs{-community} = $community;
    $snmpArgs{-maxmsgsize} = $maxmsgsize;
  } elsif ( $snmp_version =~ /3/ ) {
    # Must define a security level even though default is noAuthNoPriv
    # v3 requires a security username
    if (defined $seclevel  && defined $secname) {
      # Must define a security level even though defualt is noAuthNoPriv
      # Authentication wanted
      $snmpArgs{-username} = $secname;
      if ($seclevel eq ('authNoPriv' || 'authPriv') ) {
	$snmpArgs{-authprotocol} = $authproto;
	if ($authpass =~ /^0x/ ) {
	  $snmpArgs{-authkey} = $authpass;
	} else {
	  $snmpArgs{-authpassword} = $authpass;
	}
	if ($seclevel eq  'authPriv') { # Privacy (DES encryption) wanted
	  if ($privpass =~ /^0x/){
	    $snmpArgs{-privkey} = $privpass;
	  } else {
	    $snmpArgs{-privpassword} = $privpass;
	  }
	}
      }
    }
  }
  my ($session, $error) = Net::SNMP->session(%snmpArgs);
  if (!defined($session)) {
    $state='UNKNOWN';
    $answer=$error;
    print ("$PROGNAME $state: $answer\n");
    exit $ERRORS{$state};
  }
  return ($session, $error);
}
## End validation

