#! /usr/bin/perl -w
#
# check_comet - nagios plugin
#
# Copyright (C) 2015 Guenther Mair,
# Derived from check_ifoperstatus by Christoph Kron.
#
# 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.
#
#
# Report bugs to:  guenther.mair@hoslo.ch
#
# $Id: ab77f9f1d69ea17716b275455a4e64b25fadfb01 $
#

my $libexec;

BEGIN {
  my @lib_folders = qw(/usr/local/nagios/libexec/ /usr/lib/nagios/plugins/ /usr/lib64/nagios/plugins/ /usr/share/nagios/libexec/);
  foreach my $lib (@lib_folders) {
    if (-d $lib) {
      $libexec = $lib;
      last;
    }
  }
}

use POSIX;
use strict;

use lib $libexec;
use utils qw($TIMEOUT %ERRORS &print_revision &support);

use Net::SNMP;
use Getopt::Long;
&Getopt::Long::config('bundling');

## function prototypes

sub in_array($@);
sub print_help();
sub usage();
sub process_arguments();

## module-specific variables

my $PROGNAME = "check_comet";
my $REVISION = '$Id: ab77f9f1d69ea17716b275455a4e64b25fadfb01 $';
my $debug;
my $warning  = 0.0;
my $critical = 0.0;
my $state    = 'OK';
my $answer   = "";
my $result;
my $sysinfo;

## variables for argument handling

my $opt_h;
my $opt_V;
my $status;

## snmp specific variables

my $timeout;
my $hostname;
my $session;
my $error;
my $response;
my $key;
my $value;
my $community   = "public";
my $snmpversion = 1;
my $maxmsgsize  = 1472;       # Net::SNMP default is 1472
my ($seclevel, $authproto, $secname, $authpass, $privpass, $auth_method, $auth_token, $priv_method, $priv_token);
my $context = "";
my $port    = 161;
my @snmpoids;

## comet sensor values

my $values     = '1.3.6.1.4.1.22626.1.2.1';
my $temp       = '1.3.6.1.4.1.22626.1.2.1.1.0';
my $unit       = '1.3.6.1.4.1.22626.1.2.1.9.0';
my $temp_value = 0.0;
my $unit_value = "";

## unit specific globals

my $globals      = '1.3.6.1.4.1.22626.1.2.2';
my $sensorName   = '1.3.6.1.4.1.22626.1.2.2.1';
my $serialNumber = '1.3.6.1.4.1.22626.1.2.2.2';
my $deviceType   = '1.3.6.1.4.1.22626.1.2.2.3';

## validate arguments

process_arguments();

## just in case of problems, let's avoid blocking the calling process for too long

$SIG{'ALRM'} = sub {
  print("ERROR: No snmp response from $hostname (alarm)\n");
  exit $ERRORS{"UNKNOWN"};
};

alarm($timeout);

## main function
print "Comet Sensor Check: " if (!defined $debug);

## retrieve system information
$sysinfo = system_information();

## retrieve temperature information
print "Retrieving device values:\n" if (defined $debug);
if (!defined($response = $session->get_table($values))) {
  printf(" - %-35s - OID-tree %s not found\n", "values", $values) if (defined $debug);
} else {
  while (($key, $value) = each %{$response}) {
    printf(" * %-35s = %s\n", $key, $value) if (defined $debug);

    ## values need to be converted (temperature from string to float, unit from hex-string to string
    if ($key =~ /^${temp}/) { $temp_value = trim($value) + 0.0; }
    elsif ($key =~ /^${unit}/) { $unit_value = chr(hex(substr(trim($value), -2))); }
  }
}

## set status information
if ($temp_value > $critical) {
  $state = 'CRITICAL';
} elsif ($temp_value > $warning) {
  $state = 'WARNING';
}

## terminate with exit code
printf("%s temperature %s at %.1f °%s|temp=%.1f\n", $sysinfo, $state, $temp_value, $unit_value, $temp_value);
exit $ERRORS{$state};

## subroutines

