#!/usr/bin/perl
################################################################################
## Jonathan Miller 04/21/2009
## check_icap.pl
## v: 0.9
## vnotes: Not yet tested on vendor besides SecureComputing Webwasher, but does
## 	   conform to ICAP RFC as best as I can deduce.
################################################################################
##
## Check for proper responses in ICAP server
## instances. Similar to HTTP, an ICAP/1.0 200 OK
## indicates service health for a health check
## action.
##
################################################################################
## 4/22/09 so far this is only tested on SecureComputing Webwasher AV instances
## soon to be tested on Bluecoat ProxyAV as well
################################################################################

use Nagios::Plugin::Getopt;

# Instantiate usage object
$ng = Nagios::Plugin::Getopt->new(
  usage => 'Usage: %s -H <host> [-m REQMOD|RESPMOD]', 
  version => '0.1',
  url => 'http://www.iamfromit.com/check_icap/',
  blurb => 'This plugin test health of an ICAP service.',
  plugin => 'check_icap',
);

## spec and help arguments are mandatory for objects

## what host will we be checking?
$ng->arg(
  spec => 'host|H=s',
  help => 'Specify the hostname or IP address',
  required => 1,
);

## on what port should the aforementioned host be checked?
$ng->arg(
  spec => 'port|P=s',
  help => 'Specify the port',
  default => '1344',
);

## should it be a REQMOD request or a RESPMOD request?
## REQMOD (REQuest MODification) will be a simple URL check 
## RESPMOD (RESPonse MODification) will be trickier, as we'll need to supply
##   some filedata to check
$ng->arg(
  spec => 'method|m=s',
  help => [
    'Check a REQMOD service',
    'Check a RESPMOD service',
  ],
  label => [ 'REQMOD', 'RESPMOD' ],
  required => 1,
);

## what is the name of the reqmod or respmod service on this appliance?
## examples: avscan, wwav, wwreqmod, wwrespmod, ec
## this is used to build the initial request to the ICAP instance, such as
## REQMOD icap://the.ip.add.ress/wwrespmod ICAP/1.0
$ng->arg(
  spec => 'service|s=s',
  help => 'Service name of the AV service (e.g. wwrespmod, wwreqmod, avscan)',
  required => 1,
);

## what to expect in return data when the request data is sent
$ng->arg(
  spec => 'expect|e=s',
  help => 'What response should check_icap look for',
  default => 'ICAP/1.0 200 ok',
);

# Parse arguments and process standard ones (e.g. usage, help, version)
$ng->getopts;

#### ICAP ERROR CODES

@icap_errors = ("100 - Continue after ICAP Preview (Section 4.5).",
"204 - No modifications needed (Section 4.6).",
"400 - Bad request.",
"404 - ICAP Service not found.",
"405 - Method not allowed for service (e.g., RESPMOD requested for service that supports only REQMOD).",
"408 - Request timeout.  ICAP server gave up waiting for a request from an ICAP client.",
"418 - Further encapsulations required",
"500 - Server error.  Error on the ICAP server, such as *out of disk space*",
"501 - Method not implemented.  This response is illegal for an OPTIONS request since implementation of OPTIONS is mandatory.",
"502 - Bad Gateway.  This is an ICAP proxy and proxying produced an error.",
"503 - Service overloaded.  The ICAP server has exceeded a maximum connection limit associated with this service; the ICAP client should not exceed this limit in the future.",
"505 - ICAP version not supported by server.");
#print "\r\n".$icap_errors{"500"}."\r\n";

## receive the last line returned if not "ICAP/1.0 200 OK", substr the num out of it, and print the matching array var 
sub GetIcapError {
	my($linecode) = @_;
#print "Linecode: $linecode\r\n";
$error_code = substr($linecode, 9, 3);
#print "\$error_code: $error_code\r\n";
@temp = grep{/^$error_code/} @icap_errors;
print "@temp\n";
exit 3;
};

################################################################################
## catch up with what we've done so far
##
## established the command line paramaters
## -h for help
## -u for usage
## -H for host
## -m for method
## -p for port
## -e for expected output string
################################################################################

my $t;
use Net::Telnet ();
$t = new Net::Telnet;

if ($ng->get('method') =~ m/REQMOD/) {
$t->open(Host =>$ng->get('host'),Port => $ng->get('port'));
$t->print("REQMOD icap://10.10.3.10/".$ng->get('service')." ICAP/1.0
Host: ".$ng->get('host')."
X-Scan-Progress-Interval: 10
Encapsulated: req-hdr=0, null-body=51

GET /test.exe HTTP/1.1
Host: icap.health.check\r\n");
$line = $t->getline;
die &GetIcapError($line) unless $line =~ /ICAP\/1.0 200 OK/;
if ( $line =~ /ICAP\/1.0 200 OK/ ) {
	print "OK\r\n";
	exit 0;
}
} elsif ($ng->get('method') =~ m/RESPMOD/) {
$t->open(Host =>$ng->get('host'),Port => $ng->get('port'));
$t->print("RESPMOD icap://".$ng->get('host')."/".$ng->get('service')." ICAP/1.0
Host: ".$ng->get('host')."
X-Scan-Progress-Interval: 3
Encapsulated: req-hdr=0, res-hdr=51, res-body=90

GET /test.exe HTTP/1.1
Host: icap.health.check

HTTP/1.1 200 OK
Content-length: 70

46
1234567890123456789012345678901234567890123456789012345678901234567890
0
\r\n");
$line = $t->getline;
die &GetIcapError($line) unless $line =~ /ICAP\/1.0 200 OK/;
if ( $line =~ /ICAP\/1.0 200 OK/ ) {
	print "OK\r\n";
	exit 0;
}
} else {
	print "Unhandled exception during the check (Probably unknown ICAP method or typo in CLI usage)\r\n";
	exit 3;
}
