From: meyer@darkwing.uoregon.edu (David M. Meyer 503/346-1747)
Newsgroups: comp.protocols.tcp-ip.domains
Subject: how to you do consistency checking?
Date: 26 Jan 93 16:47:57
Organization: University Network Services, University of Oregon, Eugene, OR
	97403
Distribution: world
NNTP-Posting-Host: darkwing.uoregon.edu



	I have the need to check consistency checking between my
	zone and reverse zone files. I wrote the following perl
	code, but I'm not too happy with it. Anyone have anything
	better, or suggestions on how to make this better?

	Thanks,


	Dave

	David M. Meyer			Voice:     503/346-1747
	Senior Network Engineer		Pager:	   503/342-9458
	Office of University Computing	FAX:	   503/346-4397
	Computing Center		Internet:  meyer@ns.uoregon.edu
	University of Oregon
	1225 Kincaid
	Eugene, OR 97403	



-------


#!/usr/local/bin/perl -- # -*- perl -*-

#
#	checkdns --
#
#	Be mindful of the multitiude of errors I make here...
#
#	David M. Meyer
#	meyer@phloem.uoregon.edu
#	15-Jan-93
#
#	$Header: /usr/local/etc/named/RCS/checkdns,v 1.0 1993/01/27 00:36:55 meyer Exp $
#
#
require('getopts.pl');

($program = $0) =~ s%.*/%%;
$Usage = " [args]				\
	-d for debugging			\
	-i <initial domain name>		\
	-n <network number>			\
	-r <path to reverse zone file>		\
	-s <subnet mask> (subnet octet only)	\
	-u for this message			\ 
	-z <path to zone file>			\
";

do Getopts("di:n:r:s:uz:");

if ($opt_d eq "") {
    $DEBUG = 0;
} else {
   $DEBUG = 1;
}

if ($opt_i eq "") {
    $domain = "uoregon.edu";
} else {
   $domain = $opt_i;
}

if ($opt_n eq "") {
    $network = "128.223";
} else {
   $network = $opt_n;
}

if ($opt_r eq "") {
    $revzonefile = "/usr/local/etc/named/uoregon.rev";
} else {
   $revzonefile = $opt_r;
}

if ($opt_s eq "") {
    $subnetmask = 0xfe;			# subnet only
} else {
   $subnetmask = $opt_s;
}
if ($opt_u ne "") {
    printf("Usage: $program $Usage");
    exit 1;
}

if ($opt_z eq "") {
    $zonefile = "/usr/local/etc/named/uoregon.zone";
} else {
   $zonefile = $opt_z;
}


#
#	Do some inititalizing...
#

$GOOD			= 0;
$BAD			= 1;
$exitval		= $GOOD;
$HaveRevNotZone		= 0;
$HaveInconsistent	= 0;
$HaveBadIP		= 0;
$NoNameServer		= 0;


select(STDERR); $| = 1;
&CheckZone($zonefile);
&CheckRevZone($revzonefile);
printf("\nAnalyzing...\n");


#
#	First, check for inconsistency
#

if ($HaveInconsistent) {
    foreach $ipaddr (sort keys(%Inconsistent)) {
	printf(STDERR "%s inconsistent\n", $ipaddr);
    }
    $exitval = $BAD;
}

#
#	Now, just see if every address in the zone file is 
#	actually in the reverse zone file. If not, check that the
#	file actually had a good IP address (not a typo -> inconsistent).
#

foreach $ipaddr (sort keys(%ZoneHaveSeen)) {
    next if &NotOurSubnet($ipaddr);
    if ($RevHaveSeen{$ipaddr} ne $ipaddr) {
	if (!&CouldBeIPAddr($ipaddr)) {
	    printf(STDERR "Bad IP address for %s (%s) in %s\n", 
		   $IPAddrs{$ipaddr}, $ipaddr, $zonefile);
	}
	printf(STDERR "Missing %s (%s) from %s\n",
	       $IPAddrs{$ipaddr}, $ipaddr, $revzonefile);
	$exitval = $BAD;
    }
}

#
#	Check for addresses in the reverse zone file which are not 
#	in the zone file
#

if ($HaveRevNotZone) {
    foreach $ipaddr (sort keys(%RevNotZone)) {
	$fqdn = $RevNotZone{$ipaddr};
	printf(STDERR "Missing %s (%s) from %s\n",
	       $fqdn, $ipaddr, $zonefile);
	if (!&CouldBeIPAddr($ipaddr)) {
	    printf(STDERR "Bad IP address for %s (%s) in %s\n", 
		   $fqdn,$ipaddr, $revzonefile);
	}
    }
    $exitval = $BAD;
}

if ($HaveBadIP) {
    foreach $ipaddr (sort keys(%BadIP)) {
	$fqdn = $BadIP{$ipaddr};
	printf(STDERR "%s (%s) bad IP address\n", $fqdn, $ipaddr);
    }
    $exitval = $BAD;
}
	
if ($NoNameServer) {
    foreach $ns (sort keys(%NoNameServer)) {
	printf(STDERR "Inconsistent NS records for %s\n", $ns);
    }
    $exitval = $BAD;
}
	
exit $exitval;
	
#
#	Just check A records in a zone file. "Try" to handle include
#	files...
#

