#!/usr/bin/perl
#
# Copyright (c) 2007-2018 Eric F Crist
#
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or 
# without modification, are permitted provided that the following 
# conditions are met:
#
# Redistributions of source code must retain the above copyright 
# notice, this list of conditions and the following disclaimer.
# 
# Redistributions in binary form must reproduce the above copyright 
# notice, this list of conditions and the following disclaimer in 
# the documentation and/or other materials provided with the distribution.
# 
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#

use strict;
use warnings;

my $OS = `uname`;
if ($OS =~ m/^\wBSD/){
	use lib "/usr/local/libexec/nagios";
} elsif ($OS =~ m/Linux/){
	use lib "/usr/local/nagios/libexec";
}

use Net::SNMP::Util;
use Data::Dumper;
use Getopt::Long;
use Pod::Usage;

'$Revision: 363 $' =~ m/Revision: (\d+)/;
my $revision = $1;
'$HeadURL: https://www.secure-computing.net/svn/trunk/nagios/check_printer $' =~ m/HeadURL: ([\w\:\/\-\.\_]+) /;
my $src_url = $1;
my $debug = 0;
my ($host, @ignore);
my $community = "public";
my $warning = "15";
my $critical = "10";
my $pages = 0;

our %getopts;
Getopt::Long::Configure("bundling");
GetOptions(
	"h|host=s"	=>	\$host,
	"C|community=s"	=>	\$community,
	"w|warn=i"	=>	\$warning,
	"c|crit=i"	=>	\$critical,
	"i=i"		=>	\@ignore,
	'V|version'	=>	sub { print '$Id: check_printer 363 2014-07-23 14:37:59Z ecrist $' ."\n"; exit 0 },
	"d|debug"	=>	\$debug,
	"p|pages"	=>	\$pages,
	"man"		=>	sub { pod2usage(-exitstatus => 0, -verbose => 2); exit 0 }
) or pod2usage(2);


pod2usage(2)  unless (defined($host));

my $base_oid = ".1.3.6.1.2.1.43.11.1.1";
my $type_oid = "3.1";
my $name_oid = "6.1";
my $uom_oid = "7.1";
my $max_oid  = "8.1";
my $curr_oid = "9.1";
my $loop;


my $comm = $ARGV[1];

my @vars = ("snmpwalk -On -v 1 -c $community $host $base_oid.$name_oid",
	   "snmpwalk -On -v 1 -c $community $host $base_oid.$uom_oid",
	   "snmpwalk -On -v 1 -c $community $host $base_oid.$max_oid",
	   "snmpwalk -On -v 1 -c $community $host $base_oid.$curr_oid");

my(@values, @names, @max, @min);

our %uom = (     "4" => "µm",
	 	 "7" => "impressions",
		 "8" => "pages",
		"11" => "hrs",
		"12" => "oz/1000",
		"13" => "gr/10",
		"14" => "gr/100",
		"15" => "mL",
		"16" => "feet",
		"17" => "meters",
		"18" => "items",
		"19" => "%");

my %oids = (
	name => "$base_oid.$name_oid",
	uom => "$base_oid.$uom_oid",
	max => "$base_oid.$max_oid",
	curr => "$base_oid.$curr_oid",
	pages => "1.3.6.1.2.1.43.10.2.1.4.1",
);

my %snmpparams = (
	-version => 1,
	-community => $community,
);

my ($result, $error) = snmpwalk(
	hosts => $host,
	oids => \%oids,
	snmp => \%snmpparams,
);
die "[ERROR] $error\n" unless defined $result;

my %status;

foreach my $i (sort(keys %{$result->{$host}{"name"}})) {
	#for (my $i=1; $i <= keys(%{$result->{$host}{"name"}});$i++){
	my $cname = $result->{$host}{"name"}{$i};
	my %tmp = (
		uom	=>	$result->{$host}{"uom"}{$i},
		max	=>	$result->{$host}{"max"}{$i},
		curr	=>	$result->{$host}{"curr"}{$i},
	);
	$status{$cname} = \%tmp;
}

#print &Dumper(\%status);

## Assemble performance data and error string.
my $perf_str = "";
my $is_warn = 0;
my $is_crit = 0;
my $err_str = "";
while (my($key, $value) = each(%status)){
	$key =~ s/,/ /g;
	my ($maximum, $current, $val_critical, $val_warning);
	$val_critical = $critical;
	$val_warning = $warning;
	if (!exists($value->{"curr"}) || $value->{"curr"} == -3){
		## we can process as yes/no
		$current = 100;
		## Override the critical/warning, since they're moot
		$val_critical = 0;
		$val_warning = 0;
	} elsif (($value->{"max"} == -2) and ($value->{"uom"} ne '%')){
		## we don't know (-2 == unknown) the max and can't compute
		next;
	} else {
		$maximum = $value->{"max"};
	}
	
	if (!exists($value->{"curr"}) || $value->{"curr"} < 0){
		## weird value, set to 0 for math reasons
		$current = 0;
	} else {
		$current = round(($value->{"curr"} / $maximum) * 100);
	}

	my $lcurrent;
	if (($key =~ m/waste/i) or ($key =~ m/Toneruppsamlare/i) or ($key =~ m/Restonner/i)){
		# invert the $current as waste is calculated in reverse
		if ($value->{"curr"} == -3){
			$lcurrent = 0;
		} else {
			$lcurrent = $maximum - $current;
		}
	} else {
		$lcurrent = $current;
	}
	if ($lcurrent < $val_critical){
		$is_crit = $is_crit + 1;
		if ($err_str eq ""){
			$err_str = "$key";
		} else {
			$err_str = "$err_str, $key";
		}
	} elsif ($lcurrent < $val_warning){
		$is_warn = $is_warn + 1;
		if ($err_str eq ""){
			$err_str = "$key";
		} else {
			$err_str = "$err_str, $key";
		}
	}
	if ($debug){
		print "Key: $key\nCur: $current\nWarn: $val_warning\nCrit: $val_critical\n";
	}
	$perf_str .= " '$key'=$current%;$val_warning;$val_critical;0;100";
}

