#!/usr/bin/perl -w
# Nagios check for 3M SIP2 protocol
# Copyright 2011-2013 BC Libraries Cooperative
# Robin H. Johnson <rjohnson@sitka.bclibraries.ca>
use strict;
use warnings;
use IO::Socket;
use Getopt::Long;
# These lines bring in the real Nagios utils.pm
#use lib "/usr/lib/nagios/plugins";
#use lib "/usr/lib64/nagios/plugins";
#use lib "/usr/libexec/nagios/plugins";
#eval 'use utils qw($TIMEOUT %ERRORS &usage)';
my ($TIMEOUT, %ERRORS);
# Borrowed from Nagios utils.pm:
$TIMEOUT = 15;
%ERRORS=('OK'=>0,'WARNING'=>1,'CRITICAL'=>2,'UNKNOWN'=>3,'DEPENDENT'=>4);
sub usage {
  my $format=shift;
  printf($format,@_);
  exit $ERRORS{'UNKNOWN'};
}
# Back to normal code
$|=1;

our $PROTOCOL = '2.00';
my ( $host, $port, $username, $password, $institution, $verbose, $help, $critical_state, $warning_state, $opt_t, $sock, $response);
$critical_state = 'YYYYYY';
$warning_state = '......';

sub syntax {
  my $s = shift;
  unless ($s =~ m/Help:/) {
  	$s = "Error: (".$s.")" or $s = 'Unknown';
  }
  print "$s\n" unless ($help);
  print "Syntax: $0 -h <host> -u <username> -p <password> -i <institution>\n";
  print "* --host -h         Host\n";
  print "* --username -u     Username\n";
  print "* --password -p     Password\n";
  print "* --institution -i  Institution\n";
  print "* --port -P n       Port\n";
  print "  --warning ......  Return warning if state does not match regex (6 chars exactly)\n";
  print "  --critical ...... Return critical if state does not match regex (6 chars exactly).\n";
  print "  --verbose -v      Verbose\n";
  print "  --help -h         This help\n";
  exit $ERRORS{'UNKNOWN'};
}

sub display_res {
  my ($state, $answer) = @_;
  printf "%s: %s", $state, $answer;
  exit $ERRORS{$state};
}

sub sip2_login {
  my ($username, $password, $institution) = @_;
  return sprintf "9300CN%s|CO%s|CP%s|\r", $username, $password, $institution;
}
sub sip2_sc_status {
  my ($status, $width) = @_;
  display_res('UNKNOWN', "sip2_sc_status: Invalid status\n") unless $status =~ /^.$/;
  display_res('UNKNOWN', "sip2_sc_status: Invalid width\n") unless $width =~ /^\d{1,3}$/;
  return sprintf "99%s%03d%4s\r", $status, $width, $PROTOCOL;
}

sub read_sip2 {
  my ($sock) = @_;
  local $/ = "\r"; 
  my $response = <$sock>; 
  return undef unless defined $response;
  chomp($response); 
  $response =~ s/\r$//g;
  return $response;
}

Getopt::Long::Configure('bundling');
GetOptions (
  "p|password=s"    => \$password,
  "u|username=s"    => \$username,
  "i|institution=s" => \$institution,
  "H|host=s"        => \$host,
  "P|port=i"        => \$port,
  "h|help"          => \$help,
  "v|verbose"       => \$verbose,
  "t|timeout=i"     => \$opt_t,
  "c|critical=s"    => \$critical_state, 
  "w|warning=s"     => \$warning_state,
);

syntax("Help:") if ($help);
syntax("Missing host") unless (defined($host));

(($opt_t) && ($TIMEOUT = $opt_t)) || ($TIMEOUT = 120);
usage("timeout must be numeric value") unless ($TIMEOUT =~ m/^[0-9]+$/);

# Don't hang if there are timeout issues
$SIG{'ALRM'} = sub {
	# display_res will not work here
	print ("CRITICAL: No response from SIP2 server (alarm)\n");
	exit $ERRORS{'CRITICAL'};
};
alarm($TIMEOUT);

print "Connecting to $host:$port\n" if ($verbose);
unless ($sock = IO::Socket::INET->new(PeerAddr => $host, PeerPort => $port, Proto => 'tcp')) {
  display_res('CRITICAL', "Could not connect to SIP2 server ".$host.":".$port."\n");
}
print "Connected to $host:$port\n" if ($verbose);

