#!/usr/bin/perl # Copyright (c) 2010 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 English; use Net::DNS::Resolver; use Getopt::Long; $SIG{'__DIE__'} = sub { print @_; exit 4; }; my $RES = Net::DNS::Resolver->new; my $DLV = 'dlv.isc.org'; sub get_tag_generic { my $zone = shift; my $type = shift; my @result; my $pkt = $RES->send($zone, $type); return () unless $pkt; return () unless $pkt->answer; for my $rr ($pkt->answer) { next unless ($rr->type eq $type); next unless (lc($rr->name) eq lc($zone)); # only handle KSKs, i.e. keys with the SEP flag set next if ($type eq 'DNSKEY' && !($rr->is_sep)); push @result, $rr->keytag; }; my %unique = (); @result = sort {$a <=> $b} grep {!$unique{$_}++} @result; return @result }; sub get_dnskeytags { my $zone = shift; return get_tag_generic($zone, 'DNSKEY'); }; sub get_dstags { my $zone = shift; return get_tag_generic($zone, 'DS'); }; sub get_dlvtags { my $zone = shift; $zone .= ".".$DLV; return get_tag_generic($zone, 'DLV'); }; sub usage { my $fd = shift; my $exit = shift; print $fd "Usage: $PROGRAM_NAME overview|check-dlv|check-ds zone [zone...]\n"; print $fd " $PROGRAM_NAME --dir overview|check-dlv|check-ds\n"; print $fd " $PROGRAM_NAME --help\n"; exit $exit; } my $params; Getopt::Long::config('bundling'); GetOptions ( '--help' => \$params->{'help'}, '--dir=s' => \$params->{'dir'}, '--dlv=s' => \$params->{'dlv'}, ) or usage(\*STDERR, 1); usage(\*STDOUT, 0) if ($params->{'help'}); my $mode = shift @ARGV; my @zones; if (scalar @ARGV) { warn "--dir option ignored" if defined $params->{'dir'}; @zones = @ARGV; } else { my $dir = $params->{'dir'}; usage(\*STDOUT, 0) unless (defined $dir); chdir $dir or die "chdir $dir failed? $!\n"; opendir DIR, '.' or die ("Cannot opendir $dir\n"); for my $file (readdir DIR) { next if ( -l "$file" ); next unless ( -f "$file" ); next if $file =~ /^(dsset|keyset)-/; push @zones, $file; } closedir(DIR); }; usage(\*STDOUT, 0) unless (defined $mode && $mode =~ /^(overview|check-dlv|check-ds)$/); $DLV = $params->{'dlv'} if $params->{'dlv'}; my %data; for my $zone (@zones) { $data{$zone} = { 'dnskey' => join(', ', get_dnskeytags($zone)), 'ds' => join(', ', get_dstags($zone)), 'dlv' => join(', ', get_dlvtags($zone)) }; } if ($mode eq 'overview') { my $format = "%60s %-10s %-10s %-10s\n"; printf $format, "zone", "DNSKEY", "DS in parent", "DLV"; printf $format, "-"x 60, "-"x 10, "-"x 10, "-"x 10; for my $zone (sort {$a cmp $b} keys %data) { printf $format, $zone, $data{$zone}->{'dnskey'}, $data{$zone}->{'ds'}, $data{$zone}->{'dlv'}; } exit(0); } elsif ($mode eq 'check-dlv' || $mode eq 'check-ds') { my $key; $key = 'dlv' if $mode eq 'check-dlv'; $key = 'ds' if $mode eq 'check-ds'; die ("key undefined") unless $key; my @warn; my @ok; for my $zone (sort {$a cmp $b} keys %data) { my $dnskey = $data{$zone}->{'dnskey'} || '-'; my $target = $data{$zone}->{$key} || '-'; if ($dnskey ne $target) { push @warn, "$zone ($dnskey != $target)"; } else { push @ok, "$zone ($dnskey)"; }; } print "WARNING: ", join(", ", @warn), "\n" if (scalar @warn); print "OK: ", join(", ", @ok), "\n" if (scalar @ok); exit (1) if (scalar @warn); exit (0); } else { die ("Invalid mode '$mode'\n"); };