plugins/check_dns-serial
changeset 4 ebada07bb701
parent 3 9ce7eb4a39c3
child 5 2e27cfdf85ea
equal deleted inserted replaced
3:9ce7eb4a39c3 4:ebada07bb701
     1 #! /usr/bin/perl
     1 #! /usr/bin/perl
     2 # © 2014 Heiko Schlittermann <hs@schlittermann.de>
     2 # © 2014 Heiko Schlittermann <hs@schlittermann.de>
       
     3 
     3 =head1 NAME
     4 =head1 NAME
     4 
     5 
     5  check_dns-serial - check the dns serial number from multiple sources
     6  check_dns-serial - check the dns serial number from multiple sources
     6 
     7 
     7 =head1 SYNOPSIS
     8 =head1 SYNOPSIS
    12 
    13 
    13 B<check_dns-serial> is designed as a Icinga/Nagios plugin to verify that
    14 B<check_dns-serial> is designed as a Icinga/Nagios plugin to verify that
    14 all responsible NS have the same serial number for their zones.
    15 all responsible NS have the same serial number for their zones.
    15 
    16 
    16 Domains we are not responsible for are marked as B<critical>.
    17 Domains we are not responsible for are marked as B<critical>.
       
    18 
       
    19 The list of domains may consist of the following items:
       
    20 
       
    21 =over
       
    22 
       
    23 =item I<domain>
       
    24 
       
    25 A plain domain name.
       
    26 
       
    27 =item B<file://>I<file>
       
    28 
       
    29 A file name containing the domains, line by line.
       
    30 
       
    31 =item B<local:>
       
    32 
       
    33 This item uses the output of C<named-checkconf -p> to get the list of
       
    34 master/slave zones. The 127.in-addr.arpa, 168.192.in-addr.arpa, and
       
    35 0.in-addr.arpa, and 127.in-addr.arpa zones are suppressed.
       
    36 
       
    37 =back
    17 
    38 
    18 =cut
    39 =cut
    19 
    40 
    20 use 5.014;
    41 use 5.014;
    21 use strict;
    42 use strict;
    25 use Pod::Usage;
    46 use Pod::Usage;
    26 
    47 
    27 my %resolver;
    48 my %resolver;
    28 sub uniq { my %h; @h{@_} = (); return keys %h; }
    49 sub uniq { my %h; @h{@_} = (); return keys %h; }
    29 
    50 
       
    51 # return a list of the zones known to the local
       
    52 # bind
       
    53 sub get_local_zones {
       
    54     my @conf;
       
    55     open(my $z, '-|', 'named-checkconf -p');
       
    56     while (<$z>) {
       
    57         state $line;
       
    58         s/^\s*(.*?)\s*$/$1 /;
       
    59         chomp($line .= $_);    # continuation line
       
    60         if (/\A\}/) {          # config item done
       
    61             $line =~ s/\s$//;
       
    62             push @conf, $line;
       
    63             $line = '';
       
    64         }
       
    65     }
       
    66     return grep { 
       
    67 	# FIXME: 172.0 .. 172.31 is missing
       
    68 	not /^\b(?:0|127|10|168\.192|255)\.in-addr\.arpa$/ and
       
    69 	not /^localhost$/;
       
    70     } map { /zone\s"(\S+)"\s/ } grep { /type (?:master|slave)/ } @conf;
       
    71 }
       
    72 
    30 sub get_domains {
    73 sub get_domains {
    31     my @sources = @_;
    74     my @sources = @_;
    32     my @domains = ();
    75     my @domains = ();
    33 
    76 
    34     foreach my $src (@sources) {
    77     foreach my $src (@sources) {
    35 
    78 
    36         if ($src =~ m{^(?:(/.*)|file://(/.*))}) {
    79         if ($src =~ m{^(?:(/.*)|file://(/.*))}) {
    37             open(my $f, '<', $1) or die "$0: Can't open $1 for reading: $!\n";
    80             open(my $f, '<', $1) or die "$0: Can't open $1 for reading: $!\n";
    38             push @domains, map { /^\s*(\S+)\s*/ } <$f>;
    81             push @domains, map { /^\s*(\S+)\s*/ } <$f>;
       
    82             next;
       
    83         }
       
    84 
       
    85         if ($src =~ m{^local:}) {
       
    86             push @domains, get_local_zones;
    39             next;
    87             next;
    40         }
    88         }
    41 
    89 
    42         push @domains, $src;
    90         push @domains, $src;
    43     }
    91     }
    51     my ($domain) = @_;
    99     my ($domain) = @_;
    52     my @ns;
   100     my @ns;
    53 
   101 
    54     my $r = $resolver{$nameserver} //=
   102     my $r = $resolver{$nameserver} //=
    55       Net::DNS::Resolver->new(nameservers => [$nameserver]);
   103       Net::DNS::Resolver->new(nameservers => [$nameserver]);
    56     my $q = $r->query($domain, 'NS') or die $r->errorstring, "\n";
   104     my $q = $r->query($domain, 'NS') or die $r->errorstring, "\@$nameserver\n";
    57     push @ns, map { $_->nsdname } grep { $_->type eq 'NS' } $q->answer;
   105     push @ns, map { $_->nsdname } grep { $_->type eq 'NS' } $q->answer;
    58 
   106 
    59     return sort @ns;
   107     return sort @ns;
    60 }
   108 }
    61 
   109 
    63     my ($nameserver) = map { /^\@(.*)/ } $_[0] =~ /^\@/ ? shift : '@8.8.8.8';
   111     my ($nameserver) = map { /^\@(.*)/ } $_[0] =~ /^\@/ ? shift : '@8.8.8.8';
    64     my ($domain) = shift;
   112     my ($domain) = shift;
    65 
   113 
    66     my $r = $resolver{$nameserver} //=
   114     my $r = $resolver{$nameserver} //=
    67       Net::DNS::Resolver->new(nameservers => [$nameserver]);
   115       Net::DNS::Resolver->new(nameservers => [$nameserver]);
    68     my $q = $r->query($domain, 'SOA') or die $r->errorstring, "\n";
   116     my $q = $r->query($domain, 'SOA') or die $r->errorstring, "\@$nameserver\n";
    69     return (map { $_->serial } grep { $_->type eq 'SOA' } $q->answer)[0];
   117     return (map { $_->serial } grep { $_->type eq 'SOA' } $q->answer)[0];
    70 }
   118 }
    71 
   119 
    72 # - the nameservers known from the ns records
   120 # - the nameservers known from the ns records
    73 # - from the primary master if this is not one of the
   121 # - from the primary master if this is not one of the