sub ltrim { my $s = shift; $s =~ s/^\s+//;       return $s }
sub rtrim { my $s = shift; $s =~ s/\s+$//;       return $s }
sub trim  { my $s = shift; $s =~ s/^\s+|\s+$//g; return $s }

sub in_array($@) {
  my $needle = shift(@_);
  my %items  = map { $_ => 1 } @_;
  return (exists($items{$needle})) ? 1 : 0;
}

sub system_information {
  my $si;
  my $key;
  my $value;

  my $name = "";    # sensorName
  my $sn   = "";    # serialNumber
  my $type = "";    # deviceType

  print "Retrieving global values:\n" if (defined $debug);
  if (!defined($response = $session->get_table($globals))) {
    printf(" - %-35s - OID-tree %s not found\n", "globals", $globals) if (defined $debug);
    return "";
  } else {
    while (($key, $value) = each %{$response}) {
      printf(" * %-35s = %s\n", $key, $value) if (defined $debug);
      if    ($key =~ /^${sensorName}/)   { $name = trim($value); }
      elsif ($key =~ /^${serialNumber}/) { $sn   = trim($value); }
      elsif ($key =~ /^${deviceType}/)   { $type = trim($value); }
    }

    $si = ($type eq "") ? 'unknown device' : 'deviceType ' . $type;
    if ($name ne "") { $si .= ' ' . $name; }
    if ($sn ne "")   { $si .= ' S/N ' . $sn; }

    print "\n" . $si . "\n\n" if (defined $debug);

    return $si;
  }
}

sub usage() {
  printf("\nMissing arguments!\n\n");
  printf("Usage:\n\n");
  printf("%s -H <HOSTNAME> [-C <community>] [-d] -w WARNING -c CRITICAL\n", $PROGNAME);
  printf("Copyright (C) 2015 Guenther Mair\n\n");
  printf("See '%s --help' for details.\n", $PROGNAME);
  printf("\n\n");
  exit $ERRORS{"UNKNOWN"};
}

sub print_help() {
  printf("%s plugin\n", $PROGNAME);
  printf("\n+---------+---------+---------+---------+---------+---------+---------+---------\n\n");
  printf("Module specific parameters\n\n");
  printf("  -d (--debug)        debug / verbose mode (print checked details)\n");
  printf("  -w (--warning)      emit warning above this temperature\n");
  printf("  -c (--critical)     emit critical above this temperature\n");
  printf("\n+---------+---------+---------+---------+---------+---------+---------+---------\n\n");
  printf("SNMP parameters\n\n");
  printf("  -H (--hostname)     hostname to query (required)\n");
  printf("  -C (--community)    SNMP read community (defaults to public, used with SNMP\n");
  printf("                      v1 and v2c)\n");
  printf("  -v (--snmpversion)  1 for SNMP v1 (default)\n");
  printf("                      2 for SNMP v2c\n");
  printf("                      SNMP v2c will use get_bulk for less overhead\n");
  printf("  -L (--seclevel)     choice of 'noAuthNoPriv', 'authNoPriv', or 'authPriv'\n");
  printf("  -U (--secname)      username for SNMPv3 context\n");
  printf("  -a (--authproto)    authentication protocol (MD5 or SHA1)\n");
  printf("  -A (--authpass)     authentication password (cleartext ASCII or localized key\n");
  printf("                      in hex with 0x prefix generated by using 'snmpkey'\n");
  printf("                      utility auth password and authEngineID)\n");
  printf("  -X (--privpass)     privacy password (cleartext ASCII or localized key\n");
  printf("                      in hex with 0x prefix generated by using 'snmpkey'\n");
  printf("                      utility privacy password and authEngineID)\n");
  printf("  -p (--port)         SNMP port (default 161)\n");
  printf("  -M (--maxmsgsize)   max message size - usefull only for v1 or v2c\n");
  printf("  -t (--timeout)      seconds before the plugin times out (default=%s)\n", $TIMEOUT);
  printf("  -V (--version)      plugin version\n");
  printf("  -h (--help)         show this usage screen\n\n");
  print_revision($PROGNAME, '$Id: ab77f9f1d69ea17716b275455a4e64b25fadfb01 $');
}

sub process_arguments() {
  $status = GetOptions(
    "V"             => \$opt_V,
    "version"       => \$opt_V,
    "h"             => \$opt_h,
    "help"          => \$opt_h,
    "d"             => \$debug,
    "debug"         => \$debug,
    "w=f"           => \$warning,
    "warning=f"     => \$warning,
    "c=f"           => \$critical,
    "critical=f"    => \$critical,
    "v=i"           => \$snmpversion,
    "snmpversion=i" => \$snmpversion,
    "C=s"           => \$community,
    "community=s"   => \$community,
    "L=s"           => \$seclevel,
    "seclevel=s"    => \$seclevel,
    "a=s"           => \$authproto,
    "authproto=s"   => \$authproto,
    "U=s"           => \$secname,
    "secname=s"     => \$secname,
    "A=s"           => \$authpass,
    "authpass=s"    => \$authpass,
    "X=s"           => \$privpass,
    "privpass=s"    => \$privpass,
    "p=i"           => \$port,
    "port=i"        => \$port,
    "H=s"           => \$hostname,
    "hostname=s"    => \$hostname,
    "M=i"           => \$maxmsgsize,
    "maxmsgsize=i"  => \$maxmsgsize,
    "t=i"           => \$timeout,
    "timeout=i"     => \$timeout,
  );

  if ($status == 0) {
    print_help();
    exit $ERRORS{'OK'};
  }

  if ($opt_V) {
    print_revision($PROGNAME, '$Id: ab77f9f1d69ea17716b275455a4e64b25fadfb01 $');
    exit $ERRORS{'OK'};
  }

  if ($warning == 0.0 || $critical == 0.0) {
    print_help();
    print "WARNING: " . $warning . "\n";
    print "CRITICAL: " . $critical . "\n";
    exit $ERRORS{'OK'};
  }

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

  if (!utils::is_hostname($hostname)) {
    usage();
    exit $ERRORS{"UNKNOWN"};
  }

  $timeout = $TIMEOUT unless (defined $timeout);

  if ($snmpversion =~ /3/) {

    # Must define a security level even though default is noAuthNoPriv
    # v3 requires a security username
    if (defined $seclevel && defined $secname) {

      # Must define a security level even though defualt is noAuthNoPriv
      unless (grep /^$seclevel$/, qw(noAuthNoPriv authNoPriv authPriv)) {
        usage();
        exit $ERRORS{"UNKNOWN"};
      }

      # Authentication wanted
      if ($seclevel eq 'authNoPriv' || $seclevel eq 'authPriv') {
        unless ($authproto eq 'MD5' || $authproto eq 'SHA1') {
          usage();
          exit $ERRORS{"UNKNOWN"};
        }

        if (!defined $authpass) {
          usage();
          exit $ERRORS{"UNKNOWN"};
        } else {
          if ($authpass =~ /^0x/) {
            $auth_method = "-authkey";
            $auth_token  = $authpass;
          } else {
            $auth_method = "-authpassword";
            $auth_token  = $authpass;
          }
        }
      }

      # Privacy (DES encryption) wanted
      if ($seclevel eq 'authPriv') {
        if (!defined $privpass) {
          usage();
          exit $ERRORS{"UNKNOWN"};
        } else {
          if ($authpass =~ /^0x/) {
            $priv_method = "-privkey";
            $priv_token  = $privpass;
          } else {
            $priv_method = "-privpassword";
            $priv_token  = $privpass;
          }
        }
      }
    } else {
      usage();
      exit $ERRORS{'UNKNOWN'};
    }
  }    # end snmpv3

  # start snmpv1 / snmpv2
  if ($snmpversion =~ /[12]/) {
    ($session, $error) = Net::SNMP->session(
      -hostname   => $hostname,
      -community  => $community,
      -port       => $port,
      -version    => $snmpversion,
      -maxmsgsize => $maxmsgsize
    );

    if (!defined($session)) {
      $state  = 'UNKNOWN';
      $answer = $error;
      printf("%s: %s\n", $state, $answer);
      exit $ERRORS{$state};
    }
  } elsif ($snmpversion =~ /3/) {
    if ($seclevel eq 'noAuthNoPriv') {
      ($session, $error) = Net::SNMP->session(
        -hostname => $hostname,
        -port     => $port,
        -domain   => $domain,
        -version  => $snmpversion,
        -username => $secname
      );
    } elsif ($seclevel eq 'authNoPriv') {
      ($session, $error) = Net::SNMP->session(
        -hostname     => $hostname,
        -port         => $port,
        -domain       => $domain,
        -version      => $snmpversion,
        -username     => $secname,
        $auth_method  => $auth_token,
        -authprotocol => $authproto
      );
    } elsif ($seclevel eq 'authPriv') {
      ($session, $error) = Net::SNMP->session(
        -hostname     => $hostname,
        -port         => $port,
        -domain       => $domain,
        -version      => $snmpversion,
        -username     => $secname,
        $auth_method  => $auth_token,
        -authprotocol => $authproto,
        $priv_method  => $priv_token,
      );
    }
  } else {
    $state = 'UNKNOWN';
    printf("%s: No support for SNMP v%s yet\n", $state, $snmpversion);
    exit $ERRORS{$state};
  }
}
