#! /usr/bin/perl
###############################################################################
#                                                                             #
#             Check an APC Environmental Monitoring Probe via SNMP            #
#                                                                             #
###############################################################################
#
# Copyright (c) 2005 Simon Butcher <simon@butcher.name>
#  
# 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
#
##############################################################################
#
#
# Performance data:
#    +-------+-----------------------------------------------+
#    | Field | Description                                   |
#    +-------+-----------------------------------------------+
#    |   1   | Remote temperature (degrees celcius)          |
#    |   2   | Remote temperature scale (1 = C, 2 = F)       |
#    |   3   | Remote humidity (percent)                     |
#    |   4   | Remote probe name                             |
#    |   5   | Probe connection status (1 = Bad, 2 = OK)     |
#    +-------+-----------------------------------------------+
#
##############################################################################


use Net::SNMP;
use Getopt::Std;

$script = "check_snmp_apc_emp";
$script_title =
  "Check an APC Environmental Monitoring Probe via SNMP";
$script_version = "20051203";

# SNMP options
$version = "1";
$timeout = 2;

# Various SNMP OID prefixes to look at..
$oid_emp                = ".1.3.6.1.4.1.318.1.1.10"; # Environmental Monitoring
$oid_emp_ext_probe      = ".1"; # (+ above) external probes
$oid_emp_int_probe      = ".2"; # (+ above) internal and integrated probes
$oid_probe_ext_status   = ".3.3.1"; # (+ emp_ext_probe) probe status table
$oid_probe_int_status   = ".3.2.1"; # (+ emp_int_probe) probe status table
$oid_probe_status_name  = ".2"; # (+ status + idx) the probe's name
$oid_probe_status_conn  = ".3"; # (+ status + idx) connection status (1=Bad, 2=OK)
$oid_probe_status_temp  = ".4"; # (+ status + idx) the temperature
$oid_probe_status_scale = ".5"; # (+ status + idx) temperature units (1=C, 2=F)
$oid_probe_status_humid = ".6"; # (+ status + idx) the humidity

# The OID prefix for the bit of the table for the probe we found
$probe_table_oid = "";
$probe_table_index = "";

# Thresholds
$critical_temp = -1;
$warning_temp = -1;
$critical_humidity_max = -1;
$warning_humidity_max = -1;
$critical_humidity_min = -1;
$warning_humidity_min = -1;

# Our return status - we start with 0 (OK) and hope for the best
$status = 0;

# Our return string with lots of interesting stuff in it
$returnstr = "";

# The SNMP hostname and community to use for the query
$hostname = "";
$community = "public";


# Grab the command line options
getopts("h:H:C:P:w:c:");

# If we didn't get any options, show some help and quit
if ($opt_h) {
    usage();
    exit(-1); # Unknown
}

# Get the hostname, if it was given..
if (defined($opt_H)) {
    $hostname = $opt_H;
} else {
    # We really need a hostname
    usage();
    exit(-1); # Unknown
}

# Get the SNMP community
if (defined($opt_C)) {
    $community = $opt_C;
}

# Get the probe name to search for
if (defined($opt_P)) {
    $probename = $opt_P;
} else {
    print "A probe name must be specified!\n";
    exit(-1); # Unknown
}

# Grab the warning thresholds
if (defined($opt_w)) {
    my @warning = split(/\,/, $opt_w);

    if (!($warning[0] eq "")) {
        $warning_temp = $warning[0];
    }
    if (!($warning[1] eq "")) {
	$warning_humidity_max = $warning[1];
    }
    if (!($warning[2] eq "")) {
	$warning_humidity_min = $warning[2];

	# If we also have the maximum humidity value, make sure it is higher
	# than the minimum
	if (($warning_humidity_max != -1) &&
	    ($warning_humidity_max < $warning_humidity_min)) {
	    print "Warning humidity maximum is lower than the minimum!\n";
	    exit(-1); # Unknown
	}
    }
}

