#!/usr/bin/perl
# nagios: +epn
#
# check_ifoperstatus - nagios plugin 
#
# Copyright (C) 2006 Larry Low
#
# 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:  llow0@yahoo.com
#
# Primary MIB reference - IF-MIB,ISDN-MIB,DS1-MIB,CISCO-HSRP-MIB
#
# Version 0.81 fix - forgot to add support for using different snmp ports (-H host:port)
# Version 0.8  added --cisco to use locIfReason from OLD-CISCO-INTERFACES MIB
#              added --warnadmin to warn if ifAdminStatus is down instead of critical
# Version 0.7  added snmpv3 support
# Version 0.61 fix - removed -h for HSRP group as it conflicts with help
#              fix - ifAlias will now replace | characters with # so that nagios doesn't confuse for perfdata
#              fix - had typos of isdnd
# Version 0.6  added -h (--hsrpgrp) option to check the state of an HSRP group
#              added -t (--type) new type of hsrpgrp (requires -h)
#              Note: HSRP currently will alarm CRITICAL only if state is not active or standby
# Version 0.5  $snmp was not checked for being defined - fixed
#              replaced -c (--cactidb) with individual options for each connect string (see help)
#              added -t (--type) option and integrated isdn d channel and dsx1 checking
# Version 0.4  added -c (--cactidb) option to use cacti database to get index
# Version 0.3  added -D (--ifdescr) option to match ifDescr to parameter
#              added -N (--ifname) option to match ifName to parameter
#              if just -D is specified only ifDescr looked up, otherwise ifName is
# Version 0.2  conformed with ePN
#
use strict;
use warnings;
use lib '/usr/lib/nagios/plugins';
use utils qw($TIMEOUT %ERRORS &print_revision &support);
use vars qw($PROGNAME);

# Just in case of problems, let's not hang Nagios
$SIG{'ALRM'} = sub {
	print ("ERROR: Plugin took too long to complete (alarm)\n");
	exit $ERRORS{"UNKNOWN"};
};
alarm($TIMEOUT);

$PROGNAME = "check_ifoperstatus.pl";
sub print_help ();
sub print_usage ();

my ($opt_h,$opt_V,$opt_ifXTable,$opt_cisco,$opt_warnadmin);
my $community = "public";
my $snmp_version = 2;
my $cacti_database = "cacti";
my $cacti_username = "cacti";
my $type = "default";
my ($cacti_password,$cacti_hostname,$cacti_port);
my ($hostname,$port,$ifindex,$ifDescr_match,$ifName_match,$hsrpgrp);

use Getopt::Long;
Getopt::Long::Configure('bundling');
Getopt::Long::GetOptions(
	"V"   => \$opt_V,	  "version"    => \$opt_V,
	"h"   => \$opt_h,	  "help"       => \$opt_h,
	"C=s" => \$community,	  "community=s" => \$community,
	"H=s" => \$hostname,	  "hostname=s" => \$hostname,
	"k=i" => \$ifindex,	  "key=i" => \$ifindex,
	"v=i" => \$snmp_version,  "snmp_version=i" => \$snmp_version,
	"I"   => \$opt_ifXTable,  "ifmib" => \$opt_ifXTable,
	"D=s" => \$ifDescr_match, "ifdescr=s" => \$ifDescr_match,
	"N=s" => \$ifName_match,  "ifname=s" => \$ifName_match,
	"t=s" => \$type,          "type=s" => \$type,
	"warnadmin" => \$opt_warnadmin,
	"cisco" => \$opt_cisco,
	"hsrpgrp=i" => \$hsrpgrp,
	"cacti_database=s" => \$cacti_database,
	"cacti_username=s" => \$cacti_username,
	"cacti_password=s" => \$cacti_password,
	"cacti_hostname=s" => \$cacti_hostname,
	"cacti_port=s" => \$cacti_port
);
# -h & --help print help
if ($opt_h) { print_help(); exit $ERRORS{'OK'}; }
# -V & --version print version
if ($opt_V) { print_revision($PROGNAME,'$Revision: 0.81 $ '); exit $ERRORS{'OK'}; }
# Invalid hostname print usage
($hostname,$port) = split(':',$hostname,2);
if (!defined($port) || ($port !~ /^[0-9]{1,5}$/)) { $port = 161; }
if (!utils::is_hostname($hostname)) { print_usage(); exit $ERRORS{'UNKNOWN'}; }
# No ifindex supplied print usage
if (!defined($ifindex)) { print_usage(); exit $ERRORS{'UNKNOWN'}; }
# If using cacti db then ifDescr or ifName must be set
if (defined($cacti_password)) {
	if (!defined($ifDescr_match) && !defined($ifName_match)) {
		print "Error: Must specifiy ifDescr or ifName when using cacti lookup method.\n";
		print_usage();
		exit $ERRORS{'UNKNOWN'};
	}
}
if ($type eq "hsrpgrp") {
	if (!defined($hsrpgrp)) {
		print "Error: HSRP Group is required when using type of 'hsrpgrp'.\n";
		print_usage();
		exit $ERRORS{'UNKNOWN'};
	}
}

