813 lines
19 KiB
Perl
813 lines
19 KiB
Perl
#!/usr/bin/perl -w
|
|
# $Id: check_zone 1842 2021-07-08 14:25:00Z willem $
|
|
|
|
=head1 NAME
|
|
|
|
check_zone - Check a DNS zone for errors
|
|
|
|
=head1 SYNOPSIS
|
|
|
|
C<check_zone> [ C<-r> ][ C<-v> ] I<domain> [ I<class> ]
|
|
|
|
=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
|
|
|
|
=item C<-r>
|
|
|
|
Perform a recursive check on subdomains.
|
|
|
|
=item C<-v>
|
|
|
|
Verbose.
|
|
|
|
=item C<-a alternate_domain>
|
|
|
|
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).
|
|
|
|
=item C<-e exception_file>
|
|
|
|
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).
|
|
This allows for skipping entries that are odd or unusual, but not causing problems. Note: this only works with A - PTR checks.
|
|
|
|
=back
|
|
|
|
=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 SEE ALSO
|
|
|
|
L<perl(1)>, L<axfr>, L<check_soa>, L<mx>, L<perldig>, L<Net::DNS>
|
|
|
|
=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 warnings;
|
|
use Carp;
|
|
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;
|
|
local $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 assert { croak 'violated assertion' unless shift; return }
|
|
|
|
|
|
sub parse_exceptions_file {
|
|
my %exceptions;
|
|
my $file = $opt_e || "";
|
|
return %exceptions unless ( -r $file);
|
|
open( my $fh, '<', $file );
|
|
die "Couldn't read $file: $!" unless $fh;
|
|
while (<$fh>) {
|
|
chomp;
|
|
#print " raw line: $_\n";
|
|
next if /^$/;
|
|
next if /^\s*#/;
|
|
s/#.*$//;
|
|
s/^\s*//;
|
|
s/\s*$//;
|
|
s/'//g;
|
|
my ($left, $right) = (split /[\s:]+/, $_)[0, -1];
|
|
push @{$exceptions{$left}}, $right;
|
|
#print "processed line: $line\n";
|
|
|
|
}
|
|
close($fh);
|
|
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 = Net::DNS::Resolver->new();
|
|
$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 my $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 my $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);
|
|
}
|
|
}
|
|
return;
|
|
}
|
|
|
|
sub check_soa {
|
|
|
|
my( $domain, $class, $nsl ) = @_;
|
|
my( $soa_sn, $soa_diff ) = ( 0, 0 );
|
|
my( $ns, $soa_rr );
|
|
my $rr_count = 0;
|
|
|
|
my $res = Net::DNS::Resolver->new();
|
|
|
|
$res->defnames( 0 );
|
|
$res->retry( 2 );
|
|
$res->recurse( 0 );
|
|
|
|
# Contact each name server and get the
|
|
# SOA for the somain.
|
|
#
|
|
|
|
foreach my $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 my $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";
|
|
return;
|
|
}
|
|
|
|
sub check_ptr {
|
|
|
|
my( $domain, $class, $nsl, $zone ) = @_;
|
|
|
|
my $res = Net::DNS::Resolver->new();
|
|
my $ptr_rr;
|
|
my $rr_count = 0;
|
|
|
|
$res->defnames( 0 );
|
|
$res->retry( 2 );
|
|
$res->nameservers( @$nsl );
|
|
|
|
foreach my $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";
|
|
return;
|
|
}
|
|
|
|
sub check_ns {
|
|
|
|
my( $domain, $class, $nsl, $zone ) = @_;
|
|
my $res = Net::DNS::Resolver->new();
|
|
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 my $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";
|
|
return;
|
|
}
|
|
|
|
sub check_a {
|
|
|
|
my( $domain, $class, $nsl, $zone ) = @_;
|
|
|
|
my $res = Net::DNS::Resolver->new();
|
|
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 my $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";
|
|
return;
|
|
}
|
|
|
|
|
|
sub check_mx {
|
|
|
|
my( $domain, $class, $nsl, $zone ) = @_;
|
|
|
|
my $res = Net::DNS::Resolver->new();
|
|
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 my $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";
|
|
return;
|
|
}
|
|
|
|
sub check_cname {
|
|
|
|
my( $domain, $class, $nsl, $zone ) = @_;
|
|
|
|
my $res = Net::DNS::Resolver->new();
|
|
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 my $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";
|
|
return;
|
|
}
|
|
|
|
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 = Net::DNS::Resolver->new();
|
|
|
|
$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 my $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" );
|
|
}
|
|
return;
|
|
}
|
|
|
|
|
|
sub xcheck_ptr2a {
|
|
|
|
my( $ptr_rr, $domain, $class, $nsl ) = @_;
|
|
|
|
my $res = Net::DNS::Resolver->new();
|
|
|
|
$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 my $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" );
|
|
}
|
|
return;
|
|
}
|
|
|
|
|
|
sub xcheck_name {
|
|
|
|
my( $name, $domain, $class, $nsl ) = @_;
|
|
|
|
my $res = Net::DNS::Resolver->new();
|
|
|
|
$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 my $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" );
|
|
}
|
|
return;
|
|
}
|
|
|
|
|
|
sub types4name {
|
|
|
|
my( $name, $domain, $class, $nsl ) = @_;
|
|
|
|
my $res = Net::DNS::Resolver->new();
|
|
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 my $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 );
|
|
return;
|
|
}
|
|
|
|
|
|
|
|
|