#!/usr/bin/perl # Copyright (c) 2010,2012 Peter Palfrader # # Permission is hereby granted, free of charge, to any person obtaining # a copy of this software and associated documentation files (the # "Software"), to deal in the Software without restriction, including # without limitation the rights to use, copy, modify, merge, publish, # distribute, sublicense, and/or sell copies of the Software, and to # permit persons to whom the Software is furnished to do so, subject to # the following conditions: # # The above copyright notice and this permission notice shall be # included in all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. use strict; use warnings; use threads; use English; use Getopt::Long; use FindBin qw($Bin); use YAML; use File::Basename; my $CHECK = $Bin.'/dsa-check-zone-rrsig-expiration'; $SIG{__DIE__} = sub { print @_; exit 3; }; sub convert_time { my $ticks = shift; my $unit = shift; unless (defined $unit) { my $newticks; ($newticks, $unit) = $ticks =~ m/^(\d*)([smhdw]?)$/; if (!defined $newticks) { print STDERR "Warning: invalid timestring to convert '$ticks'\n"; return $ticks; } $ticks = $newticks; } if ($unit eq 's' || $unit eq '') { } elsif ($unit eq 'm') { $ticks *= 60; } elsif ($unit eq 'h') { $ticks *= 60*60; } elsif ($unit eq 'd') { $ticks *= 60*60*24; } elsif ($unit eq 'w') { $ticks *= 60*60*24*7; } else { print STDERR "Warning: invalid unit '$unit'\n" } return $ticks; } sub check_one { $SIG{'KILL'} = sub { threads->exit(); }; my $zone = shift; my $check = shift; my $extra = shift; my $params = shift; my @cmd = ($check, '-w', $params->{'warn'}, '-c', $params->{'critical'}); push(@cmd, '-r', $extra->{'initial_refs'}) if exists $extra->{'initial_refs'}; push(@cmd, '-d') if $params->{'debug'}; push(@cmd, $zone); open(P, '-|', @cmd) or die ("Cannot run $CHECK for $zone\n"); my @p =

; close P; $p[0] = $zone.': '. $p[0] if (scalar @p > 0); my $res = $CHILD_ERROR >> 8; return ($res, \@p); } my $USAGE = "Usage: $PROGRAM_NAME [--help] | [--debug] [--timeout=] [--warn=] [--critical=] [--geozonedir=] \n"; my $params = { 'timeout' => 45, 'warn' => '14d', 'critical' => '7d' }; Getopt::Long::config('bundling'); GetOptions ( '--help' => \$params->{'help'}, '--timeout=i' => \$params->{'timeout'}, '--warn=s' => \$params->{'warn'}, '--debug' => \$params->{'debug'}, '--critical=s' => \$params->{'critical'}, '--geozonedir=s' => \$params->{'geozonedir'}, ) or die ($USAGE); if ($params->{'help'}) { print $USAGE; exit(0); }; die ($USAGE) unless (scalar @ARGV == 1); my $INDIR = shift; my $states = [qw{critical warn unknown ok unsigned}]; my $count = { map { $_ => [] } @$states }; my $details = { map { $_ => [] } @$states }; my %dnsseczones; # load list of classic zones that will do DNSSEC chdir $INDIR or die "chdir $INDIR failed? $!\n"; opendir INDIR, '.' or die ("Cannot opendir $INDIR\n"); for my $file (sort {$a cmp $b} (readdir INDIR)) { next if ( -l "$file" ); next unless ( -f "$file" ); my $do_dnssec = 1; my $delegated = 1; my $initial_refs = undef; open(F, '<', $file) or die ("Cannot open $file: $!\n"); for () { if (/^; wzf:\s*dnssec\s*=\s*0\s*$/) { $do_dnssec = 0; } if (/^; delegated\s*=\s*no\s*$/) { $delegated = 0; } if (/^; check-initial-refs\s*=\s*(.*?)\s*$/) { $initial_refs = $1; } }; close F; if ($do_dnssec && $delegated) { die "Duplicate zone $file?\n" if exists $dnsseczones{$file}; $dnsseczones{$file} = {}; $dnsseczones{$file}->{'initial_refs'} = $initial_refs if defined $initial_refs; } else { push @{$count ->{'unsigned'}}, $file; push @{$details->{'unsigned'}}, "$file: marked unsigned or undelegated.\n"; }; } closedir(INDIR); # load list of geodns zones that will do DNSSEC if (defined $params->{'geozonedir'}) { chdir $params->{'geozonedir'} or die "chdir $params->{'geozonedir'} failed? $!\n"; opendir INDIR, '.' or die ("Cannot opendir $params->{'geozonedir'}\n"); for my $file (sort {$a cmp $b} (readdir INDIR)) { next unless $file =~ /\.zone$/; my $zone = basename($file, '.zone'); die "Duplicate zone $zone?\n" if exists $dnsseczones{$zone}; $dnsseczones{$zone} = {}; } closedir(INDIR); } my %threads; for my $zone (sort {$a cmp $b} keys %dnsseczones) { die "Duplicate zone $zone?\n" if defined $threads{$zone}; my $thr = threads->create({'context' => 'list'}, \&check_one, $zone, $CHECK, $dnsseczones{$zone}, $params); $threads{$zone} = $thr; } my $begin = time; while (time - $begin <= $params->{timeout}) { for my $zone (sort {$a cmp $b} keys %threads) { next unless $threads{$zone}->is_joinable(); my ($res, $det) = $threads{$zone}->join(); my $type = ($res == 0) ? 'ok' : ($res == 1) ? 'warn' : ($res == 2) ? 'critical' : 'unknown'; push @{$details->{$type}}, @$det; push @{$count ->{$type}}, $zone; delete $threads{$zone}; } last if scalar keys %threads == 0; print STDERR (scalar keys %threads), " threads left: ", join(" ", keys %threads), "\n" if $params->{'debug'}; sleep 1; } for my $zone (sort {$a cmp $b} keys %threads) { push @{$count ->{'warn'}}, $zone; push @{$details->{'warn'}}, "$zone: timeout during check\n"; $threads{$zone}->kill('KILL')->detach(); } my $exit = 0; my %state_mapping = ( 'unknown' => 255, 'critical' => 2, 'warn' => 1, 'unsigned' => 0, 'ok' => 0 ); for my $state (@$states) { @{$count->{$state}} = sort {$a cmp $b} @{$count->{$state}}; @{$details->{$state}} = sort {$a cmp $b} @{$details->{$state}}; if (scalar @{$count->{$state}}) { printf "%s: %d", uc($state), scalar @{$count->{$state}}; if ($state_mapping{$state} > 0) { print ": ", join(', ', @{$count->{$state}}); }; print "; "; $exit = $state_mapping{$state} if ($state_mapping{$state} > $exit); }; }; printf "unsigned: %d", scalar @{$count->{'unsigned'}}; print "\n"; for my $state (@$states) { for (@{$details->{$state}}) { s/\|/;/g; print $_; } } exit $exit;