#!/usr/bin/perl -w
#
# check_3com_alive -  Check 3Com switch for full stack
# nagios: -epn
#
# Copyright (C) 2009 Jason Abraham <jtabraham@gmail.com>
#
# WCIT
#
# This plugin checks to make sure that all units in a stack are available.  It is meant to 
#  be used as a host check_command to allow for more effective parent/topology testing (see below).
#  Works with 3Com switchs which follow the Huawei-3Com(H3C) a3com.jv-mib.huawei SNMP standard.
# Why is this check command more effective for parent/topology tests then a simple ping?
#  Imagine you have a stack of three switches and the Nagios server is plugged into Unit 1 and the 
#  monitored server(FRED) is plugged into Unit 3.  If Unit 3 fails or is disconnected from the stack the
#  Nagios server can still ping the switch, but can no longer reach FRED.  A ping test would, therfore,
#  incorrectly tag FRED as bad when the real issues is the switch.
#
#
# 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.
#


use strict;
use warnings;
use Nagios::Plugin;
use File::Basename;
use Net::SNMP;

###Constants
use constant WCIT_DEFTCPTIMEOUT     => 5;
# Huawei-3Com OIDs
use constant WCIT_OID_h3cFtmUnitID  => ".1.3.6.1.4.1.43.45.1.10.2.1.1.1.1.1.2";

#evil globals but Perl loves them
use vars qw($sess $np);
#Other Vars
my ($VERSION, $PROGNAME);
my ($err, %resH, $key, $theNum); 

$VERSION = "0.7";
$PROGNAME = basename($0);

$np = Nagios::Plugin->new(
  usage => "Usage: %s -H <host> [ -n|--num <units> ] [ -s|--single <unit> ]
  [ -C <community> ] [ -T |--tcptimeout <secs> ]",
  version => $VERSION,
  blurb => "This plugin checks to make sure that all units in a stack are available.  
  It is  meant to be used as a host check_command to allow for more effective 
  parent/topology testing.  Works with 3Com switches which follow the 
  Huawei-3Com(H3C) a3com.jv-mib.huawei SNMP standard.
  ", 
	extra => "
WARNING: if a single unit fails the entire stack will be deemed down.  For 
  example:  you have two units stacked; The Nagios server and the Web server are
  plugged into Unit 1; the DNS server is plugged into Unit 2.  If Unit 2 fails, 
  Nagios will assume the DNS server and the Web server are unreachable due 
  to topology rules.  If this behavior is undesirable, you can create a host 
  for each unit and use the --single option.
  ",
  timeout => 30,
);

# Define and document the valid command line options
# usage, help, version, timeout and verbose are defined by default.
$np->add_arg(
	spec => 'host|H=s',
	help => 
qq{-H, --host=ADDRESS
   Host name or IP Address},
  required => 1
);
$np->add_arg(
	spec => 'num|n=i',
	help => 
qq{-n, --num=INTEGER
   Total number of units that should be in the stack. Required unless the
   option -s is specified. },
  required => 0
);

$np->add_arg(
	spec => 'single|s=i',
	help => 
qq{-s, --single=INTEGER
   Only test for a specific unit in a stack.  Will override -n option. },
  required => 0
);

$np->add_arg(
	spec => 'community|C=s',
	help => 
qq{-C, --community=STRING
   SNMP read community string (default: public)},
  required => 0,
  default => 'public'
);

$np->add_arg(
	spec => 'tcptimeout|T=i',
	help => 
qq{-T, --tcptimeout=INTEGER
   Timeout value, in seconds, for SNMP responses (default: }.WCIT_DEFTCPTIMEOUT.')'.qq{
   Not to be confused with general plugin timeout},
  required => 0,
  default => WCIT_DEFTCPTIMEOUT
);

# Parse arguments and process standard ones (e.g. usage, help, version)
$np->getopts;

# Option checking MORE NEEDED
$np->nagios_die("Either 'num' or 'single' options must be specified") unless(defined($np->opts->num) || defined($np->opts->single));
$np->nagios_die("TCP Timeout must greater than 0") if( $np->opts->tcptimeout < 1 );


##############################################################################
# Start actual work

#Create Session
($sess,$err) = Net::SNMP->session(Hostname => $np->opts->host,
                                      Community => $np->opts->community,
                                      Version => 1,
                                      Timeout => $np->opts->tcptimeout);
$np->nagios_exit(CRITICAL,"$err") unless($sess);

# Start Plugin Timeout
alarm $np->opts->timeout;
%resH = get_snmphash(WCIT_OID_h3cFtmUnitID,'h3cFtmUnitID');
alarm(0);
$np->nagios_exit(CRITICAL,"SNMP ERROR") unless (%resH);

if ( defined($np->opts->single) ) {
  $theNum = $np->opts->single;
  foreach $key (keys %resH) {
    $np->nagios_exit(OK,"Unit #".$theNum." in stack") if( $theNum == $resH{$key} );
  }
  $np->nagios_exit(CRITICAL,"Unit #".$theNum." not found")
}

$theNum = scalar keys(%resH);
$np->nagios_exit(CRITICAL,"Only $theNum of ".$np->opts->num." units in stack") if( $theNum < $np->opts->num );
$np->nagios_exit(OK,"$theNum of ".$np->opts->num." units in stack");


#################################
# Start SUBS
sub get_snmphash {
  my ($oid, $oidtext, $res, %orghash, %newhash, $oidLen, $orgKey, $newKey);
  ($oid, $oidtext) = @_;
  $res = $sess->get_table($oid);
  unless(defined $res) {
    print ">>SNMP Error:".$sess->error."  OID:$oidtext($oid)\n" if($np->opts->verbose);
    return ();
  }
  %orghash = %{$res};
  %newhash = ();
  $oidLen = length($oid)+1; #+1 for the . appeneded
  foreach $orgKey (keys %orghash)
  {
    $newKey = substr("$orgKey",$oidLen);
    $newhash{$newKey} = $orghash{$orgKey};
  }
  return %newhash;
}