# Setup SNMP object
use Net::SNMP qw(INTEGER OCTET_STRING IPADDRESS OBJECT_IDENTIFIER NULL);
my ($snmp, $snmperror);
if ($snmp_version == 2) {
	($snmp, $snmperror) = Net::SNMP->session(
		-hostname => "$hostname:$port",
		-version => 'snmpv2c',
		-community => $community
	);
} elsif ($snmp_version == 3) {
	my ($v3_username,$v3_password,$v3_protocol,$v3_priv_passphrase,$v3_priv_protocol) = split(":",$community);
	my @auth = ();
	if (defined($v3_password)) { push(@auth,($v3_password =~ /^0x/) ? 'authkey' : 'authpassword',$v3_password); }
	if (defined($v3_protocol)) { push(@auth,'authprotocol',$v3_protocol); }
	if (defined($v3_priv_passphrase)) { push(@auth,($v3_priv_passphrase =~ /^0x/) ? 'privkey' : 'privpassword',$v3_priv_passphrase); }
	if (defined($v3_priv_protocol)) { push(@auth,'privprotocol',$v3_priv_protocol); }

	($snmp, $snmperror) = Net::SNMP->session(
		-hostname => "$hostname:$port",
		-version => 'snmpv3',
		-username => $v3_username,
		@auth
	);
} else {
	($snmp, $snmperror) = Net::SNMP->session(
		-hostname => "$hostname:$port",
		-version => 'snmpv1',
		-community => $community
	);
}

if (!defined($snmp)) {
	print ("UNKNOWN - SNMP error: $snmperror\n");
	exit $ERRORS{'UNKNOWN'};
}

