#!/usr/bin/perl -w
#
# check_3com_ensure -  Check 3Com switch various parameters
# nagios: -epn
#
# Copyright (C) 2009 Jason Abraham <jtabraham@gmail.com>
#
# WCIT
#
# 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_WARNFLAG      => 1;
use constant WCIT_CRITFLAG      => 2;
use constant WCIT_DEFTCPTIMEOUT => 5;
use constant WCIT_DEFWAGE       => 120;   # 2 hours
use constant WCIT_DEFCAGE       => 2880;  # 2 days
use constant WCIT_TESTCFG       => 1;
use constant WCIT_TESTIGMP      => 2;
use constant WCIT_TESTSTP       => 4;
# Huawei-3Com OIDs
use constant WCIT_OID_h3cCfgRunModifiedLast => ".1.3.6.1.4.1.43.45.1.6.10.1.1.1.0";
use constant WCIT_OID_h3cCfgRunSavedLast    => ".1.3.6.1.4.1.43.45.1.6.10.1.1.2.0";
use constant WCIT_OID_hwIgmpSnoopingStatus  => ".1.3.6.1.4.1.43.45.1.2.23.1.7.1.1.0";
use constant WCIT_OID_hwdot1sStpStatus      => ".1.3.6.1.4.1.43.45.1.2.23.1.14.1.0";
use constant WCIT_OID_ENABLED   => 1;
use constant WCIT_OID_DISABLED  => 2;


#evil globals but Perl loves them
use vars qw($sess $np $status $msg);

#Other vars
my ($VERSION,$PROGNAME);
my ($onlyTest,$warn,$crit);

#INIT
$VERSION = "0.7";
$PROGNAME = basename($0);
$status = 0;
$msg = undef;
$onlyTest = 0;

$np = Nagios::Plugin->new(
  usage => "Usage: %s -H <host> [ -C <community> ] [ -w <mins> ] [ -c <mins> ] 
  [ -N ]   [ -i <bool> ] [ -I ]    [ -s <bool> ] [ -S ] 
  [ -p <path> ] [ -T |--tcptimeout <secs> ]",
  version => $VERSION,
  blurb => "This plugin ensures various settings of 3Com switchs following 
  the Huawei-3Com(H3C) a3com.jv-mib.huawei SNMP standard.", 
  extra => "Note: If path is set to zero any unsaved config changes on the switch will result
  in an immediate critical alarm."
);

# 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 => 'community|C=s',
	help => 
  qq{-C, --community=STRING
   SNMP read community string (default: public)
   },
  required => 0,
  default => 'public'
);

