#!/usr/bin/perl

#     Copyright (C) 2017 Anthony Walters <anthony.walters@gmx.com>
#
#     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 3 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, see <http://www.gnu.org/licenses/>.

# Author:  Anthony Walters <anthony.walters@gmx.com>
# Website: https://github.com/anthonywalters/check-upsmib-plugin

use strict;
use warnings;

use Net::SNMP;
use Getopt::Long;
Getopt::Long::Configure('no_ignore_case');

# Plugin Info
my $plugin_version_number       = '0.1';
my $plugin_copyright_string     = 'Copyright (c) 2017 Anthony Walters <anthony.walters@gmx.com>';

# Command line option variables
my $opt_hostname;
my $opt_snmp_community;
my $opt_check_type;
my $opt_snmp_version = 1;
my $opt_snmp_port = 161;
my $opt_version_flag;
my $opt_help_flag;
my $opt_warning_threshold;
my $opt_critical_threshold;

###### Constants ##############################################################

# Constants for the plugin return status
use constant {
    OK          => 0,
    WARNING     => 1,
    CRITICAL    => 2,
    UNKNOWN     => 3,
};

# Human Friendly Constants for $oid_upsOutputSource
use constant {
    OUTPUTSOURCE_OTHER      => "1",
    OUTPUTSOURCE_NONE       => "2",
    OUTPUTSOURCE_NORMAL     => "3",
    OUTPUTSOURCE_BYPASS     => "4",
    OUTPUTSOURCE_BATTERY    => "5",
    OUTPUTSOURCE_BOOSTER    => "6",
    OUTPUTSOURCE_REDUCER    => "7"
};

# Human Friendly Constants for $oid_upsBatteryStatus
use constant {
    BATTERY_UNKNOWN     => "1",
    BATTERY_NORMAL      => "2",
    BATTERY_LOW         => "3",
    BATTERY_DEPLETED    => "4"
};


###### Plugin specific oids ###################################################
# http://www.oidview.com/mibs/0/UPS-MIB.html
# http://www.circitor.fr/Mibs/Html/UPS-MIB.php

my $oid_upsIdentName                    = "1.3.6.1.2.1.33.1.1.5.0";
my $oid_upsAlarmsPresent                = "1.3.6.1.2.1.33.1.6.1.0";
my $oid_upsBatteryStatus                = "1.3.6.1.2.1.33.1.2.1.0";
my $oid_upsBatteryTemperature           = "1.3.6.1.2.1.33.1.2.7.0";
my $oid_upsEstimatedChargeRemaining     = "1.3.6.1.2.1.33.1.2.4.0";
my $oid_upsEstimatedMinutesRemaining    = "1.3.6.1.2.1.33.1.2.3.0";
my $oid_upsSecondsOnBattery             = "1.3.6.1.2.1.33.1.2.2.0";
my $oid_upsOutputSource                 = "1.3.6.1.2.1.33.1.4.1.0";

###### Variables to hold the return values of the SNMP queries ################
my $upsIdentName;
my $upsAlarmsPresent;
my $upsBatteryStatus;
my $upsBatteryTemperature;
my $upsEstimatedChargeRemaining;
my $upsEstimatedMinutesRemaining;
my $upsSecondsOnBattery;
my $upsOutputSource;

###### Variables used by the plugin code ######################################
my $plugin_return_status;
my $plugin_return_string;
my $plugin_perf_data_string;
my $plugin_warning_threshold;
my $plugin_critical_threshold;

###############################################################################
###### Program Start ##########################################################

# Get the command line arguments
get_arguments();

# If help inforation was asked for, print usage and exit
if ($opt_help_flag) {
    print_usage();
    exit 0;
}

# If version inforation was asked for, print version info and exit
if ($opt_version_flag) {
    print_version_info();
    exit 0;
}

# Check the command line arguments: hostname, community and check type must be defined.
if (!defined $opt_hostname || !defined $opt_snmp_community || !defined $opt_check_type) {
    print_usage();
    exit UNKNOWN;
}