print "Trying login for $username\n" if ($verbose);
print $sock sip2_login($username, $password, $institution);
$response = read_sip2($sock);
if(!defined($response)) {
  display_res('CRITICAL', "Did not recieve any login response!\n");
}
print "Login response: $response\n" if ($verbose);
if($response !~ /^94.$/) {
  display_res('CRITICAL', "Did not recieve a login response code 94x, got '$response' instead\n");
}
if($response !~ /^941$/) {
  display_res('CRITICAL', "Login failed, got '$response'\n");
}
print "Logged in as $username\n" if ($verbose);

print "Doing sc_status request\n" if ($verbose);
print $sock sip2_sc_status(1, 30);
$response = read_sip2($sock);
print "sc_status raw response: $response\n" if ($verbose);
# 98YYYYNN60000320110614    0053422.00AOSITKA|BXYYYYYYYYYNYNNNYN|
if($response !~ /^98/) {
  display_res('CRITICAL', "Did not recieve a sc_status response code 98..., got '$response' instead\n");
}
my (# 1.00/2.00
    $acs_state, $acs_timeout, $acs_retries, $acs_time, $acs_protocol, $acs_institution, 
    $acs_libraryname, 
    $acs_supported_messages, $acs_terminal_location, $acs_screen_message, $acs_print_line);

# 1.00/2.00
my %acs_status = (
'state' => substr($response,2,6),
'timeout' => substr($response,8,3),
'retries'=> substr($response,11,3),
'time' => substr($response,14,18),
'protocol' => substr($response,32,4),
);
$response = substr($response, 36);
print "sc_status raw response remaining: $response\n" if ($verbose);
($acs_status{'institution'}) = ($response =~ m/.*AO([^|]+)|/) if ($response =~ /.*AO/);
($acs_status{'libraryname'}) = ($response =~ m/.*AM([^|]+)|/) if ($response =~ /.*AM/);
# 2.00
($acs_status{'supported_messages'}) = ($response =~ m/.*BX([^|]+)|/) if ($response =~ /.*BX/);
($acs_status{'terminal_location'}) = ($response =~ m/.*AN([^|]+)|/) if ($response =~ /.*AN/);
($acs_status{'screen_message'}) = ($response =~ m/.*AF([^|]+)|/) if ($response =~ /.*AF/);
($acs_status{'print_line'}) = ($response =~ m/.*AG([^|]+)|/) if ($response =~ /.*AG/);

my @key_order = qw(state timeout retries time protocol institution libraryname supported_messages terminal_location screen_message print_line);
printf("sc_status response: %s\n", join(' ', map { sprintf "%s=%s", $_, $acs_status{$_} if defined($acs_status{$_}) } @key_order)) if ($verbose);

my $state = $acs_status{'state'};
if($state !~ /^$critical_state$/) {
  display_res('CRITICAL', sprintf "Status does not fit critical requirements: have=%s want=%s\n", $state, $critical_state);
}
if($state !~ /^$warning_state$/) {
  display_res('WARNING', sprintf "Status does not fit warning requirements: have=%s want=%s\n", $state, $warning_state);
}

close $sock;
my $time = $acs_status{'time'};
my $tz = substr($time, 8, 4);
$tz = 'Local' if ($tz =~ /^ {4}$/);
$tz = 'UTC+'.(unpack('C',substr($tz,3,1))-64) if ($tz =~ /^ {3}[A-M]$/);
$tz = 'UTC-'.(unpack('C',substr($tz,3,1))-78+1) if ($tz =~ /^ {3}[N-Y]$/);
$tz = 'UTC' if ($tz =~ /^ {3}Z$/);
# TODO: check if the time on the SIP server is drifting/broken?

$time = sprintf("%s/%s/%s %s:%s:%s %s", 
substr($time, 0, 4), substr($time, 4, 2), substr($time, 6, 2), 
substr($time, 12, 2), substr($time, 14, 2), substr($time, 16, 2),
$tz);
display_res('OK', "Terminal logged in; state ($state) is ok; timesync is $time\n");

# vim: ft=perl ai et sts=4 ts=4:
