#!/usr/local/bin/perl -w

# $Id: tls.pl,v 1.1 2019/07/30 14:21:48 lolo Exp $

use strict;

my %protocols=(
	'pop3s' => {
		'port' => 995,
		'send' => "quit\r\n",
		'expect' => "+OK",
		'banner' => 1,
	},
	'imaps' => {
		'port' => 993,
		'send' => "ABC123 LOGOUT\r\n",
		'expect' => "* OK",
		'banner' => 1,
	},
);

my $timeout = 10;

use IO::Socket::SSL;
use Net::SSLeay;
use Time::Local;

use XymonExt::Untaint;
use XymonExt::ReportToBB;

sub get_cert_info($);


my @tags= keys %protocols ;

foreach my $k (keys %ENV)
{
	XymonExt::Untaint::untaint(\$ENV{$k});
};

foreach my $tag (@tags)
{
	if (open(XYMONGREP,"$ENV{'XYMONHOME'}/bin/xymongrep tls_$tag |"))
	{
		while (<XYMONGREP>)
		{
			# 62.210.94.14 mail.agneau.org # tls_imaps tls_pop3s
			if (/^(\d+\.\d+\.\d+\.\d+)\s+(\S+)\s*#\s*.*tls_(\Q$tag\E)/)
			{
				my $ip =$1;
				my $bbdispname = $2;
				my $proto = $3;
	
				&check_tls_proto($ip,$proto,$bbdispname)
			};
		};
		close(XYMONGREP);

	}
	else
	{
		warn "open $ENV{'XYMONHOME'}/bin/xymongrep $tag* | failed $!\n";
	};
};

exit 0;

sub ASN1_TIME2epoch($)
{
	my $asn1time = shift;

	# 2019-06-01T01:15:26Z
	my $isotime = Net::SSLeay::P_ASN1_TIME_get_isotime($asn1time);

	if ($isotime =~ /^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2})(\.\d+Z?)?/i)
	{
		my $res=undef;
		eval
		{
			$res=timegm($6,$5,$4,$3,$2-1,$1-1900);
		};
		if($@)
		{
			warn "1 ASN1_TIME2epoch $isotime $6,$5,$4,$3,$2-1,$1-1900 \n";
		};
		return $res;
	}
	else
	{
		warn "2 ASN1_TIME2epoch $isotime\n";
	};
	return undef;
}

# return color, expiredays, certinfo
sub get_cert_info($)
{
	my $cert = shift;

	if (!$cert)
	{
		return ('RED',undef,'');
	};

	my $certinfo='';

	$certinfo="Server certificate:\n";
	$certinfo.="\tsubject:".Net::SSLeay::X509_NAME_oneline(Net::SSLeay::X509_get_subject_name($cert))."\n";
	my @rvs = Net::SSLeay::X509_get_subjectAltNames($cert);
	if (scalar( @rvs)>1)
	{
		$certinfo.="\tSubject AltNames:\n";
	};
	while(scalar( @rvs)>1)
	{
		my $type = shift @rvs;
		my $name = shift @rvs;
		if ($type ==GEN_DNS)
		{
			$certinfo.="\t\t$name\n";
		};
	};
	$certinfo.="\tstart date: ".Net::SSLeay::P_ASN1_TIME_get_isotime(Net::SSLeay::X509_get_notBefore($cert))."\n";
	$certinfo.="\texpire date:".Net::SSLeay::P_ASN1_TIME_get_isotime(Net::SSLeay::X509_get_notAfter($cert))."\n";
	$certinfo.="\tkey size:".Net::SSLeay::EVP_PKEY_bits(Net::SSLeay::X509_get_pubkey($cert))."\n";
	$certinfo.="\tissuer:".Net::SSLeay::X509_NAME_oneline(Net::SSLeay::X509_get_issuer_name($cert))."\n";
	$certinfo.="\tsignature algorithm: ".Net::SSLeay::OBJ_obj2txt(Net::SSLeay::P_X509_get_signature_alg($cert))."\n";

	my $notafter= ASN1_TIME2epoch(Net::SSLeay::X509_get_notAfter($cert));
	if ($notafter)
	{
		my $now = time();
		my $expire = $notafter-time();
		my $expiredays = int($expire/ 86400);
		$certinfo.="\n";
		if ($expire>0)
		{
			$certinfo.="Server certificate expire in $expiredays days\n";
		}
		else
		{
			$certinfo.="Server certificate expired since $expiredays days\n";
		};
		# 30 pour dehidrated -7 -1
		if ($expiredays >22)
		{
			return ('GREEN',$expiredays,$certinfo);
		}
		elsif($expire>0)
		{
			return ('YELLOW',$expiredays,$certinfo);
		}
		else
		{
			return ('RED',$expiredays,$certinfo);
		};
	}
	else
	{
			return ('RED',-1,$certinfo);
	};

}

