#!/usr/bin/perl
#
# $Id: https.monitor,v 1.8 2003/11/11 21:18:53 vitroth Exp $
#
# An extremely simple https monitor for mon.
#
# Code structure based on Jon Meek & Jim Trocki's http.monitor program.
#
# https code taken from the get_page.pl function from the 
# Net::SSLeay distribution by Sampo Kellomaki <sampo@iki.fi>
#
# It makes use of the Net::SSLeay library and the OpenSSL package
# (www.openssl.org).
#
# To get around the problem that Net::SSLeay carps to STDERR 
# uncontrollably about a number of things (e.g. connection refused),
# we get around this by running the actual ssl get as an escaped 
# perl program and dropping the stderr of that instance. Gross, but
# strangely effective.
#
# Use the -v option if you actually want to see the full result and
# all headers. You'd never use this from mon, since it provides 
# non-mon-compliant output, but it can be interesting from the command
# line.
#
# Use the -w option if you want to be warned about SSL certificats which
# are about to expire, or already have expired.  '-w 10' would give you ten 
# days notice about a certificate expiration, which should be plenty of time
# to get a replacement certificate.
# 
#
#     Distribution and use of this program is under the same terms
#     as the OpenSSL package itself (i.e. free, but mandatory
#     attribution; NO WARRANTY). Please consult COPYRIGHT file in
#     the root of the SSLeay distribution.
# 

use strict; 

use Socket;
use Net::SSLeay qw(die_now die_if_ssl_error) ;
use Getopt::Std;   
use Time::ParseDate;
#
use English;


#Net::SSLeay::load_error_strings();
#Net::SSLeay::SSLeay_add_ssl_algorithms();

# Comment this out since on systems without a /dev/[u]random this
# line causes an unneccesary carp which will confuse mon.
# If you use Linux or BSD or other OS which supports a random device,
# feel free to uncomment this line.
#Net::SSLeay::randomize();

use vars qw($opt_p $opt_t $opt_u $opt_v $opt_w);
getopts ("vp:t:u:w:");
my $PORT = $opt_p || 443;
my $TIMEOUT = $opt_t || 30;
my $URL = $opt_u || "/";
my $EXPIREWARN = $opt_w ;       # How long in advance to warn about cert expiration.  0 means don't warn
$EXPIREWARN = 0 if (!defined $EXPIREWARN); # Don't warn by default

my @failures = ();
my @detail = ();

$PORT = getservbyname($PORT, 'tcp') unless $PORT =~ /^\d+$/;

my ($host, $OK);

foreach $host (@ARGV) {

    $OK = &httpsGET($host, $PORT, $URL);

    if (!defined ($OK) || $OK == 0) {
        push (@failures, $host);
    }
}

if (@failures == 0) {
    exit 0;
}

print "@failures\n";
print join("\n",@detail);

exit 1;



#  Main function begins here
sub httpsGET {
    my ($site, $port, $path) = (@_);
    my $total_bytes = 0;       #set total bytes transferred to 0
    my ($headers,$cert, $res, $reply, $ServerOK);

    print "attempting to contact site $site on port $port with path $path\n" if $opt_v;

    $ServerOK = 1;
    eval {    
	local $SIG{ALRM} = sub { print STDERR "$site: Timeout\n" if $opt_v; die "Timeout Alarm" };
        alarm $TIMEOUT;

	($reply, $res, $headers, $cert) = Net::SSLeay::get_https3($site, $port, $path);
	print STDERR "XXXXX\nreply\nXXXXX\n$reply\nXXXXX\nres\nXXXXX\n$res\nXXXXX\nheaders\nXXXXX\n$headers\nXXXXX\ncert\nXXXXX\n$cert\n" if $opt_v;
	if (!($res =~ /^HTTP\/([\d\.]+)\s+(?:200|30[12]|401)\b/)) {
            $ServerOK = 0;
	    push(@detail,"$site: $res\n$headers\n");
        } elsif (!$cert) {
            $ServerOK = 0;
	    push @detail, "$site: No certificate returned\n";
	} else {

	  if ($EXPIREWARN) {
	    my $servercertname = Net::SSLeay::X509_NAME_oneline(Net::SSLeay::X509_get_subject_name($cert));
	    my $signingcertname = Net::SSLeay::X509_NAME_oneline(Net::SSLeay::X509_get_issuer_name($cert));
	    my $notafter = Net::SSLeay::P_ASN1_UTCTIME_put2string (Net::SSLeay::X509_get_notAfter($cert));
	    my $notbefore = Net::SSLeay::P_ASN1_UTCTIME_put2string (Net::SSLeay::X509_get_notBefore($cert));
	    my $na_time = parsedate($notafter);
	    my $nb_time = parsedate($notbefore);
	    my $now = time;
	    my $later = $now + (86400 * $EXPIREWARN);
	    print STDERR "XXXXX\nnotbefore $notbefore\nnotafter $notafter\nna_time $na_time\nnb_time $nb_time\nnow $now\nlater $later\n" if $opt_v;
	    if ( $now < $nb_time ) {
		push @detail,"$site: Certificate not valid until $notbefore\ncertificate: $servercertname\nCA certificate: $signingcertname";
		$ServerOK = 0;
	    }
	    if ($now > $na_time) {
		push @detail,"$site: Certificate expired as of $notafter\ncertificate: $servercertname\nCA certificate: $signingcertname";
		$ServerOK = 0;
	    } elsif ($later > $na_time ) {
		push @detail,"$site: Certificate will expire at $notafter\ncertificate: $servercertname\nCA certificate: $signingcertname";
		$ServerOK = 0;
	    }
	  }
	}

    };
    
    if ($EVAL_ERROR and ($EVAL_ERROR =~ 'Timeout Alarm')) {
        push @detail, "$site: *** Timeout\n";
        return 0;
    }

    if ($EVAL_ERROR) {
	push @detail, "$site:  Eval Error $EVAL_ERROR\n";
	return 0;
    }
    
    return $ServerOK;

}