my $state = 'UNKNOWN';
my $output = "Interface status retrieval failed. (index $ifindex)";
# Begin plugin check code
{
	# If cacti_password is set, use cacti database for lookup of ifIndex
	if (defined($cacti_password)) {
		use DBI;
		my $dsn = "DBI:mysql:database=$cacti_database";
		if (defined($cacti_hostname)) {
			$dsn .= ";host=$cacti_hostname";
		}
		if (defined($cacti_port)) {
			$dsn .= ";port=$cacti_port";
		}
		my $dbh = DBI->connect($dsn,$cacti_username,$cacti_password);

		if (!defined($dbh)) {
			print ("UNKNOWN - DB error: unable to connect to cacti database\n");
			exit $ERRORS{'UNKNOWN'};
		}

		my $sql = "SELECT snmp_index FROM host_snmp_cache JOIN host ON host_snmp_cache.host_id=host.id WHERE hostname='$hostname' AND field_name=";
		if (defined($ifDescr_match)) {
			$sql .= "'ifDescr' AND field_value='$ifDescr_match'";
		} else {
			$sql .= "'ifName' AND field_value='$ifName_match'";
		}
		$sql .= " AND snmp_query_id=1 ORDER BY snmp_index";

		my $sth = $dbh->prepare($sql);
		$sth->execute();

		my @row = $sth->fetchrow_array;
		$sth->finish();
		$dbh->disconnect();
		if (!defined($row[0])) {
			print ("UNKNOWN - DB error: index not found in cacti database\n");
			exit $ERRORS{'UNKNOWN'};
		} else {
			$ifindex = $row[0];
		}
	}

	# index should be found now, now run checks depending on type
	my $ifDescr      = '1.3.6.1.2.1.2.2.1.2';
	my $ifName       = '1.3.6.1.2.1.31.1.1.1.1';
	my $ifAlias      = '1.3.6.1.2.1.31.1.1.1.18';
	my $ifAdminStatus = '1.3.6.1.2.1.2.2.1.7';
	my $locIfReason = '1.3.6.1.4.1.9.2.2.1.1.20';
	my @snmpoids = ();

	if (defined($ifDescr_match)) {
		if (defined($ifName_match)) {
			push (@snmpoids,"$ifName.$ifindex");
		}
		push (@snmpoids,"$ifDescr.$ifindex");
	} else {
		push (@snmpoids,"$ifName.$ifindex");
	}

	if ($opt_ifXTable) { push (@snmpoids,"$ifAlias.$ifindex"); }
	if ($opt_warnadmin) { push (@snmpoids,"$ifAdminStatus.$ifindex"); }
	if ($opt_cisco) { push (@snmpoids,"$locIfReason.$ifindex"); }

	# index and shared oids set, now set status oid
	my $oid_status = "1.3.6.1.2.1.2.2.1.8.$ifindex"; # ifOperStatus (default)
	if ($type eq "isdnd") {
		$oid_status = "1.3.6.1.2.1.10.20.1.3.4.1.2.$ifindex"; # isdnLapdOperStatus
	} elsif ($type eq "dsx1") {
		$oid_status = "1.3.6.1.2.1.10.18.6.1.10.$ifindex"; # dsx1LineStatus
	} elsif ($type eq "hsrpgrp") {
		$output .= " HSRP Group $hsrpgrp";
		$oid_status = "1.3.6.1.4.1.9.9.106.1.2.1.1.15.$ifindex.$hsrpgrp"; #cHsrpGrpStandbyState
	}

	push (@snmpoids,$oid_status);

	my $result;
	$result = $snmp->get_request(
			-varbindlist => \@snmpoids
		);
	if (!defined($result)) {
		my $answer = $snmp->error;
		$snmp->close;
		print ("UNKNOWN - SNMP error: $answer\n");
		exit $ERRORS{'UNKNOWN'};
	}

	my $r_ifName = "";
	if (defined($ifDescr_match)) {
		$r_ifName = $result->{"$ifDescr.$ifindex"};
	} else {
		$r_ifName = $result->{"$ifName.$ifindex"};
	}

	if ($result->{$oid_status} !~ /^noSuch/) {
		if ($type eq "isdnd") {
			my %isdnLapdOperStatusCode = (
				1 => "inactive(1)",
				2 => "l1Active(2)",
				3 => "l2Active(3)"
			);
			$output = "LAPD '".$r_ifName."' status is '".$isdnLapdOperStatusCode{$result->{"$oid_status"}}."'";
			$state = 'OK';
			if ($result->{"$oid_status"} != 3) {
				$state = 'CRITICAL';
			}
		} elsif ($type eq "dsx1") {
			my %dsx1LineStatusCode = (
				1 => "dsx1NoAlarm",
				2 => "dsx1RcvFarEndLOF",
				4 => "dsx1XmtFarEndLOF",
				8 => "dsx1RcvAIS",
				16 => "dsx1XmtAIS",
				32 => "dsx1LossOfFrame",
				64 => "dsx1LossOfSignal",
				128 => "dsx1LoopbackState",
				256 => "dsx1T16AIS",
				512 => "dsx1RcvFarEndLOMF",
				1024 => "dsx1XmtFarEndLOMF",
				2048 => "dsx1RcvTestCode",
				4096 => "dsx1OtherFailure",
				8192 => "dsx1UnavailSigStat",
				16384 => "dsx1NetEquipOOS",
				32768 => "dsx1RcvPayloadAIS",
				65536 => "dsx1Ds2PerfThreshold"
			);

			my $statuscode = $result->{"$oid_status"};
			my $statusstring = "";
			while ( my ($index,$descr) = each(%dsx1LineStatusCode)) {
				if (($statuscode & $index) > 0) {
					if (length($statusstring) > 0) { $statusstring .= " " };
					$statusstring .= $descr;
				}
			}
			$output = "DS1 '".$r_ifName."' status is '".$statusstring."'";
			$state = 'OK';
			if ($statuscode != 1) {
				$state = 'CRITICAL';
			}
		} elsif ($type eq "hsrpgrp") {
			my %cHsrpGrpStandbyStates = (
				1 => "initial",
				2 => "learn",
				3 => "listen",
				4 => "speak",
				5 => "standby",
				6 => "active"
			);

			my $r_cHsrpGrpStandbyState = $result->{"$oid_status"};

			$output = "Interface '".$r_ifName."' HSRP Group $hsrpgrp status is '".$cHsrpGrpStandbyStates{$r_cHsrpGrpStandbyState}."'";

			$state = 'OK';
			if ($r_cHsrpGrpStandbyState < 5) {
				$state = 'CRITICAL';
			}
		} else {
	                my %ifOperStatuses = (
				1 => 'up(1)',
				2 => 'down(2)',
				3 => 'testing(3)',
				4 => 'unknown(4)',
				5 => 'dormant(5)',
				6 => 'notPresent(6)',
				7 => 'lowerLayerDown(7)'
			);

			my $r_ifOperStatus = $result->{"$oid_status"};

			$output = "Interface '".$r_ifName."' status is '".
				(defined($ifOperStatuses{$r_ifOperStatus}) ? $ifOperStatuses{$r_ifOperStatus} : "unknown($r_ifOperStatus)").
				"'";
			$state = 'OK';
			if ($r_ifOperStatus ne '1') {
				$state = 'CRITICAL';
			}
		}

		if ($opt_warnadmin) {
			my %ifAdminStatuses = (
				1 => 'up(1)',
				2 => 'down(2)',
				3 => 'testing(3)'
			);

			my $r_ifAdminStatus = $result->{"$ifAdminStatus.$ifindex"};
			if ($r_ifAdminStatus !~ /^noSuch/) {
				$output .= " admin '".$ifAdminStatuses{$r_ifAdminStatus}."'";
				if ($r_ifAdminStatus == 2) { $state = 'WARNING' };
			}
		}

		if ($opt_cisco) {
			my $r_locIfReason = $result->{"$locIfReason.$ifindex"};
			if ($r_locIfReason !~ /^noSuch/) {
				$output .= " (".$r_locIfReason.")"
			}
		}

		if ($opt_ifXTable) {
			my $r_ifAlias = $result->{"$ifAlias.$ifindex"};
			if ($r_ifAlias !~ /^noSuch/) {
				$r_ifAlias =~ s/\|/#/g;
				$output .= " - ".$r_ifAlias;
			}
		}
		$output .= " (index $ifindex)";

		if (defined($ifDescr_match)) {
			if ($ifDescr_match ne $result->{"$ifDescr.$ifindex"}) {
				$output .= " (ifDescr mismatch ".$ifDescr_match."!=".$result->{"$ifDescr.$ifindex"}.")";
				$state = 'CRITICAL';
			}
		}
		if (defined($ifName_match)) {
			if ($ifName_match ne $result->{"$ifName.$ifindex"}) {
				$output .= " (ifName mismatch ".$ifName_match."!=".$result->{"$ifName.$ifindex"}.")";
				$state = 'CRITICAL';
			}
		}
	}
}
print "$state - $output\n";
exit $ERRORS{$state};

