#! /usr/bin/perl -wT
#
# Check_apc_ups - Check APC UPS status via SNMP
# Shamelessly copied from check_breeze.pl
#
# To do:
# - Send SNMP queries directly, instead of forking `snmpget`.
# - Make the status less verbose.  Maybe we can send an "onLine, time
#   remaining: hh:mm:ss" if all is well, and a list of specific problems
#   if something is broken. 

use strict;
use Getopt::Long;
use vars qw($opt_V $opt_h $opt_H $opt_T $opt_t $opt_R $opt_r 
  $opt_L $opt_l $PROGNAME);
use lib utils.pm;
use utils qw(%ERRORS &print_revision &support &usage);

sub print_help ();
sub print_usage ();
sub get_snmp_int_val ($);
sub escalate_exitval ($);

$ENV{'PATH'}='';
$ENV{'BASH_ENV'}=''; 
$ENV{'ENV'}='';

Getopt::Long::Configure('bundling');
GetOptions
	("V"   => \$opt_V, "version"		=> \$opt_V,
	 "h"   => \$opt_h, "help"		=> \$opt_h,
	 "T=s" => \$opt_T, "temp-critical"	=> \$opt_T,
	 "t=s" => \$opt_t, "temp-warning"	=> \$opt_t,
	 "R=s" => \$opt_R, "runtime-critical"	=> \$opt_R,
	 "r=s" => \$opt_r, "runtime-warning"	=> \$opt_r,
	 "L=s" => \$opt_L, "load-critical"	=> \$opt_L,
	 "l=s" => \$opt_l, "load-warning"	=> \$opt_l,
	 "H=s" => \$opt_H, "hostname=s"		=> \$opt_H);

if ($opt_V) {
	print_revision($PROGNAME,'$Revision$');
	exit $ERRORS{'OK'};
}

if ($opt_h) {print_help(); exit $ERRORS{'OK'};}

($opt_H) || ($opt_H = shift) || usage("Host name/address not specified\n");
my $host = $1 if ($opt_H =~ /([-.A-Za-z0-9]+)/);
($host) || usage("Invalid host: $opt_H\n");

# Defaults

$opt_R *= 60 * 100 if (defined $opt_R);	# Convert minutes to secs/100
$opt_r *= 60 * 100 if (defined $opt_R);

my $tempcrit    = $opt_T || 60;
my $tempwarn    = $opt_t || 40;
my $runtimecrit = $opt_R || 30 * 60 * 100;     # Secs / 100
my $runtimewarn = $opt_r || 60 * 60 * 100;
my $loadcrit    = $opt_L || 85;
my $loadwarn    = $opt_l || 50;

if ($tempcrit !~ /\d+/) { usage ("Invalid critical temperature threshold.\n"); }
if ($tempwarn !~ /\d+/) { usage ("Invalid critical temperature threshold.\n"); }

if ($runtimecrit !~ /\d+/) {
  usage ("Invalid critical run time threshold.\n");
}
if ($runtimewarn !~ /\d+/) {
  usage ("Invalid warning run time threshold.\n");
}

if ($loadcrit !~ /\d+/ || $loadcrit < 0 || $loadcrit > 100) {
  usage ("Invalid critical load threshold.\n");
}
if ($loadwarn !~ /\d+/ || $loadwarn < 0 || $loadwarn > 100) {
  usage ("Invalid warning load threshold.\n");
}


# APC UPS OIDs
# APC MIBs are available at ftp://ftp.apcftp.com/software/pnetmib/mib
my $upsBasicOutputStatus          = ".1.3.6.1.4.1.318.1.1.1.4.1.1.0";
my $upsBasicBatteryStatus         = ".1.3.6.1.4.1.318.1.1.1.2.1.1.0";
my $upsAdvInputLineFailCause      = ".1.3.6.1.4.1.318.1.1.1.3.2.5.0";
my $upsAdvBatteryTemperature      = ".1.3.6.1.4.1.318.1.1.1.2.2.2.0";
my $upsAdvBatteryRunTimeRemaining = ".1.3.6.1.4.1.318.1.1.1.2.2.3.0";
my $upsAdvBatteryReplaceIndicator = ".1.3.6.1.4.1.318.1.1.1.2.2.4.0";
my $upsAdvOutputLoad              = ".1.3.6.1.4.1.318.1.1.1.4.2.3.0";
my $upsAdvTestDiagnosticsResults  = ".1.3.6.1.4.1.318.1.1.1.7.2.3.0";