# Set up the snmp session
my ($snmp_session, $snmp_error) = Net::SNMP->session(
    -hostname   => $opt_hostname,
    -community  => $opt_snmp_community,
    -version    => $opt_snmp_version,
    -port       => $opt_snmp_port,
);
# If the snmp session did not get set up, exit with a plugin status of UNKNOWN
if (!defined($snmp_session)) {
    printf "ERROR: %s.\n", $snmp_error;
    exit UNKNOWN;
}

# Do the type of check that was requested via the command line options
if (lc $opt_check_type eq 'alarmspresent') {
    do_check_on_AlarmsPresent();
} elsif (lc $opt_check_type eq 'batterystatus') {
    do_check_on_BatteryStatus();
} elsif (lc $opt_check_type eq 'batterytemperature') {
    do_check_on_BatteryTemperature();
} elsif (lc $opt_check_type eq 'estimatedchargeremaining') {
    do_check_on_EstimatedChargeRemaining();
} elsif (lc $opt_check_type eq 'estimatedminutesremaining') {
    do_check_on_EstimatedMinutesRemaining();
} elsif (lc $opt_check_type eq 'outputsource') {
    do_check_on_OutputSource();
} else {
    print "ERROR: Invalid CHECK_TYPE in command line arguments\n";
}

# Close the snmp session
$snmp_session->close();

# Now return a return status and string for use by nagios/icinga
if (defined $plugin_return_status) {
    # Add a newline to the end of the output string
    $plugin_return_string .= "\n";
    # Print the return string, and exit with a status code
    print $plugin_return_string;
    exit $plugin_return_status;
} else {
    print "$0 : Failed to geterate a status value, exiting with UNKNOWN\n";
    exit UNKNOWN;
}

###### Program End ############################################################
###############################################################################

###### Subroutines ############################################################

sub do_check_on_AlarmsPresent {
    # Check for the warning option, if not set, give a default value
    if (defined $opt_warning_threshold) {
        $plugin_warning_threshold = $opt_warning_threshold;
    } else {
        $plugin_warning_threshold = 1;
    }

    # Check for the critical option, if not set, give a default value
    if (defined $opt_critical_threshold) {
        $plugin_critical_threshold = $opt_critical_threshold;
    } else {
        $plugin_critical_threshold = 1;
    }

    # Query the ups for each oid that we are interested in
    $upsIdentName = get_snmp_value($oid_upsIdentName);
    $upsAlarmsPresent = get_snmp_value($oid_upsAlarmsPresent);
    
    # If the ups returned a value
    if (defined $upsAlarmsPresent) {
        # Check to see if the alarms present is a non-negative number, zero or greater
        if ($upsAlarmsPresent >= 0) {
            # Decide what return value this plugin will return with.
            if ($upsAlarmsPresent >= $plugin_critical_threshold ) {
                $plugin_return_status = CRITICAL;
                $plugin_return_string = "Status is CRITICAL: ";
            } elsif ($upsAlarmsPresent >= $plugin_warning_threshold ) {
                $plugin_return_status = WARNING;
                $plugin_return_string = "Status is WARNING: ";
            } else {
                $plugin_return_status = OK;
                $plugin_return_string = "Status is OK: ";
            }
        # The value is not a positive integer, set return status to unknown
        } else {
            $plugin_return_status = UNKNOWN;
            $plugin_return_string = "NOT A POSITIVE INTEGER: ";
        }
        $plugin_return_string .= $upsIdentName . " - " if (defined $upsIdentName);
        $plugin_return_string .= "Number of UPS alarms is " . $upsAlarmsPresent;
    # The ups failed to return a value, set the return status to unknown
    } else {
        $plugin_return_status = UNKNOWN;
        $plugin_return_string = "Status is UNKNOWN: Could not contact snmp server";
    }

    # Generate the perf data and add it to the return string
    if (defined $upsAlarmsPresent) {
        $plugin_perf_data_string = "upsAlarmsPresent=$upsAlarmsPresent";
        $plugin_return_string .= " | " . $plugin_perf_data_string;
    }
}