# Grab the critical thresholds
if (defined($opt_c)) {
    my @critical = split(/\,/, $opt_c);

    if (!($critical[0] eq "")) {
        $critical_temp = $critical[0];
    }	
    if (!($critical[1] eq "")) {
	$critical_humidity_max = $critical[1];
    }
    if (!($critical[2] eq "")) {
	$critical_humidity_min = $critical[2];

	# If we also have the maximum humidity value, make sure it is higher
	# than the minimum
	if (($critical_humidity_max != -1) &&
	    ($critical_humidity_max < $critical_humidity_min)) {
	    print "Critical humidity maximum is lower than the minimum!\n";
	    exit(-1); # Unknown
	}
    }
}

# If we have both temperature thresholds, make sure the critical is higher than
# the warning
if (($warning_temp != -1) &&
    ($critical_temp != -1) &&
    ($warning_temp > $critical_temp)) {
    print "Critical temperature is lower than warning temperature!\n";
    exit(-1); # Unknown
}

# If we have both the humidity maximum thresholds, make sure the critical is
# higher than the warning
if (($warning_humidity_max != -1) &&
    ($critical_humidity_max != -1) &&
    ($warning_humidity_max > $critical_humidity_max)) {
    print "Critical humidity maximum is lower than warning maximum!\n";
    exit(-1); # Unknown
}

# If we have both the humidity minimum thresholds, make sure the critical is
# lower than the warning
if (($warning_humidity_min != -1) &&
    ($critical_humidity_min != -1) &&
    ($warning_humidity_min < $critical_humidity_min)) {
    print "Critical humidity minimum is higher than warning maximum!\n";
    exit(-1); # Unknown
}

# Initialise the SNMP session via the Net::SNMP perl module
my ($snmp_session, $snmp_error) = Net::SNMP->session(
    -community => $community,
    -hostname => $hostname,
    -version => $version,
    -timeout => $timeout,
);

# Grab interesting details
check_device();
    
# Shut down the SNMP session
$snmp_session->close();

# Return the return string and return status
print "$returnstr\n";
exit($status);


##############################################################################
##############################################################################
#
# Change the status level
#
sub status {
    my $newstatus = $_[0];

    # If the new status is greater than the old status, change the old status
    if ($newstatus > $status) {
	$status = $newstatus;
    }
}


##############################################################################
##############################################################################
#
# Grab a value from snmp
#
sub grab_snmp_value {
    my $this_oid = $_[0];
    my $this_value = "";

    # Try to grab the OID's value, if it exists
    if (defined($snmp_session->get_request($this_oid))) {
        foreach ($snmp_session->var_bind_names()) {
            $this_value = $snmp_session->var_bind_list()->{$_};
        }
    }

    # Return the value we got..
    return $this_value;
}


##############################################################################
##############################################################################
#
# Find the base OID for the status table
#
sub find_probe_status_table {
    # Look at internal probes first, then external probes
    for my $emp_probe ($oid_emp_int_probe . $oid_probe_int_status,
                       $oid_emp_ext_probe . $oid_probe_ext_status) {
	# Build the full OID for to this probe's status table
	my $this_probe = $oid_emp . $emp_probe;
			   
	# Reset the index to 1..
	my $index = 1;
	my $name = "."; # Trick the while loop into running 1st time
	
	# Look for names until we cannot find any entries
	while ($name ne "") {
	    # Build an OID to this probe's name
	    my $this_oid = $this_probe . $oid_probe_status_name . "." . $index;
	    
	    # Try to find the name
	    $name = grab_snmp_value($this_oid);
	    
	    # If the name equals the one we want, return the table OID!
	    if ($name eq $probename) {
		$probe_table_oid = $this_probe;
		$probe_table_index = "." . $index;
		return;
	    }
	    
	    # If we got here, it wasn't the one we wanted - increase the index
	    $index++;
	}
    }
}


