#!/usr/bin/perl
#
# $Id: check_shib_idp 3866 2013-10-23 17:56:29Z oschonef $
#

use strict;
use warnings;
use Getopt::Long;
use LWP;
use Crypt::OpenSSL::X509;
use Date::Parse;
use POSIX qw(mktime);
use constant {
    OK        => 0,
    WARNING   => 1,
    CRITICAL  => 2,
    UNKNOWN   => 3,
    DEPENDENT => 4,
};
use constant {
    IDP  => 'idp',
    JAVA => 'java',
};
my $CODE2STRING = {
    0 => 'OK',
    1 => 'WARNING',
    2 => 'CRITICAL',
    3 => 'UNKNOWN',
    4 => 'DEPENDENT',
};


sub get_diff {
    my $ts = shift;

    my ($sec1, $min1, $hour1, $day1, $mon1, $year1) =
	strptime($ts);
    my ($sec2, $min2, $hour2, $day2, $mon2, $year2) =
	gmtime(time());
    my $t1 = mktime($sec1, $min1, $hour1, $day1, $mon1, $year1);
    my $t2 = mktime($sec2, $min2, $hour2, $day2, $mon2, $year2);
    return $t1 - $t2;
}


sub status {
    my $code    = shift;
    my $message = shift;

    $message =~ s/\n*$//;
    printf("%s: %s\n", $CODE2STRING->{$code}, $message);
    exit $code;
}


my $host           = undef;
my $url            = 'idp/status';
my $use_ssl        = 0;
my $max_clock_diff = 120;
my $cert_warn_days = 31;
my $timeout        = 10;
my $verbose        = 0;
my $memory_warn    = 80;
my $memory_crit    = 95;


my $result = GetOptions('H=s' => \$host,
                        'u=s' => \$url,
                        't=i' => \$timeout,
                        'S'   => \$use_ssl,
                        'D=i' => \$max_clock_diff,
                        'C=i' => \$cert_warn_days,
                        'v'   => \$verbose,
                        'W=i'    => \$memory_warn,
                        'X=i'    => \$memory_crit);
if (!($result)) {
    status(UNKNOWN, 'USAGE: -H <host> [-u <url>] [-t <timeout>] [-S] ' .
	   '[-D <clock skew>] [-C <age>] [-W <value] [-X <value>]');
}


if (!defined($host)) {
    status(UNKNOWN, 'missing manadorty parameter -H <host>');
}

if (($memory_warn < 0) || ($memory_warn > 100)) {
    status(UNKNOWN, 'parameter -W <value> (memory warning) ' .
	   'must be between 0 and 100');
}

if (($memory_crit < 0) || ($memory_crit > 100)) {
    status(UNKNOWN, 'parameter -X <value> (memory critical) ' .
	   'must be between 0 and 100');
}

if ($memory_warn > $memory_crit) {
    status(UNKNOWN, 'value for memory warning must be less than ' .
	   'value for memory critical');
}


#
# arm timeout alarm
#
local $SIG{ALRM} = sub {
    status(UNKNOWN, 'check timed out');
};
alarm $timeout;


$url =~ s|^/+||g;
$url =~ s|/+$||g;
my $idp_status_url = sprintf('%s://%s/%s', ($use_ssl ? 'https' : 'http'),
                                            $host,
                                            $url);
print STDERR "IDP URL: $idp_status_url\n" if ($verbose > 0);

my $browser = LWP::UserAgent->new;
my $response = $browser->get($idp_status_url);
if ($response->is_success) {
    my $code    = OK;
    my $message = undef;
    my $result = {};
    eval {
        LINE:
	foreach my $line (split("\r*\n", $response->content)) {
	    if (($line =~ m/^#/) || $line eq '') {
		next LINE;
	    }
	    if ($line =~ m/^(\w+):\s+(.*?)$/g) {
		$result->{$1} = $2;
	    }
	}
    };
    if ($@) {
        status(CRITICAL, "error parsing response: " . $@);
    }
	
    my $time = $result->{'current_time'};
    my $diff = get_diff($time);
    print STDERR "time: $time, difference: $diff\n" if ($verbose > 0);
    if (abs($diff) > $max_clock_diff) {
	status(CRITICAL,
	       sprintf('clock skew too large (IDP time: %s, skew %d)',
		       $time, $diff));
    }

    my $mem_used = $result->{'used_memory'};
    my $mem_max  = $result->{'maximum_memory'};
    my $mem_percentage = -1.0;
    if (defined($mem_used) || defined($mem_max)) {
	$mem_used =~ s/MB$//;
	$mem_max  =~ s/MB$//;
	$mem_percentage = int(($mem_used * 10000) / $mem_max / 100);

	printf("memory usage %s/%s (%d%%)\n",
	       $mem_used, $mem_max, $mem_percentage)
	    if ($verbose > 0);

	if ($mem_percentage > $memory_crit) {
	    $code    = CRITICAL;
	    $message = sprintf('IDP is low on memory (%s MB/%s MB = %d%%)',
			       $mem_used, $mem_max, $mem_percentage);
	}
	elsif ($mem_percentage > $memory_warn) {
	    $code    = WARNING;
	    $message = sprintf('IDP is running low on memory ' .
			       '(%s MB/%s MB = %d%%)',
			       $mem_used, $mem_max, $mem_percentage);
	}
    }


    my $versions = {
        IDP  => $result->{'idp_version'},
	JAVA => $result->{'jdk_version'},
    };


    my $cert = undef;
    eval {
	my $cert_data = $result->{'default_signing_tls_key'};
	$cert_data =~ s/(.{64})/$1\n/gs;
	$cert = Crypt::OpenSSL::X509->new_from_string(
	    sprintf("-----BEGIN CERTIFICATE-----\n%s\n" .
		    "-----END CERTIFICATE-----\n", $cert_data));
    };
    if ($@) {
	status(UNKNOWN, "cannot parse certificate data: $@");
    }
    
    print STDERR "Certificate: " . $cert->subject() . ", validUntil: " .
	$cert->notAfter() . "\n" if ($verbose > 0);

    if ($cert->checkend(0)) {
	$code    = CRITICAL;
	$message = sprintf('signing certificate is expired since %s',
			   $cert->notAfter());
    }
    elsif ($cert->checkend($cert_warn_days * 86400)) {
	my $expire_days = int(get_diff($cert->notAfter()) / 86400);
	$code           = WARNING;
	$message        = sprintf('signing certificate expires in %d day(s)',
				  $expire_days);
    }

    if ($code != OK) {
	status($code, $message);
    }
    else {
        status(OK, sprintf('Shibboleth IDP version %s ' .
                           '(Java version %s, %d%% memory used)',
			   $versions->{IDP}, $versions->{JAVA},
                           $mem_percentage));
    }
}
else {
    my $status = $response->status_line || 'unknown';
    status(CRITICAL, sprintf('error fetching status: %s', $status));
}
exit OK;
