#!/usr/bin/perl # (C) Duane Wessels, wessels@measurement-factory.com # # $Id: check_zone_auth,v 1.1 2007/10/25 20:04:33 wessels Exp $ # # check_zone_auth # # nagios plugin to check that all authoritative nameservers for a zone # have the same NS RRset and the same serial number. # usage # # define command { # command_name check-zone-auth # command_line /usr/local/libexec/nagios-local/check_zone_auth -Z $HOSTADDRESS$ # } # # define service { # name dns-auth-service # check_command check-zone-auth # ... # } # # define host { # use dns-zone # host_name zone.example.com # alias ZONE example.com # } # # define service { # use dns-auth-service # host_name zone.example.com # } use warnings; use strict; use Getopt::Std; use Net::DNS::Resolver; use Net::DNS::Resolver::Recurse; use Time::HiRes qw ( gettimeofday tv_interval); my %opts; getopts('Z:d', \%opts); usage() unless $opts{Z}; usage() if $opts{h}; my $zone = $opts{Z}; $zone =~ s/^zone\.//; my $data; my $start; my $stop; $start = [gettimeofday()]; do_queries(); $stop = [gettimeofday()]; do_analyze(); sub do_queries { my $recres = Net::DNS::Resolver::Recurse->new; $recres->recursion_callback(sub { my $p = shift; print STDERR $p->string if $opts{d}; add_nslist_to_data($p); }); my $seed = $recres->query_dorecursion($zone, 'SOA'); critical("No response to seed query") unless $seed; $recres = undef; critical($seed->header->rcode . " from " . $seed->answerfrom) unless ($seed->header->rcode eq 'NOERROR'); print STDERR $seed->string if $opts{d}; add_nslist_to_data($seed); my $n; do { $n = 0; foreach my $ns (keys %$data) { next if $data->{$ns}->{done}; my $pkt = send_query($zone, 'SOA', $ns); add_nslist_to_data($pkt); $data->{$ns}->{queries}->{SOA} = $pkt; if ($pkt && $pkt->header->nscount == 0) { my $ns_pkt = send_query($zone, 'NS', $ns); add_nslist_to_data($ns_pkt); $data->{$ns}->{queries}->{NS} = $ns_pkt; } print STDERR "done with $ns\n" if $opts{d}; $data->{$ns}->{done} = 1; $n++; } } while ($n); } sub do_analyze { my $maxserial = 0; my $nscount = 0; foreach my $ns (keys %$data) { my $soa_pkt = $data->{$ns}->{queries}->{SOA}; critical("No response from $ns") unless $soa_pkt; print STDERR $soa_pkt->string if $opts{d}; critical($soa_pkt->header->rcode . " from $ns") unless ($soa_pkt->header->rcode eq 'NOERROR'); critical("$ns is lame") unless $soa_pkt->header->ancount; my $serial = soa_serial($soa_pkt); $maxserial = $serial if ($serial > $maxserial); $nscount++; } warning("No nameservers found. Is '$zone' a zone?") if ($nscount < 1); warning("Only one auth NS") if ($nscount < 2); foreach my $ns (keys %$data) { my $soa_pkt = $data->{$ns}->{queries}->{SOA}; my $ns_pkt = $data->{$ns}->{queries}->{NS}; # see if this nameserver lists all nameservers # my %all_ns; foreach my $ns (keys %$data) { $all_ns{$ns} = 1; } foreach my $ns (get_nslist($soa_pkt)) { delete $all_ns{$ns}; } foreach my $ns (get_nslist($ns_pkt)) { delete $all_ns{$ns}; } if (keys %all_ns) { warning("$ns does not include " . join(',', keys %all_ns) . " in NS RRset"); } warning("$ns claims is it not authoritative") unless $soa_pkt->header->aa; my $serial = soa_serial($soa_pkt); warning("$ns serial ($serial) is less than the maximum ($maxserial)") if ($serial < $maxserial); } success("$nscount nameservers, serial $maxserial"); } sub add_nslist_to_data { my $pkt = shift; foreach my $ns (get_nslist($pkt)) { print STDERR "adding NS $ns\n" if $opts{d}; $data->{$ns}->{done} |= 0; } } sub soa_serial { my $pkt = shift; foreach my $rr ($pkt->answer) { next unless ($rr->type eq 'SOA'); next unless ($rr->name eq $zone); return $rr->serial; } return 0; } sub success { output('OK', shift); exit(0); } sub warning { output('WARNING', shift); exit(1); } sub critical { output('CRITICAL', shift); exit(2); } sub output { my $state = shift; my $msg = shift; $stop = [gettimeofday()] unless $stop; my $latency = tv_interval($start, $stop); printf "ZONE %s: %s; (%.2fs) |time=%.6fs;;;0.000000\n", $state, $msg, $latency, $latency; } sub usage { print STDERR "usage: $0 -Z zone\n"; exit 3; } sub send_query { my $qname = shift; my $qtype = shift; my $server = shift; my $res = Net::DNS::Resolver->new; $res->nameserver($server) if $server; return $res->send($qname, $qtype); } sub get_nslist { my $pkt = shift; return () unless $pkt; return () unless $pkt->authority; my @nslist; foreach my $rr ($pkt->authority) { next unless ($rr->type eq 'NS'); next unless ($rr->name eq $zone); push(@nslist, lc($rr->nsdname)); } return @nslist; }