##############################################################################
##############################################################################
#
# Grab lots of interesting info from the probe
#
sub check_device {
    
    # Find the probe's location in the table, including its index..
    find_probe_status_table();

    # Grab some values
    $emp_temp =
	grab_snmp_value($probe_table_oid . 
			$oid_probe_status_temp . 
			$probe_table_index);
    $emp_temp_scale =
	grab_snmp_value($probe_table_oid .
			$oid_probe_status_scale . 
			$probe_table_index);
    $emp_humidity =
	grab_snmp_value($probe_table_oid .
			$oid_probe_status_humid . 
			$probe_table_index);
    $emp_connstat =
	grab_snmp_value($probe_table_oid .
			$oid_probe_status_conn . 
			$probe_table_index);

    # Start the return string
    $returnstr = "[$probename]";

    # Check the connection status - it must be OK!
    if ($emp_connstat != 2) {
	# Change the status..
	status(-1); # Unknown

	# Add a little notice stating what went wrong
	$returnstr =
	    "$returnstr " .
	    "The probe is not connected or was not found!";
    } else {
	# If we have the remote temperature, add it to the output
	if (!($emp_temp eq "")) {
	    # Have we broken a threshold?
	    if (($critical_temp != -1) &&
		($emp_temp >= $critical_temp)) {
		status(2); # Critical
		$returnstr = "$returnstr " . "Critical:";
	    } elsif (($warning_temp != -1) &&
		     ($emp_temp >= $warning_temp)) {
		status(1); # Warning
		$returnstr = "$returnstr " . "Warning:";
	    }

	    # Add the temperature
	    $returnstr = 
		"$returnstr " .
		"Temperature: $emp_temp "; # &deg;?

	    # Add the temperature scalar monogram
	    if ($emp_temp_scale eq 1) {
		$returnstr = "$returnstr" . "C";
	    } elsif ($emp_temp_scale eq 2) {
		$returnstr = "$returnstr" . "F";
	    } else {
		# Woops :)
		$returnstr = "$returnstr" . "?";
	    }   
	} else {
	    status(2); # Critical
	    $returnstr = "Temperature Unavailable";
	}

	if (($emp_humidity ne "") &&
	    ($emp_humidity >= 0) &&
	    ($emp_humidity <= 100)) {
	    # Have we broken a threshold?
	    if ((($critical_humidity_max != -1) &&
		 ($emp_humidity > $critical_humidity_max)) ||
		(($critical_humidity_min != -1) &&
		 ($emp_humidity < $critical_humidity_min))) {
		status(2); # Critical
		$returnstr = "$returnstr, Critical:";
	    } elsif ((($warning_humidity_max != -1) &&
		      ($emp_humidity > $warning_humidity_max)) ||
		     (($warning_humidity_min != -1) &&
		      ($emp_humidity < $warning_humidity_min))) {
		status(1); # Warning
		$returnstr = "$returnstr, Warning:";
	    } else {
		$returnstr = "$returnstr,";
	    }

	    $returnstr =
		"$returnstr " .
		"Humidity: $emp_humidity\%";
	}
    }

    # Add on some performance monitoring stuff to the return string :)
    $returnstr =
	"$returnstr" .
	"|$emp_temp;$emp_temp_scale;$emp_humidity;$probename;$emp_connstat" .
	";";
}


##############################################################################
##############################################################################
#
# Usage information
#
sub usage {
    print << "USAGE";
$script ($script_version)
Copyright (c) 2005 Simon Butcher <simon@butcher.name>

$script_title

Usage: $script -H <hostname> [-C <community>] -P <probe name>
            [-w <temp>,<hMax>,<hMin>] [-c <temp>,<hMax>,<hMin>]

Options:
    -H 	Hostname or IP address of the APC SNMP card
    -C 	SNMP read community (default is $community)
    -P  The name of the probe you wish to monitor
    -w  Warning level for temperature max and humidity max/min (if available)
    -c  Critical level for temperature max and humidity max/min (if available)
        For example: 30 degrees, between 15% and 75% humidity = "30,75,15"

  Note that some probes do not monitor humidity. If so, it will be ignored.
  Also be aware that the temperature threshold is dependant on the scale
  configured on the environmental monitoring probe!

USAGE
     exit(-1); # Unknown
}
