3 # Copyright (c) 2010,2012 Peter Palfrader <peter@palfrader.org>
5 # Permission is hereby granted, free of charge, to any person obtaining
6 # a copy of this software and associated documentation files (the
7 # "Software"), to deal in the Software without restriction, including
8 # without limitation the rights to use, copy, modify, merge, publish,
9 # distribute, sublicense, and/or sell copies of the Software, and to
10 # permit persons to whom the Software is furnished to do so, subject to
11 # the following conditions:
13 # The above copyright notice and this permission notice shall be
14 # included in all copies or substantial portions of the Software.
16 # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17 # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18 # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19 # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20 # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21 # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22 # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
34 my $CHECK = $Bin.'/dsa-check-zone-rrsig-expiration';
45 unless (defined $unit) {
47 ($newticks, $unit) = $ticks =~ m/^(\d*)([smhdw]?)$/;
48 if (!defined $newticks) {
49 print STDERR "Warning: invalid timestring to convert '$ticks'\n";
55 if ($unit eq 's' || $unit eq '') { }
56 elsif ($unit eq 'm') { $ticks *= 60; }
57 elsif ($unit eq 'h') { $ticks *= 60*60; }
58 elsif ($unit eq 'd') { $ticks *= 60*60*24; }
59 elsif ($unit eq 'w') { $ticks *= 60*60*24*7; }
60 else { print STDERR "Warning: invalid unit '$unit'\n" }
65 $SIG{'KILL'} = sub { threads->exit(); };
72 my @cmd = ($check, '-w', $params->{'warn'}, '-c', $params->{'critical'});
73 push(@cmd, '-r', $extra->{'initial_refs'}) if exists $extra->{'initial_refs'};
74 push(@cmd, '-d') if $params->{'debug'};
76 open(P, '-|', @cmd) or die ("Cannot run $CHECK for $zone\n");
79 $p[0] = $zone.': '. $p[0] if (scalar @p > 0);
81 my $res = $CHILD_ERROR >> 8;
86 my $USAGE = "Usage: $PROGRAM_NAME [--help] | [--debug] [--timeout=<nn>] [--warn=<nn>] [--critical=<nn>] [--geozonedir=<geodir>] <indir>\n";
87 my $params = { 'timeout' => 45, 'warn' => '14d', 'critical' => '7d' };
88 Getopt::Long::config('bundling');
90 '--help' => \$params->{'help'},
91 '--timeout=i' => \$params->{'timeout'},
92 '--warn=s' => \$params->{'warn'},
93 '--debug' => \$params->{'debug'},
94 '--critical=s' => \$params->{'critical'},
95 '--geozonedir=s' => \$params->{'geozonedir'},
97 if ($params->{'help'}) {
101 die ($USAGE) unless (scalar @ARGV == 1);
105 my $states = [qw{critical warn unknown ok unsigned}];
106 my $count = { map { $_ => [] } @$states };
107 my $details = { map { $_ => [] } @$states };
111 # load list of classic zones that will do DNSSEC
112 chdir $INDIR or die "chdir $INDIR failed? $!\n";
113 opendir INDIR, '.' or die ("Cannot opendir $INDIR\n");
114 for my $file (sort {$a cmp $b} (readdir INDIR)) {
115 next if ( -l "$file" );
116 next unless ( -f "$file" );
120 my $initial_refs = undef;
121 open(F, '<', $file) or die ("Cannot open $file: $!\n");
123 if (/^; wzf:\s*dnssec\s*=\s*0\s*$/) { $do_dnssec = 0; }
124 if (/^; delegated\s*=\s*no\s*$/) { $delegated = 0; }
125 if (/^; check-initial-refs\s*=\s*(.*?)\s*$/) { $initial_refs = $1; }
129 if ($do_dnssec && $delegated) {
130 die "Duplicate zone $file?\n" if exists $dnsseczones{$file};
131 $dnsseczones{$file} = {};
132 $dnsseczones{$file}->{'initial_refs'} = $initial_refs if defined $initial_refs;
134 push @{$count ->{'unsigned'}}, $file;
135 push @{$details->{'unsigned'}}, "$file: marked unsigned or undelegated.\n";
140 # load list of geodns zones that will do DNSSEC
141 if (defined $params->{'geozonedir'}) {
142 chdir $params->{'geozonedir'} or die "chdir $params->{'geozonedir'} failed? $!\n";
143 opendir INDIR, '.' or die ("Cannot opendir $params->{'geozonedir'}\n");
144 for my $file (sort {$a cmp $b} (readdir INDIR)) {
145 next unless $file =~ /\.zone$/;
147 my $zone = basename($file, '.zone');
148 die "Duplicate zone $zone?\n" if exists $dnsseczones{$zone};
149 $dnsseczones{$zone} = {};
155 for my $zone (sort {$a cmp $b} keys %dnsseczones) {
156 die "Duplicate zone $zone?\n" if defined $threads{$zone};
157 my $thr = threads->create({'context' => 'list'},
158 \&check_one, $zone, $CHECK, $dnsseczones{$zone}, $params);
159 $threads{$zone} = $thr;
163 while (time - $begin <= $params->{timeout}) {
164 for my $zone (sort {$a cmp $b} keys %threads) {
165 next unless $threads{$zone}->is_joinable();
167 my ($res, $det) = $threads{$zone}->join();
169 my $type = ($res == 0) ? 'ok' :
170 ($res == 1) ? 'warn' :
171 ($res == 2) ? 'critical' :
174 push @{$details->{$type}}, @$det;
175 push @{$count ->{$type}}, $zone;
176 delete $threads{$zone};
178 last if scalar keys %threads == 0;
179 print STDERR (scalar keys %threads), " threads left: ", join(" ", keys %threads), "\n" if $params->{'debug'};
182 for my $zone (sort {$a cmp $b} keys %threads) {
183 push @{$count ->{'warn'}}, $zone;
184 push @{$details->{'warn'}}, "$zone: timeout during check\n";
185 $threads{$zone}->kill('KILL')->detach();
189 my %state_mapping = (
196 for my $state (@$states) {
197 @{$count->{$state}} = sort {$a cmp $b} @{$count->{$state}};
198 @{$details->{$state}} = sort {$a cmp $b} @{$details->{$state}};
200 if (scalar @{$count->{$state}}) {
201 printf "%s: %d", uc($state), scalar @{$count->{$state}};
202 if ($state_mapping{$state} > 0) {
203 print ": ", join(', ', @{$count->{$state}});
206 $exit = $state_mapping{$state} if ($state_mapping{$state} > $exit);
209 printf "unsigned: %d", scalar @{$count->{'unsigned'}};
211 for my $state (@$states) {
212 for (@{$details->{$state}}) {