#! /usr/bin/perl -w
#
# check_hr_storage - nagios plugin 
#
# Copyright (C) 2008 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: check_hr_storage 22 2009-08-22 12:14:25Z gunny $
#

use POSIX;
use strict;
use Math::BigFloat;
use lib "/usr/local/nagios/libexec"  ;
use utils qw($TIMEOUT %ERRORS &print_revision &support);

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

my $PROGNAME = 'check_hr_storage';
my $REVISION = '$Rev: 22 $';
sub list ();
sub print_help ();
sub usage ();
sub process_arguments ();

my $DEBUG = 0;

my $timeout;
my $hostname;
my $session;
my $error;
my $response;
my $sub_response;
my $opt_h;
my $opt_V;
my $key;
my $lastc;
my $name;
my $status;
my $state = 'UNKNOWN';
my $answer = "";
my $snmpkey;
my $community = "public";
my $snmp_version = 1 ;
my $maxmsgsize = 1472 ; # Net::SNMP default is 1472
my ($seclevel, $authproto, $secname, $authpass, $privpass, $auth, $priv, $context);
my $port = 161;
my $list;
my $volumeName;
my $warning;
my $critical;
my $tmp;
my $percentage;
my $exact;
my $currentOID = '';
my @snmpoids;

my $unit = 'M';
my $factor;
my $thousand;

my $allocationUnits;
my $size;
my $used;
my $free;

my $hrStorageDescr = '1.3.6.1.2.1.25.2.3.1.3';
my $hrStorageAllocationUnits = '1.3.6.1.2.1.25.2.3.1.4';
my $hrStorageSize = '1.3.6.1.2.1.25.2.3.1.5';
my $hrStorageUsed = '1.3.6.1.2.1.25.2.3.1.6';

## Validate Arguments

process_arguments();
list() if (defined $list);

## Just in case of problems, let's not hang Nagios

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

alarm($timeout);



## Main function

# $volumeName =~ s/\//\\\//g;
$currentOID = fetch_volumeOID();

if ( $currentOID == -1 ) {
  printf("%s: %s\n", $state, $answer);
} else {
  push(@snmpoids, $hrStorageAllocationUnits . "." . $currentOID);
  push(@snmpoids, $hrStorageSize . "." . $currentOID);
  push(@snmpoids, $hrStorageUsed . "." . $currentOID);

  if ( ! defined($response = $session->get_request(@snmpoids)) ) {
    $answer=$session->error;
    $session->close;
    $state = 'WARNING';
    print "$state: SNMP error: $answer\n";
    exit $ERRORS{$state};
  }

  $allocationUnits = Math::BigFloat->new($response->{$hrStorageAllocationUnits . "." . $currentOID});
  print "'allocation units' for $volumeName are set to $allocationUnits bytes.\n" if $DEBUG;

  $size = Math::BigFloat->new($response->{$hrStorageSize . "." . $currentOID});
  $size->bmul($allocationUnits);
  print "The size of $volumeName is $size.\n" if $DEBUG;

  $used = Math::BigFloat->new($response->{$hrStorageUsed . "." . $currentOID});
  $used->bmul($allocationUnits);
  print "The used space on $volumeName is $used.\n" if $DEBUG;

  $free = Math::BigFloat->new($size);
  $free->bsub($used);
  print "The free space on $volumeName is $free.\n" if $DEBUG;

  if ( $DEBUG ) {
    print "Statistics:\n";
    print "- $size size\n";
    print "- $used used\n";
    print "- $free free\n";
    print "- $warning $unit (cast warning if free space falls below this)\n";
    print "- $critical $unit (cast critical if free space falls below this)\n";
  }

  if ( defined $percentage ) {
    $free = Math::BigFloat->new($free);
    $free->bmul(100);
    $free->bdiv($size);
  }

  # calculate warning values
  $tmp = Math::BigFloat->new($free);
  $warning = $tmp->bsub($warning);
  print "- warning is now at $warning $unit distance (OK if positive)\n" if $DEBUG;

  # calculate critical values
  $tmp = Math::BigFloat->new($free);
  $critical = $tmp->bsub($critical);
  print "- critical is now at $critical $unit distance (OK if positive)\n" if $DEBUG;

  if ( defined $percentage ) {
    # display everything in the appropriate units values
    $size = Math::BigFloat->new(100);
    $used = Math::BigFloat->new(100);
    $used->bsub($free);
  } else {
    # display everything in the appropriate units values
    $size->bdiv( $factor );
    $used->bdiv( $factor );
    $free->bdiv( $factor );
  }

  if ( $critical->is_neg() ) {
    $state = 'CRITICAL';
  } elsif ( $warning->is_neg() ) {
    $state = 'WARNING';
  } else {
    $state = 'OK';
  }

  $size->precision(-2);
  $used->precision(-2);
  $free->precision(-2);

  printf("%s: %s %s free|size=%s used=%s free=%s\n", $state, $free, $unit, $size, $used, $free);
}

