#!/usr/bin/perl -w
#
# 1.3.3 (2016-02-18)
# Added 'removed' state. Thanks to Paulisse85 and srdjanb
#
# 1.3.2 (2014-02-24)
# Changed the logic to get the oid to support other switches other than 3750 (Thanks to Marco Gruber)
#
# 1.3.1 (2013-03-08)
# Modified by Onlight, Inc. <support@onlight.com>
#
# Added checks of stacking port status (Up or Down).  This is only
# checked if the ring is in degraded state (Half) or the -P flag is used
# at runtime.
#
# Also, changed the logic by which the exit status is determined and did
# some other minor clean up.
#
# Nic Bernstein <nic@onlight.com>
# Onlight, Inc.
# 219 N. Milwaukee Street
# Suite 2A
# Milwaukee, WI  53202

use strict;
use Getopt::Long;
use Net::SNMP;

my $Version =			"1.3.3 20160218";
my $snmp_timeout = 		3;

my $cisco_stack_table =		"1.3.6.1.4.1.9.9.500.1.2.1.1.1";
my $cisco_stack_state =		"1.3.6.1.4.1.9.9.500.1.2.1.1.6";
my $cisco_stack_ring =		"1.3.6.1.4.1.9.9.500.1.1.3.0";
my $cisco_stackport =		"1.3.6.1.4.1.9.9.500.1.2.2.1.1";
my $cisco_stacksub_table =	"1.3.6.1.4.1.9.9.276.1.6.1.1.2.14.83.116.97.99.107.83.117.98.45.83.116";

my $status =			"OK";
my $session;
my $error;
my $result;
my $stackring_state;
my $stackport_state;

my $o_debug =			0;
my $o_alarm =			0;
my $o_ports =			0;
my $o_host;
my $o_community;
my $o_version;
my $o_help;

my %members;
my %ERRORS = (
	'OK'		=>	0,
	'WARNING'	=>	1,
	'CRITICAL'	=>	2,
	'UNKNOWN'	=>	3,
	'DEPENDENT'	=>	4,
);
my %STACK_STATES = (
         '1'		=>	'waiting',
         '2'		=>	'progressing',
         '3'		=>	'added',
         '4'		=>	'ready',
         '5'		=>	'sdmMismatch',
         '6'		=>	'verMismatch',
         '7'		=>	'featureMismatch',
         '8'		=>	'newMasterInit',
         '9'		=>	'provisioned',
         '10'		=>	'invalid',
         '11'		=>	'removed',
);


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

check_options();

($session, $error) = Net::SNMP->session(
		-hostname	=> $o_host,
		-community	=> $o_community,
		-port		=> 161,
		-timeout	=> $snmp_timeout
	);

if (!defined($session)) {
	printf("ERROR: %s.\n", $error);
	exit $ERRORS{"UNKNOWN"};
}

#
# Get Cisco stack table
#
$result = $session->get_table(
		-baseoid => $cisco_stack_table
	);

if (!defined($result)) {
	printf("ERROR: %s.\n", $session->error);
	$session->close;
	exit $ERRORS{"UNKNOWN"};
}

foreach my $key ( keys %{$result}) {
	my @key_split = split('\.', $key);
	my $id = pop(@key_split);
	#my $id = $$result{$key};
	#my $newid = ( $id * 1000 ) + 1;
	my $oid = "$cisco_stack_state.$id";
	print "DEBUG: key=$key, id=$id, oid=$oid\n" if $o_debug;

	my $result2 = $session->get_request(
			-varbindlist	=> [$oid]
		);

	if (!defined($result2)) {
		printf("ERROR: %s.\n", $session->error);
		$session->close;
		exit $ERRORS{"UNKNOWN"};
	}

	print "DEBUG: member=$$result{$key}, oid=$oid,state=" . $result2->{"$oid"} . "\n" if $o_debug;

	$members{$$result{$key}} = $result2->{"$oid"};
}

if ($o_alarm) {
	$members{"1"} = 6;
	print "-- SIMULATING ALARM -- ";
}

