#!/usr/bin/perl

# $Id: 

# check_full_zone_rrsig_expiration

# Nagios plugin to check the remaining validatability time of all RRSIG
# records in a given zone.

# Copyright (c) 2010, Internet Corporation for Assigned Names and Numbers
# 
# Permission to use, copy, modify, and/or distribute this software for any
# purpose with or without fee is hereby granted, provided that the above
# copyright notice and this permission notice appear in all copies.
#
# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.

# 2010 Dave Knight <dave@knig.ht>

# Notes:
#
# Calls out to dig to do the zone transfer as there is currently only
# support for TSIG with HMAC-MD5 in Net::DNS


use warnings;
use strict;
use English qw(-no_match_vars);
use Getopt::Std;
use Date::Calc qw( Delta_DHMS Mktime );


my $VERSION = '0.0.1';
$Getopt::Std::STANDARD_HELP_VERSION = 1;

my $DIG = 'dig';

my $DAYS_WARN = 2;
my $DAYS_CRIT = 1;
my $TTLS_WARN = 2;
my $TTLS_CRIT = 1;


my %opts;
getopts('Z:M:T:C:W:c:w:h', \%opts);

if(! $opts{Z} || ! $opts{M} || $opts{h}) {
    HELP_MESSAGE();
    exit(4);
}

my $ZONE = $opts{Z};
my $MASTER = $opts{M};
my $TSIG = $opts{T} ? "-y " . $opts{T} : "";

$DAYS_WARN = $opts{W} if($opts{W});
$DAYS_CRIT = $opts{C} if($opts{C});
$TTLS_WARN = $opts{w} if($opts{w});
$TTLS_CRIT = $opts{c} if($opts{c});


my @axfr = do_axfr($ZONE,$MASTER,$TSIG);
my ($ok,$timewarn,$timecrit,$ttlswarn,$ttlscrit) = do_analyse(@axfr);

if($timecrit || $ttlscrit) {
    print "CRITICAL, "
        . ($timecrit ? "$timecrit RRSIGs with less than $DAYS_CRIT days remaining. " : "")
        . ($ttlscrit ? "$ttlscrit RRSIGs with less than $TTLS_CRIT TTLs remaining. " : "")
        . "\n";
    exit(2);
}
elsif($timewarn || $ttlswarn) {
    print "WARNING, "
        . ($timewarn ? "$timewarn RRSIGs with less than $DAYS_WARN days remaining. " : "")
        . ($ttlswarn ? "$ttlswarn RRSIGs with less than $TTLS_WARN TTLs remaining. " : "")
        . "\n";
    exit(1);
}
else {
    print "OK, $ok RRSIGs with more than $DAYS_WARN days remaining and more than $TTLS_WARN TTLs remaining.\n";
    exit(0);
}


sub HELP_MESSAGE {
    print "$0 -Z <zone> -M <master> [-T [hmac:]name:key] "
        . "[-c <TTLs> ($TTLS_CRIT)] [-w <TTLs> ($TTLS_WARN)] [-C <days> ($DAYS_CRIT)] [-W <days> ($DAYS_WARN)]\n";
}

sub VERSION_MESSAGE {
    print $VERSION, "\n";
}

sub do_axfr {
    my ($zone,$master,$tsig) = @_;
    my $command = "$DIG $tsig +noall +answer \@$master $zone AXFR";
    my @axfr = `$command`;
    my $first = shift @axfr;
    my $last = pop @axfr;
    if( $first ne $last || $first !~ /IN\tSOA/ ) { 
        print "ERROR, failed to transfer zone with \"$command\"\n";
        exit 3;
    }
    return @axfr;
}

sub do_analyse {
    my @axfr = @_;
    my ($ok,$timewarn,$timecrit,$ttlswarn,$ttlscrit) = (0,0,0,0,0);

    foreach(@axfr) {
        if(/^(.*?)\s+(\d+)\s+IN\s+RRSIG\s+(.*?)\s\d\s\d\s\d+\s(\d+)\s(\d+)\s/) {

            my ($owner,$ttl,$rrtype,$expiry,$inception) = ($1,$2,$3,$4,$5);

            $expiry =~ /^(\d{4})(\d{2})(\d{2})(\d{2})(\d{2})(\d{2})$/;
            my @exp = ($1,$2,$3,$4,$5,$6);
            my $exp_time = Mktime(@exp);

            $inception =~ /^(\d{4})(\d{2})(\d{2})(\d{2})(\d{2})(\d{2})$/;
            my @inc = ($1,$2,$3,$4,$5,$6);
            my $inc_time = Mktime(@inc);

            my @now = (localtime(time))[5,4,3,2,1,0];
            $now[0] = $now[0] + 1900;
            $now[1] = $now[1] + 1;
            my $now_time = time;

            my ($TDd,$TDh,$TDm,$TDs) = Delta_DHMS(@inc,@exp); # Total
            my ($RDd,$RDh,$RDm,$RDs) = Delta_DHMS(@now,@exp); # Remaining
            my $val_time = $exp_time - $inc_time;
            my $rem_time = $exp_time - $now_time;

            if($RDd < $DAYS_CRIT || $rem_time < ($ttl * $TTLS_CRIT)) {
                $timecrit++ if($RDd < $DAYS_CRIT);
                $ttlscrit++ if($rem_time < ($ttl * $TTLS_CRIT));
            }
            elsif($RDd < $DAYS_WARN || $rem_time < ($ttl * $TTLS_WARN)) {
                $timewarn++ if($RDd < $DAYS_WARN);
                $ttlswarn++ if($rem_time < ($ttl * $TTLS_WARN));
            }
            else {
                $ok++;
            }
        }
    }
    return ($ok,$timewarn,$timecrit,$ttlswarn,$ttlscrit);
}
