#! /usr/bin/perl -wT
#
# check_ifstatus.pl - nagios plugin 
# 
#
# Copyright (C) 2000 Christoph Kron
# Modified 5/2002 to conform to updated Nagios Plugin Guidelines (S. Ghosh)
#  Added -x option (4/2003)
#  Added -u option (4/2003)
#  Added -M option (10/2003)
#  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: ck@zet.net, nagiosplug-help@lists.sf.net
# 
# 11.01.2000 Version 1.0
#
# $Id: check_ifstatus.pl,v 1.8 2003/11/05 22:59:41 sghosh Exp $

require 5.004;
use POSIX;
use strict;
use lib utils.pm ;
use Plugin;
use Plugin::Parameter qw(:DEFAULT :standard :snmp);

use vars qw($hostname $snmp_version $community $seclevel $authproto $secname $authpass $privpass
	    $context $port $ifXTable $opt_x $opt_u $maxmsgsize $timeout $PROGNAME);
use utils qw(%ERRORS &usage);

use Net::SNMP;

$H_opt->binding(\$hostname);

my $v_opt = $snmpversionparameter;
$v_opt->flags([ 'v', 'snmp_version' ]);
$v_opt->binding(\$snmp_version);

$C_opt->binding(\$community);

$snmpv3seclevelparameter->binding(\$seclevel);
$authprotocolparameter->binding(\$authproto);
my $U_opt = $u_opt; # Use the exported username object
$U_opt->name("secname");
$U_opt->flags([ 'U', 'secname' ]);
$U_opt->binding(\$secname);
$authpasswordparameter->binding(\$authpass);
$privpasswordparameter->binding(\$privpass);
$snmpcontextparameter->binding(\$context);
$privprotocolparameter->flags([ "privprotocol" ]);

$p_opt->default(161);
$p_opt->binding(\$port);

$t_opt->binding(\$timeout);
my $I_opt = new Plugin::Parameter(-name => "ifmib", -flags => [ 'I', 'ifmib' ],
				  -optional => "yes", -valueoptional => "yes", -type => "NONE",
				  -binding => \$ifXTable,
				  -description => "Agent supports IFMIB ifXTable. For Cisco - this will provide the descriptive name. DO not use if you don't know what this is.");
my $x_opt = new Plugin::Parameter(-name => "exclude", -flags => [ 'x', 'exclude' ],
				  -optional => "yes", -valueoptional => "no", -type => "STRING",
				  -binding => \$opt_x,
				  -description => "A comma separated list of ifType values that should be excluded from the report. An empty list defaults to PPP(23).  See the IANAifType-MIB for a list of interface types");
my $u_opt = new Plugin::Parameter(-name => "unusedports", -flags => [ 'u', 'unused-ports' ],
				  -optional => "yes", -valueoptional => "no", -type => "STRING",
				  -binding => \$opt_u,
				  -description => "A comma separated list of ifIndex values that should be excluded from the report.");
my $M_opt = new Plugin::Parameter(-name => "maxmsgsize", -flags => [ 'M', 'maxmsgsize' ],
				  -optional => "yes", -valueoptional => "no", -type => "INTEGER",
				  -binding => \$maxmsgsize,
				  -default => 1472,
				  -description => "Max message size - useful only for v1 or v2c");

my @cmndopts = ( $H_opt, @snmpparameters, $I_opt, $x_opt, $u_opt, $M_opt, $t_opt );
my $plugin = new Plugin(-revision => '$Revision: 1.8 $',
			-copyright => " 2000 Christoph Kron, 2002 Subhendu Ghosh, 2004 Howard Wilkinson <howard\@cohtech.com>",
			-shortcomment => "check_ifstatus plugin for Nagios monitors operational status of each network interface on the target host",
			-longcomment => "",
			-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 ( ! utils::is_hostname($hostname)) {
					    $plugin->usage();
					    usage("$PROGNAME UNKNOWN: Invalid hostname\n");
					  }
					},
			-parameterlists => [ \@cmndopts, $h_opts, $V_opts ]);

sub setupsnmp ();