sub check_tls_proto($$$)
{
	my ($ip, $proto,$bbdispname)=@_;


	my $s;
	eval
	{
		alarm($timeout);

		$s = IO::Socket::SSL->new(
			PeerAddr => $bbdispname,
			PeerPort => $protocols{$proto}{'port'},
			Proto => 'tcp',
		);
		alarm(0);

		if (!$s)
		{
			die "IO::Socket::SSL->new $bbdispname $protocols{'port'} ".::Socket::SSL::errstr()."\n";
			#RED
			XymonExt::ReportToBB::ReportToBB($bbdispname,'tls_'.$proto,"red","IO::Socket::SSL->new $bbdispname $protocols{'port'} ".::Socket::SSL::errstr());
			return ;
		};
	};

	if ($@)
	{
		warn "$@";
		XymonExt::ReportToBB::ReportToBB($bbdispname,'tls_'.$proto,"red",$@);
		return ;
	};

	my $cert = $s->peer_certificate();

	my $certinfo='';
	my $expiredays=undef;
	my $certcolor;
	if ($cert)
	{
		($certcolor,$expiredays,$certinfo)=get_cert_info($cert);
		$certinfo.="Cipher used: ".$s->get_cipher()."\n";
		$certinfo.="TLS version: ".$s->get_sslversion()."\n";
	}
	else
	{
		XymonExt::ReportToBB::ReportToBB($bbdispname,'tls_'.$proto,"red",'Server cert not found');
		return ;
	};

my @traces;
	if( $protocols{$proto}{'banner'})
	{
		eval
		{
			alarm($timeout);
			push @traces,$s->getline();
			alarm(0);
		};

		if ($@)
		{
			warn "$@";
			XymonExt::ReportToBB::ReportToBB($bbdispname,'tls_'.$proto,"red",$@);
			return ;
		};
	};

	if( $protocols{$proto}{'send'})
	{
		eval
		{
			alarm($timeout);
			$s->printflush($protocols{$proto}{'send'});
			alarm(0);
		};

		if ($@)
		{
			warn "$@";
			XymonExt::ReportToBB::ReportToBB($bbdispname,'tls_'.$proto,"red",$@);
			return ;
		};
	};

	eval
	{
		alarm($timeout);
		push @traces, $s->getlines();
		alarm(0);
	};

	if ($@)
	{
		warn "$@";
		XymonExt::ReportToBB::ReportToBB($bbdispname,'tls_'.$proto,"red",$@);
		return ;
	};

	my $found=0;
	if ($protocols{$proto}{'expect'})
	{
		foreach my $line (@traces)
		{
			if ($line =~ /\Q$protocols{$proto}{'expect'}\E/)
			{
				$found = 1;
				last;
			};
		};
	};
	$s->close();

	my $traces = join("\n",@traces);
	if ($found)
	{
		XymonExt::ReportToBB::ReportToBB($bbdispname,'tls_'.$proto,"green",$traces);
	}
	else
	{
		XymonExt::ReportToBB::ReportToBB($bbdispname,'tls_'.$proto,"red",$traces);
	};
	XymonExt::ReportToBB::ReportToBB($bbdispname,'sslcert',$certcolor,$certinfo);

	return;
}