my @outputStatVals = (
  [ undef, undef ],					# pad 0
  [ undef, undef ],					# pad 1
  [ "onLine",			$ERRORS{'OK'} ],	# 2
  [ "onBattery",		$ERRORS{'WARNING'} ],	# 3
  [ "onSmartBoost",		$ERRORS{'WARNING'} ],	# 4
  [ "timedSleeping",		$ERRORS{'WARNING'} ],	# 5
  [ "softwareBypass",		$ERRORS{'WARNING'} ],	# 6
  [ "off",			$ERRORS{'CRITICAL'} ],	# 7
  [ "rebooting",		$ERRORS{'WARNING'} ],	# 8
  [ "switchedBypass",		$ERRORS{'WARNING'} ],	# 9
  [ "hardwareFailureBypass",	$ERRORS{'CRITICAL'} ],	# 10
  [ "sleepingUntilPowerReturn",	$ERRORS{'CRITICAL'} ],	# 11
  [ "onSmartTrim",		$ERRORS{'WARNING'} ],	# 12
);

my @failCauseVals = (
  undef,
  "noTransfer",
  "highLineVoltage",
  "brownout",
  "blackout",
  "smallMomentarySag",
  "deepMomentarySag",
  "smallMomentarySpike",
  "largeMomentarySpike",
  "selfTest",
  "rateOfVoltageChnage",
);

my @battStatVals = (
  [ undef, undef ],				# pad 0
  [ undef, undef ],				# pad 1
  [ "batteryNormal",	$ERRORS{'OK'} ],	# 2
  [ "batteryLow",	$ERRORS{'CRITICAL'} ],	# 3
);

my @battReplVals = (
  [ undef, undef ],		 			# pad 0
  [ "noBatteryNeedsReplacing",	$ERRORS{'OK'} ],	# 1
  [ "batteryNeedsReplacing",	$ERRORS{'CRITICAL'} ],	# 2
);

my @diagnosticsResultsVals = (
  [ undef, undef ],				# pad 0
  [ "OK",		$ERRORS{'OK'} ],	# 1
  [ "failed",		$ERRORS{'CRITICAL'} ],	# 2
  [ "invalidTest",	$ERRORS{'CRITICAL'} ],	# 3
  [ "testInProgress",	$ERRORS{'OK'} ],	# 4
);

my $exitval     = $ERRORS{'UNKNOWN'};
my $data;
my $onbattery   = 3;

$data = get_snmp_int_val( $upsBasicOutputStatus );

print "Output status: ";
if (defined ($data) && defined ($outputStatVals[$data][0])) {
  print "$outputStatVals[$data][0] | ";
  escalate_exitval($outputStatVals[$data][1]);
} else {
  print "unknown | ";
}

$data = get_snmp_int_val( $upsAdvBatteryRunTimeRemaining );

print "Rem time: ";
if (defined ($data)) {
  my $hrs  = int($data / (60 * 60 * 100)); # Data is hundredths of a second
  my $mins = int($data / (60 * 100)) % 60;
  my $secs = ($data % 100) / 100;
  printf "%d:%02d:%05.2f | ", $hrs, $mins, $secs;
  if ($data <= $runtimecrit) {
    escalate_exitval($ERRORS{'CRITICAL'});
  } elsif ($data <= $runtimewarn) {
    escalate_exitval($ERRORS{'WARNING'});
  } else {
    escalate_exitval($ERRORS{'OK'});
  }
} else {
  print "unknown | ";
}

$data = get_snmp_int_val( $upsBasicBatteryStatus );