sub do_check_on_BatteryStatus {
    # Check for the warning option, if not set, give a default value
    if (defined $opt_warning_threshold) {
        $plugin_warning_threshold = $opt_warning_threshold;
    } else {
        $plugin_warning_threshold = BATTERY_LOW;
    }

    # Check for the critical option, if not set, give a default value
    if (defined $opt_critical_threshold) {
        $plugin_critical_threshold = $opt_critical_threshold;
    } else {
        $plugin_critical_threshold = BATTERY_DEPLETED;
    }

    # Query the ups for each oid that we are interested in
    $upsIdentName = get_snmp_value($oid_upsIdentName);
    $upsBatteryStatus = get_snmp_value($oid_upsBatteryStatus);

    # If the ups returned a value
    if (defined $upsBatteryStatus) {
        # Decide what return value this plugin will return with.
        if ($upsBatteryStatus  >= $plugin_critical_threshold ) {
            $plugin_return_status = CRITICAL;
            $plugin_return_string = "Status is CRITICAL: ";
        } elsif ($upsBatteryStatus  >= $plugin_warning_threshold ) {
            $plugin_return_status = WARNING;
            $plugin_return_string = "Status is WARNING: ";
        } elsif ($upsBatteryStatus == BATTERY_NORMAL ) {
            $plugin_return_status = OK;
            $plugin_return_string = "Status is OK: ";
        } elsif ($upsBatteryStatus == BATTERY_UNKNOWN ) {
            $plugin_return_status = UNKNOWN;
            $plugin_return_string = "Status is UNKNOWN: ";
        } else {
            $plugin_return_status = UNKNOWN; # unknown when the battery status is not valid.
            $plugin_return_string = "Status is NOT_VALID: ";
        }

        # Make the output more readable, translate the upsBatteryStatus code into english 
        if ($upsBatteryStatus == BATTERY_UNKNOWN ) {
            $plugin_return_string .= 'Battery UNKNOWN - ';
        } elsif ($upsBatteryStatus == BATTERY_NORMAL ) {
            $plugin_return_string .= 'Battery NORMAL - ';
        } elsif ($upsBatteryStatus == BATTERY_LOW ) {
            $plugin_return_string .= 'Battery LOW - ';
        } elsif ($upsBatteryStatus == BATTERY_DEPLETED ) {
            $plugin_return_string .= 'Battery DEPLETED - ';
        } else { # Catchall for when the status is invalid, just print the value
            $plugin_return_string .= 'Battery Status ' . $upsBatteryStatus . ' - ';
        }

        # Add the name and raw value returned by the ups to the output string
        $plugin_return_string .= $upsIdentName . " - " if (defined $upsIdentName);
        $plugin_return_string .= "UPS returned a battery status of " . $upsBatteryStatus;

    # The ups failed to return a value
    } else {
        $plugin_return_status = UNKNOWN; #unknown if the snmp server did not answer
        $plugin_return_string = "Status is UNKNOWN: Could not contact snmp server";
    }

    # Generate the perf data and add it to the return string
    if (defined $upsBatteryStatus) {
        $plugin_perf_data_string = "upsBatteryStatus=$upsBatteryStatus";
        $plugin_return_string .= " | " . $plugin_perf_data_string;
    }
}

