#!/usr/bin/perl -w # Check peer certificate validity # Require perl module : IO::Socket, Net::SSLeay, Date::Parse # Require unix programs : openssl, echo, sendmail # # Copyright (C) 2003 Emmanuel Lacour # # This file is free software; you can redistribute it and/or modify it # under the terms of the GNU General Public License as published by the # Free Software Foundation; either version 2, or (at your option) any # later version. # # This file is distributed in the hope that it will be # useful, but WITHOUT ANY WARRANTY; without even the implied warranty # of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this file; see the file COPYING. If not, write to the Free # Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA # 02110-1301, USA. # # Local variables are prefixed with "l_" use strict; use IO::Socket; use Net::SSLeay; use Getopt::Long; use Date::Parse; Net::SSLeay::SSLeay_add_ssl_algorithms(); Net::SSLeay::randomize(); my $VERSION = '0.6.3'; my $AUTHOR = 'Emmanuel Lacour, '; # Default values my $opensslpath = "/usr/bin/openssl"; my $sendmailpath = "/usr/lib/sendmail"; my $mailreport = 0; my $alert = 5; my $mail = "root"; my $conf = "/etc/sslexpire/sslexpire.conf"; my %hosts; my $host; my @ports; my $port; my $portlist; my $rhost; my $rport; my $tmp; # Get options # output : -m = mail, default = stdout # host : -h host, -p port, default = from config file # config : -f configfile, default /etc/sslexpire/sslexpire.conf # standard : --help, --version my %opts; GetOptions (\%opts, 'host|h=s', 'port|p=s', 'mail|m', 'conf|c=s', 'verbose|v', 'help', 'version|'); # Command line is used if (($opts{'host'}) && ($opts{'port'})) { push @{$hosts{$opts{'host'}}}, $opts{'port'}; } elsif (($opts{'host'}) || ($opts{'port'})) { print STDERR "ERR: please provide HOST _and_ PORT or use a configuration file.\n"; &usage; # Configuration file is used } else { if ($opts{'conf'}) { $conf = $opts{'conf'}; } # Parse config file open (CONF,$conf) or die "Couldn't read configuration file $conf: $!\n"; while () { # Skip comments next if (/^[ \t]*#/); # Alert param. if (/^alert[ \t]*=/) { ($tmp,$alert) = split /=/, $_; # Mail param. } elsif (/^mail[ \t]*=/) { ($tmp,$mail) = split /=/, $_; # Use hosts from config file if none are given by command line } elsif ((!$opts{'host'}) && (!$opts{'port'}) && (/:/)) { ($tmp,$portlist) = split /:/, $_; chomp ($tmp); chomp ($portlist); # There is multiple ports if (/,/) { @ports = split /,/, $portlist; foreach (@ports) { push @{$hosts{$tmp}}, $_; } # There is only one port } else { push @{$hosts{$tmp}},$portlist; } } } close CONF; } $mailreport = 1 if ($opts{'mail'}); if ($opts{'help'}) { &usage; } if ($opts{'version'}) { print "sslexpire $VERSION\n"; print "Written by $AUTHOR\n"; exit; } # Print program usage sub usage { print "Usage: sslexpire [OPTION]... -h, --host=HOST check only this host -p, --port=TCPPORT check this port on the previous host -m, --mail report by mail instead of STDOUT -c, --conf=FILE use this config file --help print this help, then exit "; exit; } # This will return the expiration date sub getExpire { my ($l_host,$l_port) = @_; my ($l_expdate,$l_comment); # Connect to $l_host:$l_port my $socket = IO::Socket::INET->new( Proto => "tcp", PeerAddr => $l_host, PeerPort => $l_port ); # If we connected successfully if ($socket) { # Intiate ssl my $l_ctx = Net::SSLeay::CTX_new(); my $l_ssl = Net::SSLeay::new($l_ctx); Net::SSLeay::set_fd($l_ssl, fileno($socket)); my $res = Net::SSLeay::connect($l_ssl); # Get peer certificate my $l_x509 = Net::SSLeay::get_peer_certificate($l_ssl); if ($l_x509) { my $l_string = Net::SSLeay::PEM_get_string_X509($l_x509); # Get the expiration date, using openssl ($l_expdate,$l_comment) = split(/\n/, `echo "$l_string" | $opensslpath x509 -enddate -subject -noout 2>&1`); $l_expdate =~ s/.*=//; chomp($l_expdate); } else { $l_expdate = 1; $l_comment = 1; } # Close and cleanup Net::SSLeay::free($l_ssl); Net::SSLeay::CTX_free($l_ctx); close $socket; } else { $l_expdate = 1; $l_comment = 1; } return ($l_expdate,$l_comment); } # Report if needed # # sub report { # Convert date into epoch my ($l_expdate,$l_comment,$l_host,$l_port) = @_; my $l_subject = ""; if ($l_expdate ne "1") { # The current date my $l_today = time; my $l_epochdate = str2time($l_expdate); # Calculate diff between expiration date and today my $l_diff = ($l_epochdate - $l_today)/(3600*24); # Report if needed if ($l_diff < $alert) { $l_subject = "Warning ssl certificate on $l_host:$l_port expires in $l_diff days:" if ($l_diff > 1); $l_subject = "Warning ssl certificate on $l_host:$l_port expires today:" if (($l_diff > 0) && ($l_diff < 1)); $l_subject = "Warning ssl certificate on $l_host:$l_port expired:" if ($l_diff <= 0); my $l_mesg = "Expiration date: $l_expdate\n$l_comment\n"; # Mail report if ($mailreport) { sendmail($mail, $l_subject, $l_mesg); } else { print "$l_subject\n"; print "$l_mesg\n"; } } } else { $l_subject = "Unable to read certificate on $l_host:$l_port!"; if ($mailreport) { sendmail($mail, $l_subject, ""); } else { print "$l_subject\n"; } } } # Send mail - sendmail (to,subject,body) sub sendmail { my $to = shift; my $subj = shift; my $mesg = shift; chomp ($to); chomp ($subj); chomp ($mesg); open (MAIL,"| $sendmailpath -t") or die "Couldn't open $sendmailpath: $!\n"; print MAIL "To: $to\n"; print MAIL "Subject: $subj\n"; print MAIL "\n"; print MAIL "$mesg\n" if $mesg; close MAIL; if ((my $status = $?>>8) != 0) { die "sendmail: exit status $status\n"; } } # Main # # We haven't hosts to check... if (!%hosts) { print STDERR "No host to check!\n"; &usage; }; # Parse hosts foreach $host (keys %hosts) { # Parse ports for each hosts foreach $port (@{$hosts{$host}}) { if ($opts{'verbose'}) { print "Checking\t$host:$port\n"; } # Get expiration date my ($expdate,$comment) = &getExpire($host,$port); # Report &report("$expdate","$comment","$host","$port"); } } # script documentation (POD style) =head1 NAME sslexpire - Remotely check ssl certificate expiration date. =head1 DESCRIPTION This program connect to an host:port to retrieve the expiration date of the ssl certificate. It gives a report to STDOUT or by email using configuration file. =head1 COMMAND LINE PARAMETERS Optional command line parameters are the host and the port to connect. This allow checking a single host instead of using those given in the configuration file for periodically checks. =head1 OPTIONS =head2 B<-c> I, B<--conf>=I Specify an alternate config file. =head2 B<-h> I, B<--host>=I Connect to I instead of those given in the config file. =head2 B<-p> I, B<--port>=I Specify the port to connect to (used in conjonction with --host). =head2 B<-v>, B<--verbose> Prints out verbose messages. =head2 B<-m>, B<--mail> Send report by mail instead of STDOUT. It will use the address given in the config file or root by default. =head2 B<--help> Prints out command-line help. =head2 B<--version> Prints out version information. =head1 FILES /etc/sslexpire/sslexpire.conf =head1 AUTHOR Emmanuel Lacour, elacour@home-dn.net =cut