Blame contrib/check_zone

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