c411e4e7363b71cd07c13a1695417d0d5051df2f
[mirror/dsa-nagios.git] / dsa-nagios-checks / checks / dsa-check-zone-rrsig-expiration-many
1 #!/usr/bin/perl
2
3 # Copyright (c) 2010,2012 Peter Palfrader <peter@palfrader.org>
4 #
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:
12 #
13 # The above copyright notice and this permission notice shall be
14 # included in all copies or substantial portions of the Software.
15 #
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.
23
24 use strict;
25 use warnings;
26 use threads;
27
28 use English;
29 use Getopt::Long;
30 use FindBin qw($Bin);
31 use YAML;
32 use File::Basename;
33
34 my $CHECK = $Bin.'/dsa-check-zone-rrsig-expiration';
35
36 $SIG{__DIE__} = sub {
37         print @_;
38         exit 3;
39 };
40
41 sub convert_time {
42         my $ticks = shift;
43         my $unit = shift;
44
45         unless (defined $unit) {
46                 my $newticks;
47                 ($newticks, $unit) = $ticks =~ m/^(\d*)([smhdw]?)$/;
48                 if (!defined $newticks) {
49                         print STDERR "Warning: invalid timestring to convert '$ticks'\n";
50                         return $ticks;
51                 }
52                 $ticks = $newticks;
53         }
54
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" }
61         return $ticks;
62 }
63
64 sub check_one {
65         $SIG{'KILL'} = sub { threads->exit(); };
66
67         my $zone = shift;
68         my $check = shift;
69         my $extra = shift;
70         my $params = shift;
71
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, $zone);
75         open(P, '-|', @cmd) or die ("Cannot run $CHECK for $zone\n");
76         my @p = <P>;
77         close P;
78         $p[0] = $zone.': '. $p[0] if (scalar @p > 0);
79
80         my $res = $CHILD_ERROR >> 8;
81
82         return ($res, \@p);
83 }
84
85 my $USAGE = "Usage: $PROGRAM_NAME [--help] | [--timeout=<nn>] [--warn=<nn>] [--critical=<nn>] [--geozonedir=<geodir>] <indir>\n";
86 my $params = { 'timeout' => 45, 'warn' => '14d', 'critical' => '7d' };
87 Getopt::Long::config('bundling');
88 GetOptions (
89         '--help' => \$params->{'help'},
90         '--timeout=i' => \$params->{'timeout'},
91         '--warn=s' => \$params->{'warn'},
92         '--critical=s' => \$params->{'critical'},
93         '--geozonedir=s' => \$params->{'geozonedir'},
94 ) or die ($USAGE);
95 if ($params->{'help'}) {
96         print $USAGE;
97         exit(0);
98 };
99 die ($USAGE) unless (scalar @ARGV == 1);
100 my $INDIR = shift;
101
102
103 my $count =
104         { 'ok' => [],
105           'warn' => [],
106           'critical' => [],
107           'unknown' => [],
108           'unsigned' => [],
109         };
110
111
112 my %dnsseczones;
113 # load list of classic zones that will do DNSSEC
114 chdir $INDIR or die "chdir $INDIR failed? $!\n";
115 opendir INDIR, '.' or die ("Cannot opendir $INDIR\n");
116 for my $file (sort {$a cmp $b} (readdir INDIR)) {
117         next if ( -l "$file" );
118         next unless ( -f "$file" );
119         next if $file =~ /^(dsset|keyset)-/;
120
121         my $do_dnssec = 0;
122         my $initial_refs = undef;
123         open(F, '<', $file) or die ("Cannot open $file: $!\n");
124         for (<F>) {
125                 if (/^; wzf:\s*dnssec\s*=\s*1\s*$/) { $do_dnssec = 1; }
126                 if (/^; check-initial-refs\s*=\s*(.*?)\s*$/) { $initial_refs = $1; }
127         };
128         close F;
129
130         if ($do_dnssec) {
131                 die "Duplicate zone $file?\n" if exists $dnsseczones{$file};
132                 $dnsseczones{$file} = {};
133                 $dnsseczones{$file}->{'initial_refs'} = $initial_refs if defined $initial_refs;
134         } else {
135                 push @{$count->{'unsigned'}}, $file;
136         };
137 }
138 closedir(INDIR);
139
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$/;
146
147                 open (F, '<', $file) or die "Cannot open $file: $!\n";
148                 my ($zc, undef, undef) = Load(join "", (<F>));
149                 close F;
150
151                 my $zone = basename($file, '.zone');
152
153                 if ($zc->{'dnssec'}) {
154                         die "Duplicate zone $zone?\n" if exists $dnsseczones{$zone};
155                         $dnsseczones{$zone} = {};
156                 } else {
157                         push @{$count->{'unsigned'}}, $zone;
158                 };
159         }
160         closedir(INDIR);
161 }
162
163 my @details;
164
165 my %threads;
166 for my $zone (sort {$a cmp $b} keys %dnsseczones) {
167         die "Duplicate zone $zone?\n"  if defined $threads{$zone};
168         my $thr = threads->create({'context' => 'list'},
169                                   \&check_one, $zone, $CHECK, $dnsseczones{$zone}, $params);
170         $threads{$zone} = $thr;
171 }
172
173 my $begin = time;
174 while (time - $begin <= $params->{timeout}) {
175         for my $zone (sort {$a cmp $b} keys %threads) {
176                 next unless $threads{$zone}->is_joinable();
177
178                 my ($res, $det) = $threads{$zone}->join();
179
180                 push @details, @$det;
181
182                 if ($res == 0) { push @{$count->{'ok'}}, $zone; }
183                 elsif ($res == 1) { push @{$count->{'warn'}}, $zone; }
184                 elsif ($res == 2) { push @{$count->{'critical'}}, $zone; }
185                 else { push @{$count->{'unknown'}}, $zone; };
186                 delete $threads{$zone};
187         }
188         sleep(1) unless scalar keys %threads == 0;
189 }
190 for my $zone (sort {$a cmp $b} keys %threads) {
191         push @{$count->{'warn'}}, $zone;
192         push @details, "$zone: timeout during check\n";
193         $threads{$zone}->kill('KILL')->detach();
194 }
195
196 for my $k (keys %$count) {
197         @{$count->{$k}} = sort {$a cmp $b} @{$count->{$k}};
198 }
199
200 my $exit;
201 my %state_mapping = (
202         'unknown' => 255,
203         'critical' => 2,
204         'warn' => 1,
205         'ok' => 0 );
206
207 for my $state (sort {$state_mapping{$b} <=> $state_mapping{$a}} keys %state_mapping) {
208         if (scalar @{$count->{$state}}) {
209                 printf "%s: %d", uc($state), scalar @{$count->{$state}};
210                 if ($state_mapping{$state} > 0) {
211                         print ": ", join(', ', @{$count->{$state}});
212                 };
213                 print "; ";
214                 $exit = $state_mapping{$state} unless defined $exit;
215         };
216 };
217 printf "unsigned: %d", scalar @{$count->{'unsigned'}};
218 print "\n";
219 print $_ for (@details);
220 exit $exit;