Warning: Cannot modify header information - headers already sent by (output started at /home/destefa1/public_html/nf/function.php:1) in /home/destefa1/public_html/nf/function.php on line 215
Warning: Cannot modify header information - headers already sent by (output started at /home/destefa1/public_html/nf/function.php:1) in /home/destefa1/public_html/nf/function.php on line 216
Warning: Cannot modify header information - headers already sent by (output started at /home/destefa1/public_html/nf/function.php:1) in /home/destefa1/public_html/nf/function.php on line 217
Warning: Cannot modify header information - headers already sent by (output started at /home/destefa1/public_html/nf/function.php:1) in /home/destefa1/public_html/nf/function.php on line 218
Warning: Cannot modify header information - headers already sent by (output started at /home/destefa1/public_html/nf/function.php:1) in /home/destefa1/public_html/nf/function.php on line 219
Warning: Cannot modify header information - headers already sent by (output started at /home/destefa1/public_html/nf/function.php:1) in /home/destefa1/public_html/nf/function.php on line 220
#!/usr/local/bin/perl -w
# $Id: check_zone 638 2007-05-15 18:59:26Z olaf $
=head1 NAME
check_zone - Check a DNS zone for errors
=head1 SYNOPSIS
C [ C<-r> ][ C<-v> ] I [ I ]
=head1 DESCRIPTION
Checks a DNS zone for errors. Current checks are:
=over 4
=item *
Checks the domain's SOA from each of the domain's name servers. The SOA serial numbers should match. This program's output cannot be trusted if they do not.
=item *
Tries to perform an AXFR from each of the domain's name servers. This test helps to detect whether the name server is blocking AXFR.
=item *
Checks that all A records have corresponding PTR records. For each A record its PTR's name is match checked.
=item *
Checks that all PTR records match an A record (sometimes they match a CNAME). Check the PTR's name against the A record.
=item *
Checks that hosts listed in NS, MX, and CNAME records have
A records. Checks for NS and CNAME records not pointing to another CNAME (i.e., they must directly resolve to an A record). That test may be somewhat controversial because, in many cases, a MX to a CNAME or a CNAME to another CNAME will resolve; however, in DNS circles it isn't a recommended practise.
=item *
Check each record processed for being with the class requested. This is an internal integrity check.
=back
=head1 OPTIONS
=over 4
=back
=item C<-r>
Perform a recursive check on subdomains.
=item C<-v>
Verbose.
=item C<-a alternate_domain>
Treat as equal to . This is useful when supporting a change of domain names (eg from myolddomain.example.net to mynewdomain.example.net) where the PTR records can point to only one of the two supported domains (which are otherwise identical).
=item C<-e exception_file>
Ignore exceptions in file . File format can be space-separated domain pairs, one pair per line, or it can be straight output from this program itself (for simple cut-and-paste functionality).
This allows for skipping entries that are odd or unusual, but not causing problems. Note: this only works with A - PTR checks.
=head1 AUTHORS
Originally developed by Michael Fuhr (mfuhr@dimensional.com) and
hacked--with furor--by Dennis Glatting
(dennis.glatting@software-munitions.com).
"-a" and "-e" options added by Paul Archer
=head1 COPYRIGHT
=head1 SEE ALSO
L, L, L, L, L, L
=head1 BUGS
A query for an A RR against a name that is a CNAME may not follow the CNAME to an A RR.
There isn't a mechanism to insure records are returned from an authoritative source.
There appears to be a bug in the resolver AXFR routine where, if one server cannot be contacted, the routine doesn't try another in its list.
=cut
require 'assert.pl';
use strict;
use vars qw($opt_r);
use vars qw($opt_v);
use vars qw($opt_a);
use vars qw($opt_e);
use Getopt::Std;
use File::Basename;
use IO::Socket;
use Net::DNS;
getopts("rva:e:");
die "Usage: ", basename($0), " [ -r -v ] [ -a alternate_domain] [ -e eqivalent_domains_file ] domain [ class ]\n"
unless (@ARGV >= 1) && (@ARGV <= 2);
our $exit_status = 0;
$SIG{__WARN__} = sub {$exit_status=1 ; print STDERR @_ };
$opt_r = 1;
our $main_domain=$ARGV[0];
our %exceptions = parse_exceptions_file();
foreach my $key (sort keys %exceptions) {
print "$key:\t";
foreach my $val (@{$exceptions{$key}}) {
print "$val ";
}
print "\n";
}
check_domain(@ARGV);
exit $exit_status;
sub parse_exceptions_file {
my %exceptions;
my $file = $opt_e || "";
return %exceptions unless ( -r $file);
open FH, $file or warn "Couldn't read $file: $!";
my $line;
while ( defined ($line = ) ) {
chomp $line;
#print " raw line: $line\n";
next if $line =~ /^\s*#/;
$line =~ s/#.*$//;
$line =~ s/^\s*//;
$line =~ s/\s*$//;
$line =~ s/'//g;
my ($left, $right) = (split /[\s:]+/, $line)[0, -1];
push @{$exceptions{$left}}, $right;
#print "processed line: $line\n";
}
return %exceptions;
}
sub check_domain {
my ( $domain, $class ) = @_;
my $ns;
my @zone;
$class ||= "IN";
print "-" x 70, "\n";
print "$domain (class $class)\n";
print "\n";
my $res = new Net::DNS::Resolver;
$res->defnames( 0 );
$res->retry( 2 );
my( $nspack, $ns_rr, @nsl );
# Get a list of name servers for the domain.
# Error-out if the query isn't satisfied.
#
$nspack = $res->query( $domain, 'NS', $class );
unless( defined( $nspack )) {
warn "Couldn't find nameservers for $domain: ",
$res->errorstring, "\n";
return;
}
printf( "List of name servers returned from '%s'\n", $res->answerfrom );
foreach $ns_rr ( $nspack->answer ) {
$ns_rr->print if( $opt_v );
assert( $class eq $ns_rr->class );
assert( 'NS' eq $ns_rr->type );
if( $ns_rr->name eq $domain ) {
print "\t", $ns_rr->rdatastr, "\n";
push @nsl, $ns_rr->rdatastr;
} else {
warn( "asked for '$domain', got '%s'\n", $ns_rr->rdatastr );
}
}
print "\n";
warn( "\tZone has no NS records\n" ) if( scalar( @nsl ) == 0 );
# Transfer the zone from each of the name servers.
# The zone is transferred for several reasons.
# First, so the check routines won't (an efficiency
# issue) and second, to see if we can.
#
$res->nameservers( @nsl );
foreach $ns ( @nsl ) {
$res->nameservers( $ns );
my @local_zone = $res->axfr( $domain, $class );
unless( @local_zone ) {
warn "Zone transfer from '", $ns, "' failed: ",
$res->errorstring, "\n";
}
@zone = @local_zone if( ! @zone );
}
# Query each name server for the zone
# and check the zone's SOA serial number.
#
print "checking SOA records\n";
check_soa( $domain, $class, \@nsl );
print "\n";
# Check specific record types.
#
print "checking NS records\n";
check_ns( $domain, $class, \@nsl, \@zone );
print "\n";
print "checking A records\n";
check_a( $domain, $class, \@nsl, \@zone );
print "\n";
print "checking PTR records\n";
check_ptr( $domain, $class, \@nsl, \@zone );
print "\n";
print "checking MX records\n";
check_mx( $domain, $class, \@nsl, \@zone );
print "\n";
print "checking CNAME records\n";
check_cname( $domain, $class, \@nsl, \@zone );
print "\n";
# Recurse?
#
if( $opt_r ) {
my %subdomains;
print "checking subdomains\n\n";
# Get a list of NS records from the zone that
# are not for the zone (i.e., they're subdomains).
#
foreach ( grep { $_->type eq 'NS' and $_->name ne $domain } @zone ) {
$subdomains{$_->name} = 1;
}
# For each subdomain, check it.
#
foreach ( sort keys %subdomains ) {
check_domain($_, $class);
}
}
}
sub check_soa {
my( $domain, $class, $nsl ) = @_;
my( $soa_sn, $soa_diff ) = ( 0, 0 );
my( $ns, $soa_rr );
my $rr_count = 0;
my $res = new Net::DNS::Resolver;
$res->defnames( 0 );
$res->retry( 2 );
$res->recurse( 0 );
# Contact each name server and get the
# SOA for the somain.
#
foreach $ns ( @$nsl ) {
my $soa = 0;
my $nspack;
# Query the name server and test
# for a result.
#
$res->nameservers( $ns );
$nspack = $res->query( $domain, "SOA", $class );
unless( defined( $nspack )) {
warn "Couldn't get SOA from '$ns'\n";
next;
}
# Look at each SOA for the domain from the
# name server. Specifically, look to see if
# its serial number is different across
# the name servers.
#
foreach $soa_rr ( $nspack->answer ) {
$soa_rr->print if( $opt_v );
assert( $class eq $soa_rr->class );
assert( 'SOA' eq $soa_rr->type );
print "\t$ns:\t", $soa_rr->serial, "\n";
# If soa_sn is zero then an SOA serial number
# hasn't been recorded. In that case record
# the serial number. If the serial number
# doesn't match a previously recorded one then
# indicate they are different.
#
# If the serial numbers are different then you
# cannot really trust the remainder of the test.
#
if( $soa_sn ) {
$soa_diff = 1 if ( $soa_sn != $soa_rr->serial );
} else {
$soa_sn = $soa_rr->serial;
}
}
++$rr_count;
}
print "\t*** SOAs are different!\n" if( $soa_diff );
print "$rr_count SOA RRs checked.\n";
}
sub check_ptr {
my( $domain, $class, $nsl, $zone ) = @_;
my $res = new Net::DNS::Resolver;
my $ptr_rr;
my $rr_count = 0;
$res->defnames( 0 );
$res->retry( 2 );
$res->nameservers( @$nsl );
foreach $ptr_rr ( grep { $_->type eq 'PTR' } @$zone ) {
my @types;
$ptr_rr->print if( $opt_v );
assert( $class eq $ptr_rr->class );
assert( 'PTR' eq $ptr_rr->type );
print "\tchecking PTR rr '$ptr_rr' to PTR\n" if( $opt_v );
@types = types4name( $ptr_rr->ptrdname, $domain, $class, $nsl );
if( grep { $_ eq 'A' } @types ) {
xcheck_ptr2a( $ptr_rr, $domain, $class, $nsl );
} else {
warn "\t'", $ptr_rr->ptrdname,
"' doesn't resolve to an A RR (RRs are '",
join( ', ', @types ), "')\n";
}
++$rr_count;
}
print "$rr_count PTR RRs checked.\n";
}
sub check_ns {
my( $domain, $class, $nsl, $zone ) = @_;
my $res = new Net::DNS::Resolver;
my $ns_rr;
my $rr_count = 0;
$res->defnames( 0 );
$res->retry( 2 );
$res->nameservers( @$nsl );
# Go through the zone data and process
# all NS RRs for the zone (delegation
# NS RRs are ignored). Specifically,
# check to see if the indicate name server
# is a CNAME RR and the name resolves to an A
# RR. Check to insure the address resolved
# against the name has an associated PTR RR.
#
foreach $ns_rr ( grep { $_->type eq 'NS' } @$zone ) {
my @types;
$ns_rr->print if( $opt_v );
assert( $class eq $ns_rr->class );
assert( 'NS' eq $ns_rr->type );
next if( $ns_rr->name ne $domain );
printf( "rr nsdname: %s\n", $ns_rr->nsdname ) if $opt_v;
@types = types4name( $ns_rr->nsdname, $domain, $class, $nsl );
if( grep { $_ eq 'A' } @types ) {
xcheck_name( $ns_rr->nsdname, $domain, $class, $nsl );
} else {
warn "\t'", $ns_rr->nsdname,
"' doesn't resolve to an A RR (RRs are '",
join( ', ', @types ), "')\n";
}
++$rr_count;
}
print "$rr_count NS RRs checked.\n";
}
sub check_a {
my( $domain, $class, $nsl, $zone ) = @_;
my $res = new Net::DNS::Resolver;
my $a_rr;
my $rr_count = 0;
$res->defnames( 0 );
$res->retry( 2 );
$res->nameservers( @$nsl );
# Go through the zone data and process
# all A RRs. Specifically, check to insure
# each A RR matches a PTR RR and the PTR RR
# matches the A RR.
#
foreach $a_rr ( grep { $_->type eq 'A' } @$zone ) {
$a_rr->print if( $opt_v );
assert( $class eq $a_rr->class );
assert( 'A' eq $a_rr->type );
print "\tchecking A RR '", $a_rr->address, "' to PTR\n" if( $opt_v );
xcheck_a2ptr( $a_rr, $domain, $class, $nsl );
++$rr_count;
}
print "$rr_count A RRs checked.\n";
}
sub check_mx {
my( $domain, $class, $nsl, $zone ) = @_;
my $res = new Net::DNS::Resolver;
my $mx_rr;
my $rr_count = 0;
$res->defnames( 0 );
$res->retry( 2 );
$res->nameservers( @$nsl );
# Go through the zone data and process
# all MX RRs. Specifically, check to insure
# each MX RR resolves to an A RR and the
# A RR has a matching PTR RR.
#
foreach $mx_rr ( grep { $_->type eq 'MX' } @$zone ) {
$mx_rr->print if( $opt_v );
assert( $class eq $mx_rr->class );
assert( 'MX' eq $mx_rr->type );
print "\tchecking MX RR '", $mx_rr->exchange, "' to A\n" if( $opt_v );
xcheck_name( $mx_rr->exchange, $domain, $class, $nsl );
++$rr_count;
}
print "$rr_count MX RRs checked.\n";
}
sub check_cname {
my( $domain, $class, $nsl, $zone ) = @_;
my $res = new Net::DNS::Resolver;
my $cname_rr;
my $rr_count = 0;
$res->defnames( 0 );
$res->retry( 2 );
$res->nameservers( @$nsl );
# Go through the zone data and process
# all CNAME RRs. Specifically, check to insure
# each CNAME RR resolves to an A RR and the
# A RR has a matching PTR RR.
#
foreach $cname_rr ( grep { $_->type eq 'CNAME' } @$zone ) {
my @types;
$cname_rr->print if( $opt_v );
assert( $class eq $cname_rr->class );
assert( 'CNAME' eq $cname_rr->type );
print "\tchecking CNAME RR '", $cname_rr->cname, "' to A\n"
if( $opt_v );
@types = types4name( $cname_rr->cname, $domain, $class, $nsl );
if( grep { $_ eq 'A' } @types ) {
xcheck_name( $cname_rr->cname, $domain, $class, $nsl );
} else {
warn "\t'", $cname_rr->cname,
"' doesn't resolve to an A RR (RRs are '",
join( ', ', @types ), "')\n";
}
++$rr_count;
}
print "$rr_count CNAME RRs checked.\n";
}
sub check_w_equivs_and_exceptions {
my ($left, $comp, $right) = @_;
if (defined $exceptions{$left}) {
foreach my $rval (@{$exceptions{$left}}) {
$left = $right if ($rval eq $right);
}
}
if ($opt_a){
$left =~ s/\.?$opt_a$//;
$left =~ s/\.?$main_domain$//;
$right =~ s/\.?$opt_a$//;
$right =~ s/\.?$main_domain$//;
}
return (eval ("\"$left\" $comp \"$right\"") );
}
sub xcheck_a2ptr {
my( $a_rr, $domain, $class, $nsl ) = @_;
my $res = new Net::DNS::Resolver;
$res->defnames( 0 );
$res->retry( 2 );
$res->nameservers( @$nsl );
assert( $class eq $a_rr->class );
assert( 'A' eq $a_rr->type );
# Request a PTR RR against the A RR.
# A missing PTR RR is an error.
#
my $ans = $res->query( $a_rr->address, 'PTR', $class );
if( defined( $ans )) {
my $ptr_rr;
foreach $ptr_rr ( $ans->answer ) {
$ptr_rr->print if( $opt_v );
assert( $class eq $ptr_rr->class );
assert( 'PTR' eq $ptr_rr->type );
warn( "\t'", $a_rr->name, "' has address '",
$a_rr->address, "' but PTR is '",
$ptr_rr->ptrdname, "'\n" )
if( check_w_equivs_and_exceptions($a_rr->name, "ne", $ptr_rr->ptrdname) );
warn( "\t'", $a_rr->name, "' has address '",
$a_rr->address, "' but PTR is '",
ip_ptr2a_str( $ptr_rr->name ), "'\n" )
if( $a_rr->address ne ip_ptr2a_str( $ptr_rr->name ));
}
} else {
warn( "\tNO PTR RR for '", $a_rr->name,
"' at address '", $a_rr->address,"'\n" );
}
}
sub xcheck_ptr2a {
my( $ptr_rr, $domain, $class, $nsl ) = @_;
my $res = new Net::DNS::Resolver;
$res->defnames( 0 );
$res->retry( 2 );
$res->nameservers( @$nsl );
assert( $class eq $ptr_rr->class );
assert( 'PTR' eq $ptr_rr->type );
# Request an A RR against the PTR RR.
# A missing A RR is an error.
#
my $ans = $res->query( $ptr_rr->ptrdname, 'A', $class );
if( defined( $ans )) {
my $a_rr;
foreach $a_rr ( $ans->answer ) {
$a_rr->print if( $opt_v );
assert( $class eq $a_rr->class );
assert( 'A' eq $a_rr->type );
warn( "\tPTR RR '", $ptr_rr->name, "' has name '",
$ptr_rr->ptrdname, "' but A query returned '",
$a_rr->name, "'\n" )
if( check_w_equivs_and_exceptions($ptr_rr->ptrdname, "ne", $a_rr->name) );
warn( "\tPTR RR '", $ptr_rr->name, "' has address '",
ip_ptr2a_str( $ptr_rr->name ),
"' but A query returned '", $a_rr->address, "'\n" )
if( ip_ptr2a_str( $ptr_rr->name ) ne $a_rr->address );
}
} else {
warn( "\tNO A RR for '", $ptr_rr->ptrdname,
"' at address '", ip_ptr2a_str( $ptr_rr->address ), "'\n" );
}
}
sub xcheck_name {
my( $name, $domain, $class, $nsl ) = @_;
my $res = new Net::DNS::Resolver;
$res->defnames( 0 );
$res->retry( 2 );
$res->nameservers( @$nsl );
# Get the A RR for the name.
#
my $ans = $res->query( $name, 'A', $class );
if( defined( $ans )) {
# There is one or more A RRs.
# For each A RR do a reverse look-up
# and verify the PTR matches the A.
#
my $a_rr;
foreach $a_rr ( $ans->answer ) {
$a_rr->print if( $opt_v );
assert( $class eq $a_rr->class );
assert( 'A' eq $a_rr->type );
warn( "\tQuery for '$name' returned A RR name '",
$a_rr->name, "'\n" )
if( check_w_equivs_and_exceptions($name, "ne", $a_rr->name) );
xcheck_a2ptr( $a_rr, $domain, $class, $nsl );
}
} else {
warn( "\t", $name, " has no A RR\n" );
}
}
sub types4name {
my( $name, $domain, $class, $nsl ) = @_;
my $res = new Net::DNS::Resolver;
my @rr_types;
$res->defnames( 0 );
$res->retry( 2 );
$res->nameservers( @$nsl );
# Get the RRs for the name.
#
my $ans = $res->query( $name, 'ANY', $class );
if( defined( $ans )) {
my $any_rr;
foreach $any_rr ( $ans->answer ) {
$any_rr->print if( $opt_v );
assert( $class eq $any_rr->class );
push @rr_types, ( $any_rr->type );
}
} else {
warn( "\t'", $name, "' doesn't resolve.\n" );
}
# If there were no RRs for the name then
# return the RR types of ???
#
push @rr_types, ( '???' ) if( ! @rr_types );
return @rr_types;
}
sub ip_ptr2a_str {
my( $d, $c, $b, $a ) = ip_parts( $_[0]);
return "$a.$b.$c.$d";
}
sub ip_parts {
my $ip = $_[0];
assert( $ip ne '' );
if( $ip =~ /(\d+)\.(\d+)\.(\d+)\.(\d+)/oi ) {
return ( $1, $2, $3, $4 );
} else {
warn "Unable to parse '$ip'\n";
}
assert( 0 );
}