$np->add_arg(
	spec => 'warn|w=i',
	help => 
  qq{-w, --warn=INTEGER
   The number of minutes the configuration can remain unsaved before a 
   warning alarm will be issued. (default: }.WCIT_DEFWAGE.' mins)',
  required => 0,
  default => WCIT_DEFWAGE
);

$np->add_arg(
	spec => 'crit|c=i',
	help => 
  qq{-c, --crit=INTEGER
   The number of minutes the configuration can remain unsaved before a 
   critical alarm will be issued. (default: }.WCIT_DEFCAGE." mins)\n",
  required => 0,
  default => WCIT_DEFCAGE
);

$np->add_arg(
  spec => 'noconftest|N+',
  help =>
  qq{-N, --noconftest
   Disable testing of unsaved configuration. (default: test conf )
   },
  required => 0
); 

$np->add_arg(
  spec => 'igmp|i=i',
  help =>
  qq{-i, --igmp=0/1
   Enable IGMP Snooping testing.  Ensure IGMP: 0 = disabled or 1 = enabled, 
   (default: not tested) },
  required => 0
); 
   
$np->add_arg(
  spec => 'igmpcrit|I+',
  help =>
  qq{-I, --igmpcrit
   Issue a critital alarm if IGMP test fails (default: warning on IGMP fail)
   },
  required => 0
); 

$np->add_arg(
  spec => 'stp|s=i',
  help =>
  qq{-s, --stp=0/1
   Check STP global setting for entire switch, not individual ports.  
   Ensure STP: 0 = disabled or 1 = enabled (default: not tested)},
  required => 0
); 
   
$np->add_arg(
  spec => 'stpcrit|S+',
  help =>
  qq{-S, --stpcrit
   Issue a critital alarm if STP test fails (default: warning on STP fail)
   },
  required => 0
); 

$np->add_arg(
	spec => 'path|p=s',
	help => 
  qq{-p, --path=STRING
   Path for config history data to be stored between test runs. Make sure the
   nagios user has rights to read and create files in this directory.
   Specify 0 to disable. (default: /tmp )
   },
  required => 0,
  default => '/tmp'
);


$np->add_arg(
	spec => 'tcptimeout|T=i',
	help => 
  qq{-T, --tcptimeout=INTEGER
   Timeout value, in seconds, for SNMP responses (default: }.WCIT_DEFTCPTIMEOUT.qq{ secs)
   Not to be confused with general plugin timeout},
  required => 0,
  default => WCIT_DEFTCPTIMEOUT
);

##### Parse arguments end do error checking !!!MORE CHECKS NEEDED!!!
$np->getopts;

unless(defined $np->opts->noconftest)
{
  $warn = WCIT_DEFWAGE;
  $warn = $np->opts->warn if(defined $np->opts->warn);
  $crit = WCIT_DEFCAGE;
  $crit = $np->opts->warn if(defined $np->opts->crit);
  $onlyTest = $onlyTest | WCIT_TESTCFG;
  $np->nagios_die("Cannot set path to 0 and also set warn or crit values.") if($np->opts->path eq "0" && (defined $np->opts->warn || defined $np->opts->crit ) )
}

if(defined $np->opts->igmp) {
  $np->nagios_die("Invalid IGMP test value") unless( $np->opts->igmp == 0 || $np->opts->igmp == 1 );
  $onlyTest = $onlyTest | WCIT_TESTIGMP;
}elsif(defined $np->opts->igmpcrit) { $np->nagios_die("IGMPCrit specified without IGMP testing enabled"); }

if(defined $np->opts->stp) {
  $np->nagios_die("Invalid STP test value") unless( $np->opts->stp == 0 || $np->opts->stp == 1 );
  $onlyTest = $onlyTest | WCIT_TESTSTP;
}elsif(defined $np->opts->stpcrit) { $np->nagios_die("STPCrit specified without STP testing enabled"); }

#Check path options
unless ( $np->opts->path eq "0" ) {
  np->nagios_die("The temporary path does not exist: ".$np->opts->path) unless( -d $np->opts->path );
  np->nagios_die("No write permission on path: ".$np->opts->path) unless( -w $np->opts->path );
}

$np->nagios_die("TCP Timeout must greater than 0") if( $np->opts->tcptimeout < 1 );

##############################################################################
# Start actual work

#Create Session
my $err;
($sess,$err) = Net::SNMP->session(Hostname => $np->opts->host,
                                      Community => $np->opts->community,
                                      Version => 1,
                                      Timeout => $np->opts->tcptimeout,
                                      Translate => [
                                          -timeticks => 0x0
                                        ]
                                      );
$np->nagios_exit(CRITICAL,"$err") unless($sess);

# Start Plugin Timeout
alarm $np->opts->timeout;

do_cfgtest($warn,$crit) if( $onlyTest & WCIT_TESTCFG );
my ($eVal, $dCrit);
if( $onlyTest & WCIT_TESTIGMP )
{
  $eVal = WCIT_OID_DISABLED;
  $eVal = WCIT_OID_ENABLED if($np->opts->igmp == 1);
  $dCrit = 0;
  $dCrit = 1 if(defined $np->opts->igmpcrit);
  do_ensureTest('IGMP',WCIT_OID_hwIgmpSnoopingStatus,'hwIgmpSnoopingStatus',$eVal,$dCrit);
}
if( $onlyTest & WCIT_TESTSTP )
{
  $eVal = WCIT_OID_DISABLED;
  $eVal = WCIT_OID_ENABLED if($np->opts->stp == 1);
  $dCrit = 0;
  $dCrit = 1 if(defined $np->opts->stpcrit);
  do_ensureTest('STP',WCIT_OID_hwdot1sStpStatus,'hwdot1sStpStatus',$eVal,$dCrit);
}

alarm(0);

$np->nagios_exit(CRITICAL,$msg) if($status & WCIT_CRITFLAG);
$np->nagios_exit(WARNING,$msg) if($status & WCIT_WARNFLAG);
$np->nagios_exit(OK,$msg);


  
#################################
# Start SUBS

sub do_ensureTest { 
  my ($test, $oid, $oidText, $ensureValue, $doCrit) = @_;
  my $theVal;
  
  if( defined $msg ) { $msg .= "  $test:"; }
  else { $msg = "$test:"; }
  
  #Pull SNMP info
  $theVal = get_snmpval($oid,$oidText);
  unless(defined($theVal)) {
    $status |= WCIT_CRITFLAG;
    $msg .= 'SNMP ERROR';
    return;
  }
  if($theVal == $ensureValue) { $msg .= 'OK'; return; }
  if($theVal == WCIT_OID_ENABLED) { $msg .= 'ENABLED'; }
  else { $msg .= 'DISABLED'; }
  if($doCrit) { $status |= WCIT_CRITFLAG; }
  else { $status |= WCIT_WARNFLAG; }
  return;
}

sub do_cfgtest {
  my ($warnmin, $critmin) = @_;

  if( defined $msg ) { $msg .= '  CFG:'; }
  else { $msg = 'CFG:'; }

  #Pull SNMP info
  my ($currMod, $currSave);
  $currMod = get_snmpval(WCIT_OID_h3cCfgRunModifiedLast,'h3cCfgRunModifiedLast');
  unless(defined($currMod)) {
    $status |= WCIT_CRITFLAG;
    $msg .= 'SNMP ERROR';
    return;
  }
  $currSave = get_snmpval(WCIT_OID_h3cCfgRunSavedLast,'h3cCfgRunSavedLast');
  unless(defined($currSave)) {
    $status |= WCIT_CRITFLAG;
    $msg .= 'SNMP ERROR';
    return;
  }
  
  #Not temp file simple declare CRIT
  if($np->opts->path eq "0") { 
    if($currSave < $currMod) {
      $msg .= "UNSAVED";
      $status |= WCIT_CRITFLAG; 
      return;
    }
    $msg .= "OK";
    return;
  }
  
  #If all is Okay delete file and return
  my ($filepath, $host);
  $host = $np->opts->host;
  $host =~ s/\./\-/g;
  $filepath = $np->opts->path . "/nagios_3com_ensure_" . $host .".dat";
  if($currSave >= $currMod) {
    unlink($filepath) if( -e $filepath );
    $msg .= "OK";
    return;
  }

  #Unsaved Modifications
  #If there is no file create and return
  unless( -e $filepath && -s $filepath ) {
    open(OUTFILE,">",$filepath) or mydie("Unable to open file for writing: ".$filepath);
    print OUTFILE time . ":$currMod:$currSave";
    $msg .= 'UNSAVED(0m)';
    $status |= WCIT_CRITFLAG if($critmin == 0); 
    $status |= WCIT_WARNFLAG if($warnmin == 0);
    return;
  }
  #There is a file so load the values
  my ($tmpStor, $prevTime, $prevMod, $prevSave, $diffTime);
  open(INFILE,"<",$filepath) or mydie("Unable to open file for reading: ".$filepath);
  $tmpStor = <INFILE>;
  chomp $tmpStor;
  ($prevTime, $prevMod, $prevSave) = split(/:/,$tmpStor);
  #Integrity checks 
  unless($prevTime < time && $prevMod > $prevSave) {
    #File is bad so act as if it doesn't exist ---SAME CODE AS ABOVE (didn't want another function)---
    do_storeCfg($filepath, $currMod, $currSave);
    $msg .= 'UNSAVED(0m)';
    $status |= WCIT_CRITFLAG if($critmin == 0); 
    $status |= WCIT_WARNFLAG if($warnmin == 0);
    return;
  }
      
  $diffTime = (time - $prevTime)/60;
  $status |= WCIT_CRITFLAG if($diffTime >= $critmin);
  $status |= WCIT_WARNFLAG if($diffTime >= $warnmin);
  $msg = sprintf("%sUNSAVED(%.0fm)",$msg,$diffTime);
  return;
}

sub get_snmpval {
  my ($oid, $oidtext) = @_;
  my $res = $sess->get_request($oid);
  unless(defined $res) {
    print ">>SNMP Error:".$sess->error."  OID:$oidtext($oid)\n" if($np->opts->verbose);
    return undef;
  }
  return $res->{$oid};
}

sub mydie {
  my $msg = @_;
  alarm(0);
  $np->nagios_die($msg);
}