my $status;
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 $snmpIfAdminStatus = '1.3.6.1.2.1.2.2.1.7';
my $snmpIfDescr = '1.3.6.1.2.1.2.2.1.2';
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 $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';
my $snmpIfType = '1.3.6.1.2.1.2.2.1.3';
my @snmpoids = ($snmpIfOperStatus,
		$snmpIfAdminStatus,
		$snmpIfDescr,
		$snmpIfType);
		

$plugin->init();

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

#Option checking
my ($session, $error) = setupsnmp();

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

my %ifStatus = ();
# Excluded interface ports (ifIndex) - management reasons
if ($opt_u) {
  my @unused_ports = split(/,/,$opt_u);
  foreach my $key (@unused_ports) { 
    $ifStatus{$key}{'notInUse'}++ ;
  }
}

foreach my $snmpoid (@snmpoids) {
  my $response = $session->get_table($snmpoid);
  if (!defined $response) {
    $answer=$session->error;
    $session->close;
    $state = 'CRITICAL';
    if ( ( $snmpoid =~ $snmpIfName ) && defined $ifXTable ) {
      print ("$PROGNAME $state: Device does not support ifTable - try without -I option\n");
    }else{
      print ("$PROGNAME $state: $answer for $snmpoid  with snmp version $snmp_version\n");
    }
    exit $ERRORS{$state};
  }
  foreach my $snmpkey (keys %{$response}) {
    $snmpkey =~ /.*\.(\d+)$/;
    my $key = $1;
    $ifStatus{$key}{$snmpoid} = $response->{$snmpkey};
  }
}

$session->close;

$plugin->stop_timeout();

my %excluded = ();
# Excluded interfaces types (ifType) (backup interfaces, dial-on demand interfaces, PPP interfaces
if (defined $opt_x) {
  my @x = split(/,/, $opt_x);
  if ( @x) {
    foreach my $key (@x){
      $excluded{$key} = 1;
    }
  }else{
    $excluded{23} = 1; # default PPP(23) if empty list - note (AIX seems to think PPP is 22 according to a post)
  }
}

my $ifup =0 ;
my $ifdown =0;
my $ifdormant = 0;
my $ifexclude = 0 ;
my $ifunused = 0;
my $ifmessage = "";

foreach my $key (keys %ifStatus) {
  # skip unused interfaces
  if (!defined($ifStatus{$key}{'notInUse'})) {
    # check only if interface is administratively up
    if ($ifStatus{$key}{$snmpIfAdminStatus} == 1 ) {
      # check only if interface type is not listed in %excluded
      if (!defined $excluded{$ifStatus{$key}{$snmpIfType}} ) {
	if ($ifStatus{$key}{$snmpIfOperStatus} == 1 ) { $ifup++ ;}
	if ($ifStatus{$key}{$snmpIfOperStatus} == 2 ) {
	  $ifdown++ ;
	  if (defined $ifXTable) {
	    $ifmessage .= sprintf("%s: down -> %s<BR>",
				  $ifStatus{$key}{$snmpIfName},
				  $ifStatus{$key}{$snmpIfAlias});
	  }else{
	    $ifmessage .= sprintf("%s: down <BR>",
				  $ifStatus{$key}{$snmpIfDescr});
	  }
	}
	if ($ifStatus{$key}{$snmpIfOperStatus} == 5 ) { $ifdormant++ ;}
      }else{
	$ifexclude++;
      }
    }
  }else{
    $ifunused++;
  }
}

my $perfdata = sprintf("host '%s', interfaces up:%d, down:%d, dormant:%d, excluded:%d, unused:%d",
		       $hostname, $ifup, $ifdown, $ifdormant, $ifexclude, $ifunused);

if ($ifdown > 0) {
  $state = 'CRITICAL';
  $perfdata .= $ifmessage;
} else {
  $state = 'OK';
}

print ("$PROGNAME $state: $perfdata\n");
exit $ERRORS{$state};

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
      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;
	  }
	}
      }
    }
  } # end snmpv3

  ($session, $error) = Net::SNMP->session(%snmpArgs);
  if (!defined($session)) {
    $state='UNKNOWN';
    $answer=$error;
    print ("$state: $answer");
    exit $ERRORS{$state};
  }
  return ($session, $error);
}