print "Battery status: ";
if (defined ($data) && defined ($battStatVals[$data][0])) {
  my $failcause = "unknown";
  my $fc = get_snmp_int_val( $upsAdvInputLineFailCause );
  if ($data == $onbattery) {
    if (defined ($failCauseVals[$fc])) { $failcause = $failCauseVals[$fc]; }
    print "$battStatVals[$data][0] ($failcause) | ";
  } else {
    print "$battStatVals[$data][0] | ";
  }
  escalate_exitval($battStatVals[$data][1]);
} else {
  print "unknown | ";
}

$data = get_snmp_int_val( $upsAdvBatteryTemperature );

print "Battery temp(C): ";
if (defined ($data)) {
  print "$data | ";
  if ($data >= $tempcrit) {
    escalate_exitval($ERRORS{'CRITICAL'});
  } elsif ($data >= $tempwarn) {
    escalate_exitval($ERRORS{'WARNING'});
  } else {
    escalate_exitval($ERRORS{'OK'});
  }
} else {
  print "unknown | ";
}

$data = get_snmp_int_val( $upsAdvBatteryReplaceIndicator );

print "Battery repl: ";
if (defined ($data) && defined ($battReplVals[$data][0])) {
  print "$battReplVals[$data][0] | ";
  escalate_exitval($battReplVals[$data][1]);
} else {
  print "unknown | ";
}

$data = get_snmp_int_val( $upsAdvOutputLoad );

print "Output load (%): ";
if (defined ($data)) {
  print "$data | ";
  if ($data >= $loadcrit) {
    escalate_exitval($ERRORS{'CRITICAL'});
  } elsif ($data >= $loadwarn) {
    escalate_exitval($ERRORS{'WARNING'});
  } else {
    escalate_exitval($ERRORS{'OK'});
  }
} else {
  print "unknown | ";
}

$data = get_snmp_int_val( $upsAdvTestDiagnosticsResults );

print "Diag result: ";
if (defined ($data) && defined ($diagnosticsResultsVals[$data][0])) {
  print "$diagnosticsResultsVals[$data][0]\n";
  escalate_exitval($diagnosticsResultsVals[$data][1]);
} else {
  print "unknown\n";
}


exit $exitval;


sub print_usage () {
	print "Usage: $PROGNAME -H <host> -T temp -t temp -R minutes -r minutes\n";
	print "  -L percent -l percent\n";
}

sub print_help () {
	print_revision($PROGNAME,'$Revision$');
	print "Copyright (c) 2001 Gerald Combs/Jeffrey Blank/Karl DeBisschop

This plugin reports the status of an APC UPS equipped with an SNMP management
module.

";
	print_usage();
	print "
-H, --hostname=HOST
   Name or IP address of host to check
-T --temp-critical
   Battery degrees C above which a CRITICAL status will result (default: 60)
-t --temp-warning
   Battery degrees C above which a WARNING status will result (default: 40)
-R --runtime-critical
   Minutes remaining below which a CRITICAL status will result (default: 30)
-r --runtime-warning
   Minutes remaining below which a WARNING status will result (default: 60)
-L --load-critical
   Output load pct above which a CRITICAL status will result (default: 85
-l --load-warning
   Output load pct above which a WARNING status will result (default: 50

";
	support();
}

sub get_snmp_int_val ($) {
  my $val=0;
  my $oid = shift(@_);

  $val = `/usr/bin/snmpget $host public $oid 2> /dev/null`;
  my @test = split(/ /,$val,3);

  return undef unless (defined ($test[2]));

  if ($test[2] =~ /\(\d+\)/) {	# Later versions of UCD SNMP
    ($val) = ($test[2] =~ /\((\d+)\)/);
  } elsif ($test[2] =~ /: \d+/) {
    ($val) = ($test[2] =~ /: (\d+)/);
  } else {
    $val = $test[2];
  }

  return $val;
}

sub escalate_exitval ($) {
  my $newval = shift(@_);

  if ($newval > $exitval) { $exitval = $newval; }
}