1 #! /usr/bin/perl |
1 #! /usr/bin/perl |
2 use 5.014; |
2 use 5.014; |
3 use strict; |
3 use strict; |
4 use warnings; |
4 use warnings; |
5 use GetOpt:::Long; |
5 use Getopt::Long qw(GetOptionsFromArray); |
6 use Net::DNS; |
6 use Net::DNS; |
|
7 use Pod::Usage; |
|
8 |
|
9 |
|
10 my %resolver; |
|
11 sub uniq { my %h; @h{@_} = (); return keys %h; } |
7 |
12 |
8 sub get_domains { |
13 sub get_domains { |
9 return qw(schlittermann.de); |
14 my @sources = @_; |
|
15 my @domains = (); |
|
16 |
|
17 foreach my $src (@sources) { |
|
18 |
|
19 if ($src =~ m{^(?:(/.*)|file://(/.*))}) { |
|
20 open(my $f, '<', $1) or die "$0: Can't open $1 for reading: $!\n"; |
|
21 push @domains, map { /^\s*(\S+)\s*/ } <$f>; |
|
22 next; |
|
23 } |
|
24 |
|
25 push @domains, $src; |
|
26 } |
|
27 |
|
28 return @domains; |
10 } |
29 } |
11 |
30 |
12 # query the serial from |
31 # return a list of "official" nameservers |
|
32 sub get_ns { |
|
33 my ($nameserver) = map { /^\@(.*)/ } $_[0] =~ /^\@/ ? shift : '@8.8.8.8'; |
|
34 my ($domain) = @_; |
|
35 my @ns; |
|
36 |
|
37 my $r = $resolver{$nameserver} //= Net::DNS::Resolver->new(nameservers => [$nameserver]); |
|
38 my $q = $r->query($domain, 'NS') or die $r->errorstring, "\n"; |
|
39 push @ns, map { $_->nsdname } grep { $_->type eq 'NS' } $q->answer; |
|
40 |
|
41 return sort @ns; |
|
42 } |
|
43 |
|
44 sub get_serial { |
|
45 my ($nameserver) = map { /^\@(.*)/ } $_[0] =~ /^\@/ ? shift : '@8.8.8.8'; |
|
46 my ($domain) = shift; |
|
47 |
|
48 my $r = $resolver{$nameserver} //= |
|
49 Net::DNS::Resolver->new(nameservers => [$nameserver]); |
|
50 my $q = $r->query($domain, 'SOA') or die $r->errorstring, "\n"; |
|
51 return (map { $_->serial } grep { $_->type eq 'SOA' } $q->answer)[0]; |
|
52 } |
|
53 |
13 # - the nameservers known from the ns records |
54 # - the nameservers known from the ns records |
14 # - from the primary master if this is not one of the |
55 # - from the primary master if this is not one of the |
15 # NS for the zone |
56 # NS for the zone |
16 # - from a list of additional (hidden) servers |
57 # - from a list of additional (hidden) servers |
17 # |
58 # |
18 # OK - if the serial numbers are in sync |
59 # OK - if the serial numbers are in sync |
19 # WARNING - if there is some difference |
60 # WARNING - if there is some difference |
20 # CRITICAL - if the serial cannot be found at one of the sources |
61 # CRITICAL - if the serial cannot be found at one of the sources |
|
62 |
|
63 sub ns_ok { |
|
64 my ($reference, $domain) = @_; |
|
65 |
|
66 my @our = sort eval { get_ns($reference, $domain) }; |
|
67 my @their = sort +get_ns($domain); |
|
68 |
|
69 { |
|
70 local $" = "\0"; |
|
71 return 1 if "@our" eq "@their"; |
|
72 } |
|
73 |
|
74 local $" = ', '; |
|
75 die "NS differ (our @our) vs (their @their)\n"; |
|
76 }; |
|
77 |
|
78 sub main { |
|
79 my @argv = @_; |
|
80 my $opt_reference = '212.80.235.130'; |
|
81 my $opt_progress = -t; |
|
82 |
|
83 GetOptionsFromArray(\@argv, |
|
84 'reference=s' => \$opt_reference) |
|
85 and @argv or pod2usage; |
|
86 my @domains = get_domains(@argv); |
|
87 |
|
88 my (@OK, %CRITICAL); |
|
89 foreach my $domain (@domains) { |
|
90 print STDERR "$domain " if $opt_progress; |
|
91 eval { ns_ok('@212.80.235.130', $domain) }; |
|
92 if ($@) { $CRITICAL{$domain} = $@ } |
|
93 else { push @OK, $domain } |
|
94 say STDERR $@ ? 'not ok' : 'ok' if $opt_progress; |
|
95 } |
|
96 |
|
97 # use DDP; |
|
98 # p @OK; |
|
99 # p %CRITICAL; |
|
100 |
|
101 if (my $n = keys %CRITICAL) { |
|
102 print "CRITICAL: $n of ".@domains." domains\n", map { "$_: $CRITICAL{$_}" } sort keys %CRITICAL; |
|
103 return 2; |
|
104 } |
|
105 |
|
106 say 'OK: ' . @OK . ' domains checked'; |
|
107 return 0; |
|
108 |
|
109 } |
|
110 |
|
111 exit main @ARGV unless caller; |
|
112 |
|
113 __END__ |
|
114 |
|
115 =head1 NAME |
|
116 |
|
117 check_dns-serial - check the dns serial number from multiple sources |
|
118 |
|
119 =head1 SYNOPSIS |
|
120 |
|
121 check_dns-serial [options] DOMAINS |
|
122 |
|
123 =head1 OPTIONS |
|
124 |
|
125 =over |
|
126 |
|
127 =item B<--reference>=I<address> |
|
128 |
|
129 The address of the reference server for our own domains (default: 212.80.235.130) |
|
130 |
|
131 =item B<--progress> |
|
132 |
|
133 Tell about the progress. (default: on if input is connected to a terminal) |
|
134 |
|
135 =back |
|
136 |
|
137 =cut |