#!/usr/local/bin/perl
#
# usage: 
#    check_printer_status [-vsp][-h ipaddr][-m model][-f xml_configuration]
#	-v          - verbose mode
#	-s          - show status
#	-w          - wait for end of job
#	-p          - no pagecount
#	-h ipaddr   - printer ip address or DNS name
#	-m model    - model information
#	-f xml_configuration - xml configuration file
#
#
# Do SNMP query of printer; return PJL-style information for ifhp 
# (must have ifhp-3.5.8a3 or newer)
#
# Originally written to deal with Xerox appsocket printers, but could 
# easily work with other printers, too.
#
# Symlink this printer as 3 separate names use the -s, -w, -p flags:
#    check_printer_status      (status check)
#    check_printer_waitend     (EOJ check--will spin until EOJ comes back)
#    check_printer_pagecount   (pagecount query)
#


#BEGIN {
#	if ($0 =~ m/^(.*?)[\/\\]([^\/\\]+)$/) {
#		$runtimedir = $1;
#		$PROGNAME = $2;
#	}
#}

use strict;
use Net::SNMP;
use XML::Simple;
use Data::Dumper;
use Getopt::Std;
use IO::Select;

$| = 1;
# XML config file containing printer-specific information
my $xml_config="/usr/local/libexec/filters/snmp_printer_status.xml";

# other variables
my $host;
my $model = 'default';
my $error;
my $response;
my $status;
my $complete=0;
my $verbose=0;
my %parms;
my $getpagecount = 1;
my $interval = 1;
my $start_time = time();
my $poweron = 0;

sub usage()
{
	print STDERR <<EOF;
use: $0 [-vps][-t interval][-h ipaddr][-m model][-f xml_configuration]
	-v          - verbose mode
	-p          - no pagecount
	-s          - show poweron pagecount
	-t interval  - interval in secs between queries (default $interval)
	-h ipaddr   - printer ip address or DNS name
	-m model    - model information
	-f xml_configuration - xml configuration file
EOF
	exit(1);
}

getopts("vpf:h:m:t:", \%parms) or usage();
usage() if @ARGV ;
$verbose = 1 if $parms{v};

$host = $parms{h} if defined $parms{h};
$model = $parms{m} if defined $parms{m};
$xml_config = $parms{f} if defined $parms{f};
$getpagecount = 0 if defined $parms{p};
$poweron = 1 if defined $parms{s};
$interval = $parms{t} if defined $parms{t};

if( not $host ){ print STDERR "$0: no host found\n"; exit(1); }
$host =~ s/\%.*//;
if( not $host ){ print STDERR "$0: no host found\n"; exit(1); }
if( not $xml_config ){ print STDERR "$0: no xml_config found\n"; exit(1); }
if( not -f $xml_config ){ print STDERR "$0: no xml_config file '$xml_config' found\n"; exit(1); }

print STDERR "Host: $host\tModel: $model\n" if $verbose;

# parse XML config file
# slurp in all printer-specific OID info, 
my $ref = XMLin($xml_config, forcearray => 0) or die "cannot parse $xml_config";
print STDERR Dumper($ref) if $verbose;

# SNMP parameters
# Not sure why the following line doesn't work...I suspect something 
# strange in XML::Simple...Nate thinks it's in XML::Parser called from
# XML::Simple.

my $community=(substr($ref->{snmp_params}->{community}, 0) || "public");
my $timeout=($ref->{snmp_params}->{timeout} || 5);
my $retries=($ref->{snmp_params}->{retries} || 3);
my $version=($ref->{snmp_params}->{version} || 1);
my $session;
my $lastmessage = [];
my $lastlevel = [];
my $label = "";
my $start = 0;
my $old = {};
my $oid = {};

for my $name ( qw( devicestatus printerstatus alertseveritylevel alertgroup) ){
	my $hash = $ref->{$name};
	print STDERR "name: $name\n" if $verbose;
	foreach my $key (keys %$hash ){
		my $value = $hash->{$key};
		print STDERR "$key $hash->{$key}\n" if $verbose;
		$hash->{$value} = $key if $value;
	}
}

foreach my $name ( qw(
    alert_oid prtAlertSeverityLevel prtAlertDescription 
	prtAlertGroup pagecount_oid poweroncount_oid
	devicestatus_oid printerstatus_oid display_oid
	devicedescr_oid printername_oid ) ){
 	$oid->{$name}=$ref->{printerdata}->{$model}->{$name}
		|| $ref->{printerdata}->{default}->{$name}
		|| $ref->{printerdata}->{$name};
	if( not $oid->{$name} ){
			print STDERR "Missing '$name' oid value\n";
			exit(1);
	}
}

print STDERR "SNMP params: host $host, community $community, timeout $timeout, retries $retries\n" if $verbose;
($session, $error) = Net::SNMP->session(-hostname => $host,
					-community => $community,
					-timeout => $timeout,
					-version => $version,
					-retries => $retries);