sub do_check_on_BatteryTemperature {
    # Check for the warning option, if not set, give a default value
    if (defined $opt_warning_threshold) {
        $plugin_warning_threshold = $opt_warning_threshold;
    } else {
        $plugin_warning_threshold = 26;
    }

    # Check for the critical option, if not set, give a default value
    if (defined $opt_critical_threshold) {
        $plugin_critical_threshold = $opt_critical_threshold;
    } else {
        $plugin_critical_threshold = 30;
    }

    # Query the ups for each oid that we are interested in
    $upsIdentName = get_snmp_value($oid_upsIdentName);
    $upsBatteryTemperature = get_snmp_value($oid_upsBatteryTemperature);

    # If the ups returned a value
    if (defined $upsBatteryTemperature) {
        # Check to see if the estimated minutes remaining is zero or greater
        if ($upsBatteryTemperature >= 0) {
            # Decide what return value this plugin will return with.
            if ($upsBatteryTemperature >= $plugin_critical_threshold ) {
                $plugin_return_status = CRITICAL;
                $plugin_return_string = "Status is CRITICAL: ";
            } elsif ($upsBatteryTemperature >= $plugin_warning_threshold ) {
                $plugin_return_status = WARNING;
                $plugin_return_string = "Status is WARNING: ";
            } elsif ($upsBatteryTemperature <= 15) {
                $plugin_return_status = WARNING;
                $plugin_return_string = "Status is WARNING: ";
            } else {
                $plugin_return_status = OK;
                $plugin_return_string = "Status is OK: ";
            }
        # the value is not a positive integer, set the return status to unknown.
        } else {
            $plugin_return_status = UNKNOWN;
            $plugin_return_string = "Temperature returned from the UPS is NOT A POSITIVE INTEGER: ";
        }
        $plugin_return_string .= $upsIdentName . " - " if (defined $upsIdentName);
        $plugin_return_string .= "UPS battery temperature is " . $upsBatteryTemperature . " degrees";
    # The ups failed to return a value, set the return status to unknwon
    } else {
        $plugin_return_status = UNKNOWN;
        $plugin_return_string = "Status is UNKNOWN: Could not contact snmp server";
    }

    # Generate the perf data and add it to the return string
    if (defined $upsBatteryTemperature) {
        $plugin_perf_data_string = "upsBatteryTemperature=$upsBatteryTemperature;$plugin_warning_threshold;$plugin_critical_threshold;;";
        $plugin_return_string .= " | " . $plugin_perf_data_string;
    }
}

sub do_check_on_EstimatedChargeRemaining {
    # Check for the warning option, if not set, give a default value
    if (defined $opt_warning_threshold) {
        $plugin_warning_threshold = $opt_warning_threshold;
    } else {
        $plugin_warning_threshold = 50;
    }

    # Check for the critical option, if not set, give a default value
    if (defined $opt_critical_threshold) {
        $plugin_critical_threshold = $opt_critical_threshold;
    } else {
        $plugin_critical_threshold = 30;
    }

    # Query the ups for each oid that we are interested in
    $upsIdentName = get_snmp_value($oid_upsIdentName);
    $upsEstimatedChargeRemaining = get_snmp_value($oid_upsEstimatedChargeRemaining);
    $upsSecondsOnBattery = get_snmp_value($oid_upsSecondsOnBattery);

    # If the ups returned a value
    if (defined $upsEstimatedChargeRemaining) {
        # Check to see if the percentage value is between 0 and 100
        if ($upsEstimatedChargeRemaining >= 0 && $upsEstimatedChargeRemaining <= 100) {
            # Decide what return value this plugin will return with.
            if ($upsEstimatedChargeRemaining <= $plugin_critical_threshold ) {
                $plugin_return_status = CRITICAL;
                $plugin_return_string = "Status is CRITICAL: ";
            } elsif ($upsEstimatedChargeRemaining <= $plugin_warning_threshold ) {
                $plugin_return_status = WARNING;
                $plugin_return_string = "Status is WARNING: ";
            } else {
                $plugin_return_status = OK;
                $plugin_return_string = "Status is OK: ";
            }
        } else {
            # the % value is not between 0 and 100, set the return status to unknown.
            $plugin_return_status = UNKNOWN; 
            $plugin_return_string = "Status is NOT A VALID PERCENTAGE: ";
        }
        $plugin_return_string .= $upsIdentName . " - " if (defined $upsIdentName);
        $plugin_return_string .= "UPS returned an estimated charge remaining of " . $upsEstimatedChargeRemaining . "%";
    } else {
        # The ups failed to return a value
        $plugin_return_status = UNKNOWN; #return unknown if the snmp server did not answer
        $plugin_return_string = "Status is UNKNOWN: Could not contact snmp server";
    }

    # Generate the perf data and add it to the return string
    if (defined $upsEstimatedChargeRemaining) {
        $plugin_perf_data_string = "upsEstimatedChargeRemaining=$upsEstimatedChargeRemaining%";
        $plugin_perf_data_string .= " upsSecondsOnBattery=${upsSecondsOnBattery}s";
        $plugin_return_string .= " | " . $plugin_perf_data_string;
    }
}