exit $ERRORS{$state};


### subroutines

sub isNumeric
{
  if ($_[0] =~ /^(\d+\.?\d*|\.\d+)$/) {
    return 1;
  } else {
    return 0;
  }
}

sub fetch_volumeOID {
  if ( ! defined($response = $session->get_table($hrStorageDescr)) ) {
    $session->close;
    $answer = "SNMP error with snmp version ".$snmp_version." (".$session->error.")";
    $snmpkey = -1;
  }

  foreach $key ( keys %{$response} ) {
    print "comparing\n  '".$volumeName."' to\n  '".$response->{$key}."'\n" if $DEBUG;
    if ( defined $exact && $response->{$key} eq $volumeName ) {
      $key =~ /.*\.(\d+)$/;
      $snmpkey = $1;
      print "  EXACT MATCH FOUND: key $key / ID $snmpkey\n" if $DEBUG;
    } elsif ( ! defined $exact && $response->{$key} =~ /^$volumeName/) {
      $key =~ /.*\.(\d+)$/;
      $snmpkey = $1;
      print "  LEFT MATCH FOUND: key $key / ID $snmpkey\n" if $DEBUG;
    }
  }

  unless (defined $snmpkey) {
    $session->close;
    $answer = "Could not find ";
    if (defined $exact) {
      $answer .= "an exact";
    } else {
      $answer .= "a left";
    }
    $answer .= " match for '".$volumeName."' on ".$hostname;
    $snmpkey = -1;
  }

  return $snmpkey;
}

sub list() {
  if ( ! defined ($response = $session->get_table($hrStorageDescr)) ) {
    $answer = $session->error;
    $session->close;
    $state = 'CRITICAL';
    printf("$state: SNMP error with snmp version $snmp_version ($answer)\n");
    $session->close;
    exit $ERRORS{$state};
  }
 
  print "Available volume names are:\n";
  foreach $key ( keys %{$response} ) {
    $key =~ /.*\.(\d+)$/;
    print " - '".$response->{$key}."' (SNMP-ID: ".$1.")\n";
  }
  exit $ERRORS{'OK'};
}

sub usage() {
  printf "\nMissing arguments!\n";
  printf "\n";
  printf "usage: \n";
  printf "check_hr_storage -d <VOLUME> -w <MIN> -c <MIN> -H <HOSTNAME> [-C <community>] [-e]\n";
  printf "Copyright (C) 2008 Guenther Mair\n";
  printf "\n\n";
  exit $ERRORS{"UNKNOWN"};
}