sub print_help() {
	print_revision($PROGNAME,'$Revision: 0.81 $ ');
	print "Copyright (c) 2006 Larry Low\n";
	print "This program is licensed under the terms of the\n";
	print "GNU General Public License\n(check source code for details)\n";
	print "\n";
	printf "Check interface status via SNMP.\n";
	printf "(Based of original work by Christoph Kron)\n";
	print "\n";
	print_usage();
	print "\n";
	print " -H (--hostname)     Hostname/port to query - (required) (port defaults to 161)\n";
	print " -C (--community)    SNMP read community or v3 auth (defaults to public)\n";
	print "                     (v3 specified as username:authpassword:... )\n";
	print "                       username = SNMPv3 security name\n";
	print "                       authpassword = SNMPv3 authentication pass phrase (or hexidecimal key)\n";
	print "                       authprotocol = SNMPv3 authentication protocol (md5 (default) or sha)\n";
	print "                       privpassword = SNMPv3 privacy pass phrase (or hexidecmal key)\n";
	print "                       privprotocol = SNMPv3 privacy protocol (des (default) or aes)\n";
	print " -v (--snmp_version) 1 for SNMP v1\n";
	print "                     2 for SNMP v2c (default)\n";
	print "                     3 for SNMP v3\n";
	print " -k (--key)          ifIndex of interface\n";
	print " -I {--ifmib}        IF-MIB ifXTable supported\n";
	print " -D (--ifdescr)      ifDescr to compare to (or use in cacti lookup)\n";
	print " -N (--ifname)       ifName to compare to (or use in cacti lookup)\n";
	print " -t (--type)         check type (defaults to default)\n";
	print " --hsrpgrp           HSRP group to check (requires when type of hsrpgrp)\n";
	print " --cisco             retrieves locIfReason showing last reason for change\n";
	print " --warnadmin         if ifAdminStatus is down will return warning instead of critical\n";
	print "\n";
	print " Cacti Integration:\n";
	print " --cacti_database  cacti database to get index from (default cacti)\n";
	print " --cacti_username  cacti database username (default cacti)\n";
	print " --cacti_password  cacti database password (required to enable check)\n";
	print " --cacti_hostname  cacti database hostname (default localhost)\n";
	print " --cacti_port      cacti database port number (default to 3306)\n";
	print "     (ifDescr or ifName must be define to use as key in cacti database lookup)\n";
	print "\n";
	print " -V (--version)    Plugin version\n";
	print " -h (--help)       usage help\n";
	print "\n";
	print " Check Types:\n";
	print "   default - uses IF-MIB and checks ifOperStatus\n";
	print "   isdnd   - uses ISDN-MIB and checks isdnLapdOperStatus\n";
	print "   dsx1    - uses DS1-MIB and checks dsx1LineStatus\n";
	print "   hsrpgrp - uses CISCO-HSRP-MIB and checks cHsrpGrpStandbyState\n";
	print "             (alarms if not standby or active)\n";
	print "\n";
	support();
}

sub print_usage() {
	print "Usage: \n";
	print "  $PROGNAME -H <HOSTNAME> [-C <community>] -k <ifIndex>\n";
	print "  $PROGNAME [-h | --help]\n";
	print "  $PROGNAME [-V | --version]\n";
}