# Add page count as a simple integer
if ($pages) {
	$perf_str .= " 'Total Page Count'=".$result->{$host}{"pages"}{1};
}


if ($debug){
	print Dumper(\%status);
	print "\n\n############ ATTENTION ############\n";
	print "You have debug enabled.  If asked to enable debug by the developer,\n";
	print "please send all of the output, including your command line to\n";
	print "ecrist\@secure-computing.net with the subject line 'check_printer DEBUG' along\n";
	print "with a description of the problem you're experiencing.\n###################################\n";
	print '$Id: check_printer 363 2014-07-23 14:37:59Z ecrist $'."\n\n";
}
if ($is_crit){
	print "$err_str CRITICAL.  See http://$host | $perf_str\n";
	exit 2;
} elsif ($is_warn){
	print "$err_str WARNING. See http://$host | $perf_str\n";
	exit 1;
} else {
	print "Printer Supplies OK | $perf_str\n";
	exit 0;
}


###
### subroutines here
###

sub round {
	my($number) = shift;
	return int($number + .5 * ($number <=> 0));
}

__END__

=head1 check_printer

check_printer - Gather supply metrics from a printer via SNMP query

=head1 SYNOPSIS

check_printer [options]

Options:
	
	-h, --host		Printer hostname or IP address.
	-C, --community		SNMP community string, defaults to "public"
	-w, --warn		Warning threshold
	-c, --crit		Critical threshold
	-i, --ignore		Supply ignore.  See man page for more information (--man)
	-p, --pages		Print total page count in performance data.
	-V, --version		Prints version string and exits
	-d, --debug		Prints debug output, useful when reporting a problem to the developer, or getting index numbers for --ignore
	--man			Prints man page

=head1 DESCRIPTION

B<check_printer> is a Perl script that retrieves supply metrics from printers using the snmpwalk command.

=head1 OPTIONS

=over

=item -h, --host

Defines the host or IP address passed to snmpwalk.

=item -C, --community

Identifies the SNMP community string.  Requires read access only.

=item -w, --warn

Value at which a warning is generated.  For example, if -w 10 is set, when 
supply levels fall to 10% or below, a warning will be sent as part of the 
performance data.

=item -c, --crit

Value at which a critical alert is generated.  This number should be lower
than --warn is set, but this is not enforced.

=item -i, --ignore

Supply index that should be ignore from the output.  Multiple values are 
accepted, separately:

=over

check_printer -i 1 -i 2

=back

Will exclude supply indexes 1 and 2.  To get the supply index list, run the 
check_printer utility with --debug defined and the top of the output will
list available supply metrics as follows:

=over

$VAR1 = [
    '.1.3.6.1.2.1.43.11.1.1.6.1.1 = STRING: "Black Toner"',
    '.1.3.6.1.2.1.43.11.1.1.6.1.2 = STRING: "Drum Unit"',
    '.1.3.6.1.2.1.43.11.1.1.7.1.1 = INTEGER: 13',
    '.1.3.6.1.2.1.43.11.1.1.7.1.2 = INTEGER: 7',
    '.1.3.6.1.2.1.43.11.1.1.8.1.1 = INTEGER: -2',
    '.1.3.6.1.2.1.43.11.1.1.8.1.2 = INTEGER: -2',
    '.1.3.6.1.2.1.43.11.1.1.9.1.1 = INTEGER: -3',
    '.1.3.6.1.2.1.43.11.1.1.9.1.2 = INTEGER: 0'
];

=back

In the above example, we are looking for the OID string starting with 
B<.1.3.6.1.2.1.43.11.1.1.6.1> and the index is the last number. To exclude the
Drum Unit, pass I<-i 2> to check_printer

=item -p, --pages

Print the page count for the device in the performance data output as an
integer.  This does not cause any alerts on warn/crit since it is just an
ever-increasing number.

=item -V, --version

Print the check_printer version string and exits.

=item -d, --debug

Outputs debug information.  If you are having problems with a printer or device,
send the debug output to B<ecrist@secure-computing.net> and identify your printer
model and manufacturer.

=item --man

Prints this man page.  :)