sub do_check_on_EstimatedMinutesRemaining {
    # Check for the warning option, if not set, give a default value
    if (defined $opt_warning_threshold) {
        $plugin_warning_threshold = $opt_warning_threshold;
    } else {
        $plugin_warning_threshold = 25;
    }

    # Check for the critical option, if not set, give a default value
    if (defined $opt_critical_threshold) {
        $plugin_critical_threshold = $opt_critical_threshold;
    } else {
        $plugin_critical_threshold = 15;
    }

    # Query the ups for each oid that we are interested in
    $upsIdentName = get_snmp_value($oid_upsIdentName);
    $upsEstimatedMinutesRemaining = get_snmp_value($oid_upsEstimatedMinutesRemaining);
    $upsSecondsOnBattery = get_snmp_value($oid_upsSecondsOnBattery);
        
    # If the ups returned a value
    if (defined $upsEstimatedMinutesRemaining) {
        # Check to see if the estimated minutes remaining is zero or greater
        if ($upsEstimatedMinutesRemaining >= 0) {
            # Decide what return value this plugin will return with.
            if ($upsEstimatedMinutesRemaining <= $plugin_critical_threshold ) {
                $plugin_return_status = CRITICAL;
                $plugin_return_string = "Status is CRITICAL: ";
            } elsif ($upsEstimatedMinutesRemaining <= $plugin_warning_threshold ) {
                $plugin_return_status = WARNING;
                $plugin_return_string = "Status is WARNING: ";
            } else {
                $plugin_return_status = OK;
                $plugin_return_string = "Status is OK: ";
            }
        # The value is not a positive integer, set the return status to unknown.
        } else {
            $plugin_return_status = UNKNOWN;
            $plugin_return_string = "Status is NOT A POSITIVE INTEGER: ";
        }
        $plugin_return_string .= $upsIdentName . " - " if (defined $upsIdentName);
        $plugin_return_string .= "UPS returned estimated time remaining to depletion of " . $upsEstimatedMinutesRemaining . " minutes";
    } else {
        # The ups failed to return a value, set the return status to unknown
        $plugin_return_status = UNKNOWN;
        $plugin_return_string = "Status is UNKNOWN: Could not contact snmp server";
    }

    # Generate the perf data and add it to the return string
    if (defined $upsEstimatedMinutesRemaining) {
        $plugin_perf_data_string = "upsEstimatedMinutesRemaining=$upsEstimatedMinutesRemaining;$plugin_warning_threshold;$plugin_critical_threshold;;";
        $plugin_perf_data_string .= " upsSecondsOnBattery=${upsSecondsOnBattery}s";
        $plugin_return_string .= " | " . $plugin_perf_data_string;
    }
}

