#!/usr/bin/perl -w

# nagios check for debian security sync checks
#
#  Copyright (c) 2008 Alexander Wirt <formorer@debian.org>
#  Copyright (c) 2009, 2010, 2016 Peter Palfrader <peter@palfrader.org>
#
# This program 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 of the License, or
# (at your option) any later version.
#
# This program 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 program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
# USA

use LWP::UserAgent;
use Socket;
use strict;
use Date::Parse;
use Getopt::Long;
use Date::Parse;
use Date::Format;
use File::Basename;
use English;
use warnings;


sub usage($$) {
	my ($fh, $exit) = @_;
	my $basename = basename($PROGRAM_NAME);
	my $VERSION = '0.1';

	print $fh "$basename $VERSION\n";
	print $fh "Usage: $basename [--help|--version] [--verbose]\n";
	print $fh "\n";
	print $fh "  --help              Print this short help.\n";
	print $fh "  --version           Report version number.\n";
	print $fh "  --verbose           Be a little verbose.\n";
	print $fh "  --host              hostname to check.\n";
	print $fh "  --path              path to tracefile.\n";
	print $fh "  --unix              tracefile has a unix timestamp.\n";
	print $fh "  --ssl               use https.\n";
	print $fh "  --allow-skew=<foo>:<bar> if the newest timestamp is newer than <foo>secs\n";
	print $fh "                      then the mirror sync is still ok, assuming the oldest\n";
	print $fh "                      trace file is no older than <bar>\n";
	print $fh "\n";
	exit ($exit);
};

# Work around LWP not being able to verify service certs directly
my $ca_dir = '/etc/ssl/ca-debian';
$ENV{'PERL_LWP_SSL_CA_PATH'} = $ca_dir if -d $ca_dir;

$ENV{'PATH'} = '/bin:/sbin:/usr/bin:/usr/sbin';
delete @ENV{'IFS', 'CDPATH', 'ENV', 'BASH_ENV'};

my $params;

$params->{'host'} = 'security.debian.org'; #which host to check
$params->{'path'} = 'project/trace/security-master.debian.org'; 

my $OK = 0;
my $WARNING = 1;
my $CRITICAL = 2;
my $UNKNOWN = 3;

if (!GetOptions (
                '--help'                => \$params->{'help'},
                '--verbose'             => \$params->{'verbose'},
                '--version'             => \$params->{'version'},
                '--unix'                => \$params->{'unix'},
                '--ssl'                 => \$params->{'ssl'},
                '--host=s'              => \$params->{'host'},
                '--path=s'              => \$params->{'path'},
                '--allow-skew=s'        => \$params->{'skew'},
                )) {
                usage(*STDERR,1)
};
usage(*STDOUT,0) if ($params->{'help'});
usage(*STDERR,1) if (scalar @ARGV);

if (defined $params->{'skew'}) {
	if (not $params->{'skew'} =~ /^([0-9]+):([0-9]+)$/) {
		print STDERR "Invalid allow-skew format\n";
		usage(*STDERR,1);
	};
	$params->{'skew-new'} = $1;
	$params->{'skew-old'} = $2;
};

my $host = $params->{'host'};
my $path = $params->{'path'};
my @slaves;
my $status;
my @exitstatus;
my $exitcode = $OK;

@slaves = gethostbyname($params->{'host'})   or die "Can't resolve " . $params->{'host'} .": $!\n";
@slaves = map { inet_ntoa($_) } @slaves[4 .. $#slaves];
print "Checking the following hosts:\n" . join("\n", @slaves) . "\n" if $params->{'verbose'};

my @critical;
my $schema = $params->{'ssl'} ? 'https' : 'http';

foreach my $slave (@slaves) {
	my $ua = LWP::UserAgent->new;
	$ua->proxy('http', "$schema://$slave");
	print "Requesting $schema://$host/$path from $slave\n" if $params->{'verbose'};
	my $response = $ua->get("$schema://$host/$path");


	if ($response->is_success) {
		my $content = $response->content;  # or whatever
		my ($date, $foo, $bar) = split("\n", $content);
		my $synctime;

		if ($params->{'unix'}) {
			if ($date =~ /^([0-9]+)$/) {
				$synctime = $1;
			}
		} else {
			$synctime = str2time($date);
		}

		if (defined $synctime) {
			print "$slave last synced $synctime ", scalar(gmtime($synctime)), "\n" if $params->{'verbose'};
			$status->{$slave}->{'synced'} = $synctime;
		} else {
			$synctime = 0;
			$exitcode = $UNKNOWN;
			push @exitstatus, "Cannot parse tracefile on $slave";
		}
	} else {
		push @exitstatus, "$slave broken: " . $response->status_line; 
		$status->{$slave}->{'error'} = $response->status_line;
		$status->{$slave}->{'synced'} = 0;
		$exitcode = $CRITICAL;
		push @critical, $slave;
	}
}


my %seen;
my $o_sync = scalar(grep !$seen{$_}++, map{$status->{$_}->{'synced'}} keys(%{$status}));
if ($o_sync > 1) {
	my @mirrors =  sort { $status->{$a}->{'synced'} <=> $status->{$b}->{'synced'}  } keys %{$status};
	my @not_most_recent = grep { $status->{$_}->{'synced'} <=> $status->{$mirrors[-1]}->{'synced'} } @mirrors;
	$o_sync = scalar @not_most_recent;

	my $newest = time - $status->{$mirrors[-1]}->{'synced'};
	my $oldest = time - $status->{$mirrors[0]}->{'synced'};
	my $skew_ok = ( defined $params->{'skew-new'} &&
	                defined $params->{'skew-old'} &&
	                $newest <= $params->{'skew-new'} &&
	                $oldest <= $params->{'skew-old'});

	my $msg;
	if ($skew_ok) {
		$exitcode = $OK;
		$msg = "$o_sync mirror(s) not in sync (from oldest to newest), but still within acceptable skew: ";
	} else {
		$exitcode = $CRITICAL;
		$msg = "$o_sync mirror(s) not in sync (from oldest to newest): ";
	};
	push @exitstatus, $msg . join(", ", @not_most_recent);
} else {
	print "All mirrors on same sync timestamp\n" if $params->{'verbose'};
}

if ($exitcode == $CRITICAL) {
	print "CRITICAL: " . join(', ',@exitstatus) . "\n";
} elsif ($exitcode == $OK) {
	if (scalar @exitstatus > 0) {
		print "OK: " . join(', ',@exitstatus) . "\n";
	} else {
		print "OK: all mirrors up2date\n";
	}
} else {
	print "UNKNOWN: ", join(', ',@exitstatus) . "\n";
}

foreach my $mirror (keys(%{$status})) {
	if ($status->{$mirror}->{'error'}) {
		print "$mirror broken: " . $status->{$mirror}->{'error'} . "\n";
	} else {
		print "$mirror last synced: " . localtime($status->{$mirror}->{'synced'}) ."\n";
	}
}

exit $exitcode;
