#! /usr/bin/perl -w

#    Copyright (C) 2011 Matthias Förste
#
#    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 3 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, see <http://www.gnu.org/licenses/>.
#
#    Matthias Förste <foerste@schlittermann.de>

=encoding utf8
=cut

use strict;
use warnings;

use File::Basename qw(basename);
use Getopt::Long;
use Net::DNS;

# for rrsig handling
require Net::DNS::SEC;
use Pod::Usage;

my $ME      = basename $0;
my $VERSION = "0.1";

my $opt = {
    'index-host' => '84.19.194.5',
    'index-zone' => 'idx.net.schlittermann.de',
    warning      => 72,
    critical     => 48
};

my $rv = {
    OK       => 0,
    WARNING  => 1,
    CRITICAL => 2,
    UNKNOWN  => 3
};

Getopt::Long::Configure('no_ignore_case_always');
GetOptions(
    "w|warning=i"    => \$opt->{warning},
    "c|critical=i"   => \$opt->{critical},
    "H|index-host=s" => \$opt->{'index-host'},
    "Z|index-zone=s" => \$opt->{'index-zone'},
    "h|help" => sub { pod2usage( -verbose => 1, -exitval => $rv->{OK} ) },
    "m|man" => sub { pod2usage( -verbose => 2, -exitval => $rv->{OK} ) },
    "V|version" => sub { version( $ME, $VERSION ); exit $rv->{OK}; }
) or pod2usage( -verbose => 1, -exitval => $rv->{CRITICAL} );

my $rc   = 'OK';
my $res  = Net::DNS::Resolver->new;
my $ires = Net::DNS::Resolver->new( nameservers => [ $opt->{'index-host'} ] );

my ( $r, @a );
unless ( defined( $r = $ires->query( $opt->{'index-zone'}, 'txt' ) )
    && ( @a = $r->answer ) )
{

    print "No zones found";
    exit $rv->{UNKNOWN};

}

my $now = time;
my $dates;
for (qw(critical warning)) {
    my @d = gmtime $now + $opt->{$_} * 3600;
    $dates->{$_} = sprintf "%04d" . "%02d" x 5, $d[5] + 1900, $d[4] + 1,
      reverse @d[ 0 .. 3 ];
}

my $counter;
@{$counter}{qw(critical warning ok total)} = (0) x 4;

for my $txt (@a) {

    next unless $txt->rdatastr =~ /^"ZONE::(.*)::sec-on"$/;
    my $z = $1;

    my ( $r, @a );
    unless ( defined( $r = $res->query( $z, 'rrsig' ) )
        && ( @a = $r->answer ) )
    {

        print "No RRSIGs found for zone '$z'";
        exit $rv->{UNKNOWN};

    }

    for my $rrsig (@a) {

        $counter->{total}++;
        my $e = $rrsig->sigexpiration;

        if ( $e < $dates->{critical} ) {

            $counter->{critical}++;
            $rc = 'CRITICAL';

        }
        elsif ( $e < $dates->{warning} ) {

            $counter->{warning}++;
            $rc = 'WARNING' unless $rv->{$rc} > $rv->{WARNING};

        }
        else {

            $counter->{ok}++;
            next;
        }

        warn "Signature for ", $rrsig->typecovered,
          " Record(s) in zone '$z' expires at ",
          ( join '.', reverse unpack 'A4A2A2', $e ), " ($e)\n";

    }

}

print "DNSSEC $rc: ", $counter->{critical}, " critical, ", $counter->{warning},
  " warning, ", $counter->{ok}, " ok, ", $counter->{total}, " total RRSIGS.\n";
exit $rv->{$rc};

sub version($$) {
    my ( $progname, $version ) = @_;

    print <<_VERSION;
$progname version $version
Copyright (C) 2011 by Matthias Förste and Schlittermann internet & unix support.

$ME comes with ABSOLUTELY NO WARRANTY. This is free software,
and you are welcome to redistribute it under certain conditions.
See the GNU General Public Licence for details.
_VERSION
}

__END__

=head1 NAME

check_dnssec - nagios plugin to warn about rrsig expiry

=head1 SYNOPSIS

check_dnssec [-H|--index-host string]
          [-Z|--index-zone string]
          [-w|--warning int]
          [-c|--critical int]
          [-h|--help]
          [-m|--man]
          [-V|--version]

=head1 OPTIONS

=over

=item B<-H>|B<--index-host> I<string>

Hostname or IP of a DNS Server knowing our index zone.

=item B<-Z>|B<--index-zone> I<string>

Name of a zone listing our DNSSEC enabled zone (and other records).

=item B<-w>|B<--warning> I<int>

Return with warning status if any RRSIG Record in any of the above zones
expires in less than that many hours.

=item B<-c>|B<--critical> I<int>

Return with critical status if any RRSIG Record in any of the above zones
expires in less than that many hours.

=item B<-h>|B<--help>

Print detailed help screen.

=item B<-m>|B<--man>

Print manual page.

=item B<-V>|B<--version>

Print version information.

=back

=head1 DESCRIPTION

This plugin reads a list of DNSSEC enabled zones from our index zone and checks
for expiry of any of the RRSIG Records.

=head1 AUTHOR

Written by Matthias Förste L<<foerste@schlittermann.de>>

=head1 COPYRIGHT

Copyright (C) 2011 by Matthias Förste and Schlittermann internet & unix
support. This is free software, and you are welcome to redistribute it under
certain conditions. See the GNU General Public Licence for details.

=cut