sub do_check_on_OutputSource {
    # Note: warning and critical thresholds do not make sense for these checks
    #       and so they are ignored even if they are defined.

    # Query the ups for each oid that we are interested in
    $upsIdentName = get_snmp_value($oid_upsIdentName);
    $upsOutputSource = get_snmp_value($oid_upsOutputSource);

    # If the ups returned a value
    if (defined $upsOutputSource) {
        # Decide what return value and string this plugin will return with.
        if ($upsOutputSource == OUTPUTSOURCE_OTHER ) {
            $plugin_return_status = WARNING;
            $plugin_return_string = "Status is WARNING: UPS Output OTHER - ";
        } elsif ($upsOutputSource == OUTPUTSOURCE_NONE ) { 
            $plugin_return_status = CRITICAL;
            $plugin_return_string = "Status is CRITICAL: UPS Output NONE - ";
        } elsif ($upsOutputSource == OUTPUTSOURCE_NORMAL ) {
            $plugin_return_status = OK;
            $plugin_return_string = "Status is OK: UPS Output NORMAL - ";
        } elsif ($upsOutputSource == OUTPUTSOURCE_BYPASS ) {
            $plugin_return_status = WARNING;
            $plugin_return_string = "Status is WARNING: UPS Output BYPASS - ";
        } elsif ($upsOutputSource == OUTPUTSOURCE_BATTERY ) {
            $plugin_return_status = WARNING;
            $plugin_return_string = "Status is WARNING: UPS Output BATTERY - ";
        } elsif ($upsOutputSource == OUTPUTSOURCE_BOOSTER ) {
            $plugin_return_status = WARNING;
            $plugin_return_string = "Status is WARNING: UPS Output BOOSTER - ";
        } elsif ($upsOutputSource == OUTPUTSOURCE_REDUCER ) {
            $plugin_return_status = WARNING;
            $plugin_return_string = "Status is WARNING: UPS Output REDUCER - ";
        } else {
            $plugin_return_status = UNKNOWN; #return unknown when the status is not valid.
            $plugin_return_string = "Status is INVALID FROM THE UPS: ";
        }
        $plugin_return_string .= $upsIdentName . " - " if (defined $upsIdentName);
        $plugin_return_string .= "UPS returned an output source of " . $upsOutputSource;
    } else {
        # The ups failed to return a value, set the return status to unknown
        $plugin_return_status = UNKNOWN; 
        $plugin_return_string = "Status is UNKNOWN: Could not contact snmp server";
    }

    # Generate the perf data and add it to the return string
    if ($upsOutputSource) {
        $plugin_perf_data_string = "upsOutputSource=$upsOutputSource";
        $plugin_return_string .= " | " . $plugin_perf_data_string;
    }
}

# Accepts a scalar $oid
# Returns a scalar with the value of the snmp query
# Returns undef when the snmp query fails
sub get_snmp_value {
    my $oid = shift;

    my $hashref_result = $snmp_session->get_request(
        -varbindlist    => [ $oid ],
    );

    #Return undef on error, or the result of the snmp query if successful.
    if (!defined $hashref_result) {
        printf STDERR "ERROR: ", $snmp_session->error();
        return undef;
    } else {
        return $hashref_result->{$oid};
    }
}

sub print_usage {
    print "SUMMARY\n";
    print "  This plugin queries upsMIB compatible UPS over snmp v1 or v2 for monitoring purposes\n";
    print "USAGE\n";
    print "  $0 -T CHECK_TYPE -H HOST -C COMMUNITY [OPTIONS]\n";
    print "OPTIONS\n";
    print "  -T CHECK_TYPE, --checktype=CHECK_TYPE\n";
    print "  -H HOST, --hostname=HOST\n";
    print "  -C COMMUNITY, --community=COMMUNITY\n";
    print "  -p PORT, --port=PORT\n";
    print "  -s SNMP_VERSION, --snmpversion=SNMP_VERSION\n";
    print "  -w THRESHOLD, --warning=THRESHOLD\n";
    print "  -c THRESHOLD, --critical=THRESHOLD\n";
    print "  -h, --help\n";
    print "  -V, --version\n";
    print "CHECK_TYPE Check type can be one of the following:\n";
    print "  AlarmsPresent\n";
    print "  BatteryStatus\n";
    print "  BatteryTemperature\n";
    print "  EstimatedChargeRemaining\n";
    print "  EstimatedMinutesRemaining\n";
    print "  OutputSource\n";
}

sub print_version_info {
    print 'check_upsmib plugin Version ' . $plugin_version_number . "\n";
    print $plugin_copyright_string . "\n";
}

sub get_arguments {
    #get options docs: https://www.monitoring-plugins.org/doc/guidelines.html#PLUGOPTIONS
    my $status = GetOptions(
        "checktype|T=s"     => \$opt_check_type,
        "hostname|H=s"      => \$opt_hostname,
        "community|C=s"     => \$opt_snmp_community,
        "version|V"         => \$opt_version_flag,
        "port|p=i"          => \$opt_snmp_port,
        "snmpversion|s=i"   => \$opt_snmp_version,
        "warning|w=i"       => \$opt_warning_threshold,
        "critical|c=i"      => \$opt_critical_threshold,
        "help|h"            => \$opt_help_flag,
    )
        or die("Error in command line arguments\n");
}
