Blame contrib/check_zone

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