sub print_help() {
  printf "check_hr_storage plugin for Nagios\n";
  printf "\nUsage:\n";
  printf "   -H (--hostname)   Hostname to query - (required)\n";
  printf "   -C (--community)  SNMP read community (defaults to public,\n";
  printf "                     used with SNMP v1 and v2c\n";
  printf "   -v (--snmp_version)  1 for SNMP v1 (default)\n";
  printf "                        2 for SNMP v2c\n";
  printf "                        SNMP v2c will use get_bulk for less overhead\n";
  printf "                        if monitoring with -d\n";
  printf "   -L (--seclevel)   choice of \"noAuthNoPriv\", \"authNoPriv\", or \"authPriv\"\n";
  printf "   -U (--secname)    username for SNMPv3 context\n";
  printf "\n";
  printf "   -u (--unit)       use one of the following units\n";
  printf "                      M - megabytes (default unit)\n";
  printf "                      G - gigabytes\n";
  printf "                      T - terabytes\n";
  printf "                      P - petabytes\n";
  printf "                      %% - percent\n";
  printf "\n";
  printf "   -f (--factor)     use factor 1000 instead of 1024 when calculating units\n";
  printf "\n";
  printf "   -w (--warning)    amount of free memory, examples:\n";
  printf "\n";
  printf "   -c (--critical)   amount of free memory, examples:\n";
  printf "\n";
  printf "   -l (--list)       list available SNMP hrStorageDescr volume names\n";
  printf "\n";
  printf "                      (use the -f switch to calculate units by factor 1000)\n";
  printf "   -A (--authpass)   authentication password (cleartext ascii or localized key\n";
  printf "                     in hex with 0x prefix generated by using \"snmpkey\" utility\n"; 
  printf "                     auth password and authEngineID\n";
  printf "   -a (--authproto)  Authentication protocol ( MD5 or SHA1)\n";
  printf "   -X (--privpass)   privacy password (cleartext ascii or localized key\n";
  printf "                     in hex with 0x prefix generated by using \"snmpkey\" utility\n"; 
  printf "                     privacy password and authEngineID\n";
  printf "   -d (--volume)     SNMP hrStorageDescr volume name\n";
  printf "   -e (--exact)      require an exact match of the storage name (like / on Unix)\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=$TIMEOUT)\n";
  printf "   -V (--version)    Plugin version\n";
  printf "   -h (--help)       usage help \n\n";
  printf " either -l or the combination of -d, -c and -w must be specified\n\n";
  print_revision($PROGNAME, '$Revision: 22 $');
}