if( $error ){
	print STDERR "Cannot create SNMP session '$error'";
	exit(1);
}

 
sub getstatus( $ $ $ ){
	my ($name, $oid, $legend) = @_;

	print STDERR "Running '${name}' get_table '($oid)'\n" if $verbose;
	return if( not defined $oid or $oid !~ m/^\./ );
	$response = $session->get_table( -baseoid =>$oid);
	if( !$response ){
		print STDERR "Bad get_table $name $oid - " . $session->error() . "\n" if $verbose;
		print STDERR "Running ${name} get_request ($oid)\n" if $verbose;
		$response = $session->get_request( -varbindlist =>[$oid]);
	}
	if( !$response ){
		$status = $session->error();
		print STDERR "Bad OID $name $oid - $status\n" if $verbose;
		if( not defined $old->{status} or $status ne $old->{status} ){
			my $now = (time - $start_time) . "- ";
			print STDERR "${now}STATUS= $status\n" if $verbose;
			print "STATUS= $status\n";
			$old->{status} = $status;
			$old->{pagecount} = "";
		}
		next;
	}
	print STDERR "${name} response: " . Dumper($response) if $verbose;
	my $value = "";
	foreach (keys %$response){
		$value = $response->{$_};
	}
	$value = $label if ($label = $ref->{${name}}->{${value}});
	if( ${value} ne ($old->{$name} || "") ){
		print "${legend}= ${value}\n";
		$old->{$name} = ${value};
		if( $name ne "pagecount" ){
			$old->{pagecount} = "";
		}
	}
}

 while(not $complete){
	if ($start){
		# be nice to the network
		my $s = IO::Select->new();
		$s->add(\*STDIN);
		my @ready;
		print STDERR "Starting wait\n" if $verbose;
		@ready = $s->can_read( $interval );
		print STDERR "End wait\n" if $verbose;
		while( @ready ){
			my $fd = shift(@ready);
			if( <$fd> ){
				print STDERR "Refresh\n" if $verbose;
				$lastmessage = [];
				$lastlevel = [];
				$old = {};
			} else {
				print STDERR "Exit\n" if $verbose;
				exit(0);
			}
		}
	}
	if( !$start ){
		getstatus( "devicedescr", $oid->{devicedescr_oid}, "DEVICEDESCR" );
		getstatus( "printername", $oid->{printername_oid}, "PRINTERNAME" );
	}
	$start = 1;
	my $alert_oid = ($oid->{alert_oid} || "");
	if( $alert_oid =~ /^\./ ){
		print STDERR "Running alert get_table ($alert_oid)\n" if $verbose;
		$response = $session->get_table( -baseoid =>$alert_oid);
		if( !$response ){
			print STDERR "Bad get_table $alert_oid - " . $session->error() . "\n" if $verbose;
			print STDERR "Running alert get_request ($alert_oid)\n" if $verbose;
			$response = $session->get_request( -varbindlist =>[$alert_oid]);
		}
		if( $response ){
			print STDERR "printerstatus response: " . Dumper($response) if $verbose;
			my $v = {};
			my $change = 0;
			foreach my $k (sort keys %$response ){
				my $r = $response->{$k};
				my($val, $index) = $k =~ m/${alert_oid}(.*)\.(\d+)$/;
				print STDERR "my $k = $r, '$val' $index\n" if $verbose;
				$v->{$val}->[$index] = $r;
			}
			my $n = @{$v->{$oid->{prtAlertSeverityLevel}}};
			my $m = @{$lastmessage};
			$n = $m if $n < $m;
			print STDERR "decoded response: " . Dumper($v) if $verbose;
			my $critical_error = 0;
			for( my $i = 1; $i < $n; ++$i ){
				my $level = $v->{$oid->{prtAlertSeverityLevel}}->[$i];
				if( not $level ){
					if( $lastmessage->[$i] ){
						print $lastmessage->[$i] . " CLEARED\n";
						$lastmessage->[$i] = "";
						$lastlevel->[$i] = "";
						$change = 1;
					}
					next;
				}
				$level = $label if ($label = $ref->{alertseveritylevel}->{$level});
				print STDERR "LEVEL $level\n" if $verbose;
				my $alertgroup = $v->{$oid->{prtAlertGroup}}->[$i];
				$alertgroup = $label if ($label = $ref->{alertgroup}->{$alertgroup});
				my $warning = ($v->{$oid->{prtAlertDescription}}->[$i] || "");
				my $out = "STATUS= level: $level group: $alertgroup message: $warning";
				if( not defined $lastmessage->[$i]
						and ($level eq 'critical' or $lastlevel->[$i] eq 'critical') ){
					my $now = (time - $start_time) . "- ";
					if( $level eq 'warningBinaryChangeEvent' ){
						$out = $lastmessage->[$i] . " CLEARED";
						$lastmessage->[$i] = "";
						$lastlevel->[$i] = "";
					}
					print STDERR "${now}${out}\n" if $verbose;
					print $out . "\n";
					$change = 1;
				}
				$critical_error = 1 if( $level eq 'critical' );
				$lastmessage->[$i] = $out;
				$lastlevel->[$i] = $level;
			}
			print "CRITICAL $critical_error\n" if $verbose;
			next if $critical_error or $change;
		}
	}

	getstatus( "printerstatus", $oid->{printerstatus_oid}, "PRINTERSTATUS" );
	getstatus( "devicestatus", $oid->{devicestatus_oid}, "DEVICESTATUS" );
	getstatus( "display", $oid->{display_oid}, "STATUS" );
	if( $poweron ){
		getstatus( "poweroncount", $oid->{poweroncount_oid}, "STATUS" );
	}
	if( $getpagecount
		and ($old->{devicestatus} ne 'down' and $old->{devicestatus} ne 'testing')
		and ($old->{printerstatus} ne 'printing' and $old->{printerstatus} ne 'unknown') ){
		getstatus( "pagecount", $oid->{pagecount_oid}, "PAGECOUNT" );
	}
}

# all done
$session->close;


# exit with JSUCC
$status = 0;
print STDERR "Exit status: 0\n" if $verbose;
exit $status;
