#!/usr/bin/perl -w
#
# check_asc
#
# (c) 2013 by Florian Fuchs (florian.fuchs@k.roteskreuz.at)
#
# 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, write to the Free Software
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
#

use POSIX;
use strict;
use Net::SNMP;
use Switch;
use Data::Dumper;
use Getopt::Long;
use lib "/usr/local/nagios/libexec";
use utils qw($TIMEOUT %ERRORS &print_revision &support);
&Getopt::Long::config('bundling');
use Date::Parse;

my $PROGNAME="check_asc";		# program name
my $VERSIONNUMBER="1.2 (2013-11-15)";	# version number
my $PORT=161;				# snmp port
my $MAXMSGSIZE=1472;			# max snmp message size
my $SNMPVERSION=1;			# snmp version number


############################################################
# OIDs (DO NOT CHANGE UNLESS YOU KNOWN WHAT YOU ARE DOING) #
############################################################
my $OID_ASCSTATUS="1.3.6.1.4.1.4063.2.1.2.12";
my $OID_CPULOAD1="1.3.6.1.4.1.2021.10.1.3.1";
my $OID_CPULOAD5="1.3.6.1.4.1.2021.10.1.3.2";
my $OID_CPULOAD15="1.3.6.1.4.1.2021.10.1.3.3";
my $OID_UPTIME="1.3.6.1.2.1.25.1.1.0";
my $OID_RAMTOTAL="1.3.6.1.4.1.2021.4.5.0";
my $OID_RAMFREE="1.3.6.1.4.1.2021.4.6.0";
my $OID_BUFFERUSED="1.3.6.1.4.1.2021.4.14.0";
my $OID_CACHEUSED="1.3.6.1.4.1.2021.4.15.0";
my $OID_SWAPTOTAL="1.3.6.1.4.1.2021.4.3.0";
my $OID_SWAPFREE="1.3.6.1.4.1.2021.4.4.0";
my $OID_MOUNTPOINT="1.3.6.1.2.1.25.2.3.1.3";
my $OID_ALLOCSIZE="1.3.6.1.2.1.25.2.3.1.4";
my $OID_TOTALSIZE="1.3.6.1.2.1.25.2.3.1.5";
my $OID_USEDSIZE="1.3.6.1.2.1.25.2.3.1.6";
my $OID_STORAGETYPE="1.3.6.1.2.1.25.2.3.1.2";
my $OID_OSVERSION="1.3.6.1.2.1.1.1.0";
my $OID_ASCVERSION="1.3.6.1.4.1.4063.5.5";
my $OID_SYSTEMDATE="1.3.6.1.2.1.25.1.2.0";

my $status;			# GetOptions Status
my $opt_V;			# boolean (true if parameter -V was provided)
my $opt_h;			# boolean (true if parameter -h was provided)
my $hostname;			# hostname to query
my $command;			# command to execute
my $timeout;			# snmp query timeout
my %session_opts;		# session options
my $session;			# session
my $error;			# snmp error message
my $state = "UNKNOWN";		# return state (OK, CRITICAL, WARNING)
my $answer = "";		# return text
my @snmpoids;			# snmp oids to query
my $response;			# snmp query result
my $criticallevel;		# level of critical state
my $warnlevel;			# level of warning state
my $oid;			# current oid
my @lastoids;			# last digits of oids (required for space checking)
my $mountpoint;			# mounoint to check

#
# process user arguments
#
sub process_arguments();

#
# display program usage
#
sub usage($);

$status = process_arguments();	# process arguments

# in case something goes wrong
$SIG{'ALRM'} = sub {
        print ("ERROR: No snmp response from $hostname (alarm)\n");
	print ("Have you activated the \"SNMP Get\" option in the ASC DataManager\n");
	print ("under \"Configuration\"->\"Alarm Notifications\"?\n\n");
        exit $ERRORS{"UNKNOWN"};
};

alarm($timeout);		# set timeout



#
# try to open a SNMP session, if it fails, exit
#
($session, $error) = Net::SNMP->session(%session_opts);

if (!defined($session)) {
                        $state='UNKNOWN';
                        $answer=$error;
                        print ("$state: $answer\n");
			print ("Have you activated the \"SNMP Get\" option in the ASC DataManager\n");
			print ("under \"Configuration\"->\"Alarm Notifications\"?\n\n");
                        exit $ERRORS{$state};
}

#
# depending on the given command, add the approriate oids
#