sub CheckZone {
    local($zonefile) = @_;
    open(ZONE, $zonefile) || die "Can't open ZONE FILE: $!\n";
    printf("Reading zone file (%s)\n", $zonefile);
    while (<ZONE>) {
	next if /^$/;					# throw blank lines
	next if /^[\s]*;/;				# throw comments
	if (/[^\s]*\$[include|INCLUDE]/) {
	    ($include_file) = /[include|INCLUDE]\W+([^'"\s]+)/;
	    &CheckZone($include_file);
	}
	chop;						# and \n on last field
	@tmp = split;					# now get fields
	
	if ($tmp[0] eq '$origin') {
	    chop($tmp[1]);				# remove trailing .
	    $domain = $tmp[1];
	} elsif ($tmp[2] eq 'NS') { 			# $tmp[0] = domain
	    $NameServer{$tmp[3]} = $tmp[0]; 		# $tmp[3] = NS
	} elsif ($tmp[3] eq 'NS') { 			# ttl
	    $NameServer{$tmp[4]} = $tmp[0];
	} elsif ($tmp[2] eq 'A') { 			# no ttl
	    if (!&SpecialCase($tmp[0])) {		# $tmp[0] = name
		&HandleARecord($tmp[0],$domain,$tmp[3]);# $tmp[3] = IP addr
	    }
	} elsif ($tmp[3] eq 'A') {			# have ttl
	    if (!&SpecialCase($tmp[0])) { 		# $tmp[0] = name
		&HandleARecord($tmp[0],$domain,$tmp[4]);# $tmp[4 = IP addr
	    }
	}
	
    }
    close(ZONE);    
    printf("Done with $zonefile\n");
}	

#
#	Just check PTR records in a reverse zone file. "Try" to handle 
#	include files...
#

sub CheckRevZone {
    local($revzonefile) = @_;
    
    open(REVZONE, $revzonefile) || die "Can't open REVERSE ZONE FILE: $!\n";
    printf("Reading reverse zone file (%s)\n", $revzonefile);
    
    while (<REVZONE>) {
	next if /^$/;					# throw blank lines
	next if /^[\s]*[;@]/;				# throw comments
	if (/[^\s]*\$[include|INCLUDE]/) {
	    ($include_file) = /[include|INCLUDE]\W+([^'"\s]+)/;
	    &CheckRevZone($include_file);
	}
	chop;						# and \n on last field
	@tmp = split;					# get fields
	
	@addr = split(/[. ]/,$tmp[0]); 			# get addr octets
	
	if (($tmp[2] eq 'A') || ($tmp[3] eq 'A')) { 	# Ignore NSs
	    next if ($NameServer{$tmp[0]} ne "");
	    $NoNameServer{$tmp[0]} = $tmp[0];
	    $NoNameServer = 1;
	    next;
	}
#
#	Ignore inverse address 0.0 and and NS glue records
#
	next if (($addr[0] eq "")    ||			
		 ($addr[1] eq "")    ||
		 ($addr[0] eq "0")   ||
		 ($addr[1] eq "0")); 			# Ignore this
	
	$fqdn = $tmp[$#tmp];				# last in rev file
	chop($fqdn);					# trailing .
	$ipaddr = sprintf("%s.%s.%s", $network,$addr[1],$addr[0]);
	$RevHaveSeen{$ipaddr} = $ipaddr;
	
	if ($DEBUG) {
	    printf("revzone:\t$fqdn,$ipaddr\n");
	}
	
	if ($IPAddrs{$ipaddr} eq "") {
	    $HaveRevNotZone = 1;
	    $RevNotZone{$ipaddr} = $fqdn;
	}
	if ($IPAddrs{$ipaddr} ne $fqdn) {
	    $HaveInconsistent = 1;
	    $Inconsistent{$ipaddr} = $fqdn;
	}
	if (!&CouldBeIPAddr($ipaddr)) {
	    $HaveBadIP = 1;
	    $BadIP{$ipaddr} = $fqdn;
	}
    }
    close(REVZONE);
    printf("Done with $revzonefile\n");
}


#
#	Find out if it could be an IP address...
#
#	NB.	Doesn't catch problems trailing digits on the 
#		last octet, e.g., 128.223.32.115e won't be 
#		detected.
#	
#
#

sub CouldBeIPAddr {
    local($ipaddr) = @_;
    @octets = split(/[. ]/,$ipaddr);
    if ($#octets != 3) {			# count from 0
	return(0);
    } 
    if ($ipaddr =~ /(\d+\.\d+\.\d+\.\d+)/) {
	return(1);
    } else {
	return(0);
    }
}

#
#	Handle Glue and other A records for which we don't have 
#	in-addr.arpa authority.
#
						 
sub NotOurSubnet {
    local($ipaddr) = @_;
    @addr = split(/[. ]/,$ipaddr);
    $subnet = $subnetmask & $addr[2];
    if (($addr[0] == 128) &&
	($addr[1] == 223) && 
	($subnet != 4)    &&
	($subnet != 6)    &&
	($subnet != 8)    &&
	($subnet != 90)) {
	return(0);
    } else {
	return(1);
    }
}
						 
						 
#
#	These names are special cases...
#
						 
sub SpecialCase {
    local($name) = @_;
    $name =~ y/A-Z/a-z/;			# canonicalize to lower...
    if ($name eq "localhost") {
	return(1);
    } else {
	return(0);
    }
}
						 
#
#	Do this if A Record
#
#
						 
sub HandleARecord {
    local($name,$domain,$ipaddr) = @_;
    if ($name eq "") {
	$name = $lastname;
    } else {
	$lastname = $name;
    }
    $fqdn = sprintf("%s.%s.",$name,$domain);
    chop($fqdn);
    $IPAddrs{$ipaddr} = $fqdn;
    $ZoneHaveSeen{$ipaddr} = $ipaddr;
    if ($DEBUG) {
	printf("zone:\t%s,%s\n",$fqdn,$ipaddr);
    }
}