sub process_arguments() {
  $status = GetOptions(
    "V"   => \$opt_V,        "version"        => \$opt_V,
    "h"   => \$opt_h,        "help"           => \$opt_h,
    "l"   => \$list,         "list"           => \$list,
    "e"   => \$exact,        "exact"          => \$exact,
    "f"   => \$thousand,     "factor"         => \$thousand,
    "u=s" => \$unit,         "unit=s"         => \$unit,
    "v=i" => \$snmp_version, "snmp_version=i" => \$snmp_version,
    "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,
    "c=s" => \$critical,     "critical=s"     => \$critical,
    "w=s" => \$warning,      "warning=s"      => \$warning,
    "d=s" => \$volumeName,   "volume=s"       => \$volumeName,
    "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,'$Revision: 22 $');
    exit $ERRORS{'OK'};
  }

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

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

  if ( $thousand ) {
    $factor = Math::BigFloat->new(1000);
  } else {
    $factor = Math::BigFloat->new(1024);
  }


  unless ( defined $list ) {

    if ( defined $warning ) {
      if ( isNumeric($warning) == 0 ) {
        print "The warning value '".$warning."' is not numeric!\n";
        exit 3;
      }
      # no proper rounding here, just trunkating sorry...
      my @tmp = split(/[\.]/, $warning);
      $warning = Math::BigFloat->new($tmp[0]);
      print "The warning value is '".$warning."'.\n" if $DEBUG;
    } else {
      print "A minimum amount of free space must be specified (-w).\n";
      usage();
      exit $ERRORS{"UNKNOWN"};
    }

    if ( defined $critical ) {
      if ( isNumeric($critical) == 0 ) {
        print "The critical value '".$critical."' is not numeric!\n";
        exit 3;
      }
      # no proper rounding here, just trunkating sorry...
      my @tmp = split(/[\.]/, $critical);
      $critical = Math::BigFloat->new($tmp[0]);
      print "The critical value is '".$critical."'.\n" if $DEBUG;
    } else {
      print "A minimum amount of free space must be specified (-c).\n";
      usage();
      exit $ERRORS{"UNKNOWN"};
    }

    if ( defined $unit ) {
      print "Choosen unit is '".$unit."'\n" if $DEBUG;
      $unit = uc($unit);
      if ( $unit eq 'M' ) {
        $unit = 'MB';
        $tmp = 2; # megabyte: use factor by the power of 2
      } elsif ( $unit eq 'G' ) {
        $unit = 'GB';
        $tmp = 3; # gigabyte: use factor by the power of 3
      } elsif ( $unit eq 'T' ) {
        $unit = 'TB';
        $tmp = 4; # terabyte: use factor by the power of 4
      } elsif ( $unit eq 'P' ) {
        $unit = 'PB';
        $tmp = 5; # petabyte: use factor by the power of 5
      } elsif ( $unit eq '%' ) {
        $percentage = 1;
      } else {
        print "'".$unit."' is not an allowed unit type for warning values.\n";
        exit 3;
      }
    } else {
      $unit = 'MB';
      $tmp = 2; # megabyte (default): use factor by the power of 2
    }
    $factor->bpow( $tmp );
    $warning->bmul( $factor );
    $critical->bmul( $factor );

    unless ( defined $volumeName ) {
      print "A valid volume name (-d) must be provided.\n";
      usage();
      exit $ERRORS{"UNKNOWN"};
    }
  }

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

  if ( $snmp_version =~ /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 = "-authkey => $authpass" ;
          } else {
            $auth = "-authpassword => $authpass";
          }
        }
      }
   
      # Privacy (DES encryption) wanted
      if ( $seclevel eq  'authPriv' ) {
        if ( ! defined $privpass ) {
          usage();
          exit $ERRORS{"UNKNOWN"};
        } else {
          if ( $privpass =~ /^0x/ ) {
            $priv = "-privkey => $privpass";
          } else {
            $priv = "-privpassword => $privpass";
          }
        }
      }

      # Context name defined or default
      unless ( defined $context ) {
        $context = "";
      }
    } else {
      usage();
      exit $ERRORS{'UNKNOWN'}; ;
    }
  } # end snmpv3


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

    if ( ! defined($session) ) {
      $state='UNKNOWN';
      $answer=$error;
      print ("$state: $answer");
      exit $ERRORS{$state};
    }
 
  } elsif ( $snmp_version =~ /3/ ) {

    if ($seclevel eq 'noAuthNoPriv') {
      ($session, $error) = Net::SNMP->session(
        -hostname => $hostname,
        -port     => $port,
        -version  => $snmp_version,
        -username => $secname,
      );
    } elsif ( $seclevel eq 'authNoPriv' ) {
      ($session, $error) = Net::SNMP->session(
        -hostname     => $hostname,
        -port         => $port,
        -version      => $snmp_version,
        -username     => $secname,
        $auth,
        -authprotocol => $authproto,
      ); 
    } elsif ( $seclevel eq 'authPriv' ) {
      ($session, $error) = Net::SNMP->session(
        -hostname     => $hostname,
        -port         => $port,
        -version      => $snmp_version,
        -username     => $secname,
        $auth,
        -authprotocol => $authproto,
        $priv
      );
    }
     
    if ( ! defined($session) ) {
      $state='UNKNOWN';
      $answer=$error;
      print ("$state: $answer");
      exit $ERRORS{$state};
    }

  } else {
    $state='UNKNOWN';
    print ("$state: No support for SNMP v$snmp_version.\n");
    exit $ERRORS{$state};
  }
}
## End validation