switch($command)
{
	case "ASCSTATUS"
	{
		push(@snmpoids, $OID_ASCSTATUS);
	}
	
	case "CPULOAD1"
	{
		push(@snmpoids, $OID_CPULOAD1);
	}

	case "CPULOAD5"
	{
		push(@snmpoids, $OID_CPULOAD5);
	}

	case "CPULOAD15"
	{
		push(@snmpoids, $OID_CPULOAD15);
	}

	case "UPTIME"
	{
		push(@snmpoids, $OID_UPTIME);
	}
	case "RAM"
	{
		push(@snmpoids, $OID_RAMTOTAL);
		push(@snmpoids, $OID_RAMFREE);
		push(@snmpoids, $OID_BUFFERUSED);
		push(@snmpoids, $OID_CACHEUSED);
	}
	case "SWAP"
	{
		push(@snmpoids, $OID_SWAPTOTAL);
		push(@snmpoids, $OID_SWAPFREE);
	}
	case "OSVERSION"
	{
		push(@snmpoids, $OID_OSVERSION);
	}
	case "ASCVERSION"
	{
		push(@snmpoids, $OID_ASCVERSION);
	}
	case "SYSTEMDATE"
	{
		push(@snmpoids, $OID_SYSTEMDATE);
	}	
	case "DISKSPACE"
	{
		$oid = $OID_MOUNTPOINT;		# starting point (this does not contain a real mountpoint)

		# go to the next oid
		while (my $result = $session->get_next_request(-varbindlist => [$oid]))
		{
			$oid = (keys(%$result))[0];		# get oid of next mount point
			$answer = $result->{$oid};		# retrieve reust
			my ($last) = $oid =~ /.*\.(.*)/;	# get last digits of current oid (required for different OIDs like usedsize,...)

			# if we are in a different branch, there are no mount points left, so we leave the loop
			if (substr($oid, 0, length($OID_MOUNTPOINT)) ne $OID_MOUNTPOINT)
			{
				last;
			}			

			# if it is a mount point (starting with a /)
			if ($answer =~ /^\//)
			{
				# add OIDs
				push(@snmpoids, $OID_MOUNTPOINT.".".$last);
				push(@snmpoids, $OID_USEDSIZE.".".$last);
				push(@snmpoids, $OID_ALLOCSIZE.".".$last);
				push(@snmpoids, $OID_TOTALSIZE.".".$last);

				# add last digit for later use
				push(@lastoids, $last);
			}

		}
	}
	else
	{
		$session->close;
		usage("Unknown command: $command\n");		# we do not know that command
	}
}

#
# query OIDs
#
if (!defined($response = $session->get_request(@snmpoids))) {
        $answer=$session->error;
        $session->close;
        $state = 'WARNING';
        print ("$state: SNMP error: $answer\n\n");
	print ("Have you activated the \"SNMP Get\" option in the ASC DataManager\n");
	print ("under \"Configuration\"->\"Alarm Notifications\"?\n\n");
        exit $ERRORS{$state};
}


#
# depending on the command get state and prepare the answer
#
switch($command)
{
	case "ASCSTATUS"
	{
		$answer=$response->{$OID_ASCSTATUS};
		switch($answer)
		{
			case "Ok" { $state = "OK"; }
			case "Warning" { $state = "WARNING"; }
			case "Error" { $ state = "CRITICAL"; }
		}
	}
	case "CPULOAD1"
	{
		$answer=$response->{$OID_CPULOAD1}*100;
		if (defined $criticallevel && $answer>=$criticallevel)
		{
			$state = "CRITICAL";
		}
		elsif (defined $warnlevel && $answer>=$warnlevel)
		{
			$state = "WARNING";
		}
		else
		{
			$state = "OK";
		}
		$answer = "CPU Load: $answer% (1 minute average)";

	}
	case "CPULOAD5"
	{
		$answer=$response->{$OID_CPULOAD5}*100;
		if (defined $criticallevel && $answer>=$criticallevel)
		{
			$state = "CRITICAL";
		}
		elsif (defined $warnlevel && $answer>=$warnlevel)
		{
			$state = "WARNING";
		}
		else
		{
			$state = "OK";
		}
		$answer = "CPU Load: $answer% (5 minute average)";
	}
	case "CPULOAD15"
	{
		$answer=$response->{$OID_CPULOAD15}*100;
		if (defined $criticallevel && $answer>=$criticallevel)
		{
			$state = "CRITICAL";
		}
		elsif (defined $warnlevel && $answer>=$warnlevel)
		{
			$state = "WARNING";
		}
		else
		{
			$state = "OK";
		}
		$answer = "CPU Load: $answer% (15 minute average)";
	}
	case "UPTIME"
	{
		$state = "OK";
		$answer="$state: $response->{$OID_UPTIME}";
	}
	case "RAM"
	{
		my $ramtotal = int($response->{$OID_RAMTOTAL}/1024);
		my $ramfree =  int($response->{$OID_RAMFREE}/1024);
		my $ramused = $ramtotal-$ramfree;
		my $bufferused = int($response->{$OID_BUFFERUSED}/1024);
		my $cacheused = int($response->{$OID_CACHEUSED}/1024);
		my $buffercache = $bufferused + $cacheused;
		my $freewithoutbuffercache = $ramfree + $buffercache;
		my $freepercent= int($freewithoutbuffercache/$ramtotal*100);
		$answer="RAM: total: $ramtotal MB - used: $ramused MB - free: $ramfree MB";
		$answer="$answer - buffer used: $bufferused MB - cache used: $cacheused MB - buffer + cache used: $buffercache MB";
		$answer="$answer - free without buffer and cache: $freewithoutbuffercache MB ($freepercent \%)";

		if (defined $criticallevel && $freepercent<=$criticallevel)
		{
			$state = "CRITICAL";
		}
		elsif (defined $warnlevel && $freepercent<=$warnlevel)
		{
			$state = "WARNING";
		}
		else
		{
			$state = "OK";
		}
	}
	case "SWAP"
	{
		my $swaptotal = int($response->{$OID_SWAPTOTAL}/1024);
		my $swapfree = int($response->{$OID_SWAPFREE}/1024);
		my $swapused = $swaptotal-$swapfree;
		my $freepercent = int($swapfree/$swaptotal*100);
		$answer="SWAP: total: $swaptotal MB - used: $swapused MB - free: $swapfree MB ($freepercent %)";

		if (defined $criticallevel && $freepercent<=$criticallevel)
		{
			$state = "CRITICAL";
		}
		elsif (defined $warnlevel && $freepercent<=$warnlevel)
		{
			$state = "WARNING";
		}
		else
		{
			$state = "OK";
		}
	}
	case "OSVERSION"
	{
		$answer = "OS version: $response->{$OID_OSVERSION}\n";
		$state="OK";
	}
	case "ASCVERSION"
	{
		$answer = "ASC version: $response->{$OID_ASCVERSION}\n";
		$state="OK";
	}
	case "SYSTEMDATE"
	{
		my $year;
		my $month;
		my $day;
		my $hour;
		my $minute;
		my $second;
		my $systemdate;
		my $deviation;

		($year, $month, $day, $hour, $minute, $second) = $response->{$OID_SYSTEMDATE} =~ /0x(....)(..)(..)(..)(..)(..)(.*)/;		# get current time of ASC 
		my $now = localtime();			# get current time of local server

		$systemdate = sprintf("%4d-%02d-%02d %02d\:%02d\:%02d", hex($year), hex($month), hex($day), hex($hour), hex($minute), hex($second));
		$deviation = str2time($systemdate)-str2time($now);		# calculate deviation
		
		$answer = "ASC Systemdate: $systemdate";

		if ($deviation == 0)
		{
			$answer = "$answer (no deviation)";
		}
		elsif ($deviation < 0)
		{
			$answer = sprintf("$answer (%d seconds behind actual time)", abs($deviation));
		}
		elsif ($deviation > 0)
		{
			$answer = sprintf("$answer (%d seconds ahead of actual time)", abs($deviation));
		}
		
		if (defined $criticallevel && abs($deviation) >= $criticallevel)
		{
			$state = "CRITICAL";
		}
		elsif (defined $warnlevel && abs($deviation) >= $warnlevel)
		{
			$state = "WARNING";
		}
		else
		{
			$state="OK";
		}
	}
	case "DISKSPACE"
	{
		$answer = "";
		foreach (@lastoids)
		{
			if (($response->{$OID_TOTALSIZE.".".$_} > 0) && (((defined $mountpoint) && ($mountpoint eq $response->{$OID_MOUNTPOINT.".".$_})) || (! defined $mountpoint))) 
			{
				my $lmountpoint = $response->{$OID_MOUNTPOINT.".".$_};
				my $total = int($response->{$OID_ALLOCSIZE.".".$_}*$response->{$OID_TOTALSIZE.".".$_}/1024/1024);
				my $used = int($response->{$OID_ALLOCSIZE.".".$_}*$response->{$OID_USEDSIZE.".".$_}/1024/1024);
				my $free = $total - $used;
				my $freepercent = int($free/$total*100);
				my $usedpercent = 100-$freepercent;

				$answer = "$answer$lmountpoint: total $total MB - used: $used MB ($usedpercent %) - free $free MB ($freepercent %)\n";

				if (defined $criticallevel && $freepercent <= $criticallevel)
				{
					$state = "CRITICAL";
				}
				elsif (defined $warnlevel && ($freepercent <= $warnlevel) && ($state ne "CRITICAL"))
				{
					$state = "WARNING";
				}
				elsif (($state ne "CRITICAL") && ($state ne "WARNING"))
				{	
					$state = "OK";
				}
			} 
		}

		if ($answer eq "")
		{
			$answer = "Unknown mountpoint: $mountpoint\n";
		}
	}
}

print ("$answer\n");		# print answer
$session->close;		# close session
exit $ERRORS{$state};		# return state

#
# print usage
#
sub usage($)
{
	print "$_[0]\n";
	print_help();
        exit $ERRORS{'UNKNOWN'};
}


#
# print help
#
sub print_help()
{
	printf "\n*** $PROGNAME $VERSIONNUMBER ***\n\n";
	printf "Copyright (c) 2013 by Dipl.-Ing. Florian Fuchs (florian.fuchs\@k.roteskreuz.at)\n";
	printf "check_asc comes with ABSOLUTELY NO WARRANTY\n";
        printf "This programm is licensed under the terms of the ";
        printf "GNU General Public License\n(check source code for details)\n";
        printf "\n\n";
        printf "check_asc plugin for Nagios \n";
        printf "\nUsage:\n";
        printf "   -h (--help)       		usage help\n";
	printf "   -V (--version)    		plugin version\n";
	printf "   -t (--timeout)		seconds before the plugin times out (default=$TIMEOUT)\n";
	printf "   -H <hostname>\n";
	printf "   -C <command> (--command)	execute command\n";
	printf "      valid commands:	ASCSTATUS\n";
	printf "			ASCVERSION\n";
	printf "			CPULOAD1 -w <warnlevel> -c <criticallevel> (1 minute load)\n";
	printf "			CPULOAD5 -w <warnlevel> -c <criticallevel> (5 minute load)\n";
	printf "			CPULOAD15 -w <warnlevel> -c <criticallevel> (15 minute load)\n";
	printf "			DISKSPACE -w <warnlevel in percent of free space> \n";
	printf "                                  -c <criticallevel in percent of free space> \n";
	printf "                                  [-P mountpoint] \n";
	printf "			OSVERSION\n";
	printf "			RAM -w <warnlevel in percent of free memory without buffer \n";
	printf "                             and cache> -c <criticallevel in percent of free memory\n"; 
	printf "                             without buffer and cache>\n";
	printf "			SWAP -w <warnlevel in percent of free space> \n";
	printf "                             -c <criticallevel in percent of free space>\n";
	printf "			SYSTEMDATE -w <warnlevel in seconds of deviation> \n";
	printf "                                   -c <criticallevel in seconds of deviation>\n";
	printf "			UPTIME\n";
	printf "\n";
}


#
# process arguments
#
sub process_arguments()
{
	$status = GetOptions(
		"V"		=> \$opt_V, "version"	=> \$opt_V,
		"h"		=> \$opt_h, "help"	=> \$opt_h,
		"H=s"		=> \$hostname, "hostname=s"	=> \$hostname,
		"C=s"		=> \$command, "command=s"	=> \$command,
		"c=s"		=> \$criticallevel,
		"w=s"		=> \$warnlevel,
		"P=s"		=> \$mountpoint,
	);

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

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

        if (! utils::is_hostname($hostname)){
                usage("Hostname invalid or not given");
        }

	if (! $command)
	{
		usage("No command given");
	}

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

	if ((defined $mountpoint) && ($command ne "DISKSPACE"))
	{
		usage("Parameter -P may only be used for command DISKSPACE.");
	}

	if ($command eq 'CPULOAD1' || $command eq "CPULOAD5" || $command eq "CPULOAD15" || $command eq "RAM" || $command eq "SWAP" || $command eq "DISKSPACE" || $command eq "SYSTEMDATE")
	{
		unless (defined $criticallevel || defined $warnlevel)
		{
			usage("You must provide at least -c or -w parameter.");
		}		
	}
        %session_opts = (
                -hostname   => $hostname,
                -port       => $PORT,
                -version    => $SNMPVERSION,
                -maxmsgsize => $MAXMSGSIZE
        );


}