#
# Get Cisco stack ring speed if more than one stack member
#
if ( keys(%{$result}) > 1 ) {
	$result = $session->get_request(
			-varbindlist => [$cisco_stack_ring]
	);

	if (!defined($result)) {
		printf("ERROR: %s.\n", $session->error);
		$session->close;
		exit $ERRORS{"UNKNOWN"};
	}

	#
	# Parse SNMP stack ring result
	#
	if ( $result->{$cisco_stack_ring} == 1  and ! $o_alarm) {
		$stackring_state = "Full";
	}
	else {
		$stackring_state = "Half";
		$status = "WARNING";
	}

	print "DEBUG: snmp_ring_state = " . $result->{$cisco_stack_ring} . " -> ring_state = $stackring_state\n" if $o_debug;
	print "Stack Ring: $stackring_state, ";
}

#
# Get Cisco stack port state if there is a ring fault
#
if ( $stackring_state eq "Half" or $o_ports ) {
	#
	# Get Cisco stack subinterface table
	#
	$result = $session->get_table(
			-baseoid => $cisco_stacksub_table
		);

	if (!defined($result)) {
		printf("ERROR: %s.\n", $session->error);
		$session->close;
		exit $ERRORS{"UNKNOWN"};
	}
	
	foreach my $oid ( keys %{$result}) {
		my $portoid = $cisco_stackport . '.' . $result->{$oid};
		my $result2 = $session->get_request(
			-varbindlist => [$portoid]
		);
		if (!defined($result2)) {
			printf("ERROR: %s.\n", $session->error);
			$session->close;
			exit $ERRORS{"UNKNOWN"};
		}
		if ( $result2->{$portoid} == 1 ) {
			$stackport_state = "Up";
		}
		else {
			$stackport_state = "Down";
		}
			
		my $name = "";
		my @letters = split('\.', substr($oid, length($cisco_stacksub_table) + 1));
		for ( @letters ) {
			$name .= sprintf("%c", $_);
		}
		print "DEBUG: snmp_stack_port_state = " . $result2->{$portoid} . " -> stack_port_state = $stackport_state\n" if $o_debug;
		printf("StackSub-St%s: %s, ", $name, $stackport_state);
	}
}

$session->close;

my $nitems = keys (%members);
my $n = 0;

foreach my $member (keys (%members)) {
	$n++;
	if ( $members{$member} != 4 and $members{$member} != 9 ) {
		$status = "CRITICAL";
	}
	print "Member $member: $STACK_STATES{$members{$member}}";
	if ($n < $nitems) {
		print ", ";
	} else {
		print "\n";
	}
}

print "DEBUG: $status: $ERRORS{$status}\n" if $o_debug;

exit $ERRORS{$status};

##########################
#
#         FUNCTIONS
#
##########################

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

sub usage {
	print <<DATA;

Usage: $0 [-V] [-h] [-D] [-A] -H <host> -C <community>

-h, --help
   prints this help message
-V, --version
   prints version number
-D, --debug
   prints debug info. do not use in production.
-P, --ports
   report on stacking port status, even if ring is full
-A, --alarm
   the plugin simulates an alarm without the need to break the stack!!!
-H, --hostname=HOST
   name or IP address of host to check
-C, --community=STRING NAME
   community name for the host's SNMP agent (implies v1/v2 protocol)

DATA
}

sub help {
	print <<DATA;

SNMP Cisco stack for Nagios version: $Version
Author: Andrea Gabellini - <andrea.gabellini\@telecomitalia.sm>
Modified: Nic Bernstein - <nic\@onlight.com>
Check for stack's status of Cisco 3750 and probably others (i.e. 3650, 3850).

DATA

	usage();
}

sub check_options {
	Getopt::Long::Configure ("bundling");
	GetOptions(
		'h'     => \$o_help,    	'help'        	=> \$o_help,
		'V'	=> \$o_version,		'version'	=> \$o_version,
		'D'	=> \$o_debug,		'debug'		=> \$o_debug,
		'P'	=> \$o_ports,		'ports'		=> \$o_ports,
		'A'	=> \$o_alarm,		'alarm'		=> \$o_alarm,
		'H:s'   => \$o_host,		'hostname:s'	=> \$o_host,
		'C:s'   => \$o_community,	'community:s'	=> \$o_community,
	);
	if (defined ($o_help) ) {
		help();
		exit $ERRORS{"UNKNOWN"};
	}
	if (defined($o_version)) {
		version();
		exit $ERRORS{"UNKNOWN"};
	}
	if (!defined($o_host) ) {
		usage();
		exit $ERRORS{"UNKNOWN"};
	}
	if (!defined($o_community) ) {
		usage();
		exit $ERRORS{"UNKNOWN"};
	}
}
