#!/usr/local/bin/perl
#
# Copyright (c) 1993, 1994 by Silicon Systems Inc.
# 
# Permission to use, copy, modify, and distribute this software for any
# purpose with or without fee is hereby granted, provided that this
# copyright notice appear in all copies, and that the name of Silicon
# Systems Inc. not be used in advertising or publicity pertaining to
# distribution of the document or software without specific,
# written prior permission.
# 
# THE SOFTWARE IS PROVIDED "AS IS" AND SILICON SYSTEMS INC. DISCLAIMS ALL
# WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES
# OF MERCHANTABILITY AND FITNESS.   IN NO EVENT SHALL SILICON SYTEMS
# INC. BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL
# DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR
# PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS
# ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
# SOFTWARE.
#
# Author: Don Lewis <gdonl@gv.ssi1.com>
#
# This perl script reads the /etc/named.boot file to find all the zone
# files.  It reads the zone files and produces a report listing the
# hosts sorted by both name and address in a format similar to that of
# /etc/hosts (the difference being that this report contains multiple
# address fields for multi-homed hosts).  This script checks that the
# A RRs and PTR RRs match up, and also knows about CNAME RRs.
#
# This script is fairly dependent on having a complete set of the zone
# files for a domain (both forward and reverse) accessable.
#
#  $Id: dnsrpt,v 1.1.1.1 1999/10/04 22:25:54 wsanchez Exp $
#

sub getnonblank {
    local ($line, $instring, $inparens, $saweof);
    local ($handle) = @_;

    $instring = 0;
    $inparens = 0;
    $saweof = 0;

    do {
	$line = '';
	do {
	    $c = getc($handle);
	    if ($c eq ';') {
		do {
		    $c = getc($handle);
		    if ($c eq '') {
			last;
		    }
		} while ($c ne "\n");
	    }
	    if ($c eq '') {
		$saweof = 1;
		last;
	    } elsif ($c eq ';') {
		$line .= $c;
	    } else {
		$line .= $c;
	    }
	    if ($instring) {
		if ($c eq '"') {
		    $instring = 0;
		}
	    } else {
		if ($c eq '"') {
		    $instring = 1;
		} elsif ($c eq '(') {
		    $inparens = 1;
		} elsif ($c eq ')') {
		    $inparens = 0;
		}
	    }
	} while ( $inparens || $c ne "\n" );
    } while ((! $saweof) && (split(/[ \t\n]/, $line) == 0));
    return($line);
}

sub parsezone {
    local ($origin) = $_[0];
    local ($zonefile) = $_[1];
    local ($zonedata);
    local ($owner, $lcowner);
    local ($hostname, $lchostname);
    
    if ($origin ne '.') {
	$origin .= '.';
    }

    if ($directory ne '') {
	$zonefile = $directory . '/' . $zonefile;
    }

    if (! -f $zonefile) {
	die "zone file '" . $zonefile . "' for zone '" . $origin .
	    "' is not a file\n";
    }
    if (open(zone, $zonefile) != 1) {
	die "can't open zone file '" . $zonefile . "' for zone '" . $origin .
	    "'\n";
    }

    while ($zonedata = &getnonblank(zone)) {
	split(/[ \t\n]+/, $zonedata);
	if ($_[0] eq '$ORIGIN') {
	    $origin = $_[1];
	} else {
	    if ($_[0] ne '') {
		if ($_[0] eq '@') {
		    $owner = $origin;
		} elsif ($_[0] =~ /.*\.$/) {
		    $owner = $_[0];
		} elsif ($origin eq '.') {
		    $owner = $_[0] . '.';
		} else {
		    $owner = $_[0] . '.' . $origin;
		}
	    }
	    ($lcowner = $owner) =~ y/A-Z/a-z/;
	    shift;      # skip owner
	    if ($_[0] =~ /^[0-9]+$/) {
		shift;  # skip ttl
	    }
	    if ($_[0] ne 'IN') {
		print "skipping non-IN class RR ", $zonedata;
		next;
	    }
	    shift;      # skip class
	    if ($_[0] eq 'A') {
		if ($addrtohost{$_[1]}) {
		    local (@tmpvar);
		    foreach $var (split(/ /, $addrtohost{$_[1]})) {
			$tmpvar{$var} = 'junk';
		    }
		    if (! $tmpvar{$lcowner} ) {
			$addrtohost{$_[1]} .= " " . $owner;
		    }
		} else {
		    $addrtohost{$_[1]} = $owner;
		}
		if ($hosttoaddr{$lcowner}) {
		    local (@tmpvar);
		    foreach $var (split(/ /, $hosttoaddr{$lcowner})) {
			$tmpvar{$var} = 'junk';
		    }
		    if (! $tmpvar{$_[1]} ) {
			$hosttoaddr{$lcowner} .= " " . $_[1];
		    }
		} else {
		    $hosttoaddr{$lcowner} = $_[1];
		}
	    } elsif ($_[0] eq 'CNAME') {
    
		if ($_[1] =~ /.*\.$/) {
		    $hostname = $_[1];
		} elsif ($origin eq '.') {
		    $hostname = $_[1] . '.';
		} else {
		    $hostname = $_[1] . '.' . $origin;
		}
		($lchostname = $hostname) =~ y/A-Z/a-z/;
    
		if ($hosttocname{$lchostname}) {
		    $hosttocname{$lchostname} .= " " . $owner;
		} else {
		    $hosttocname{$lchostname} = $owner;
		}
	    } elsif ($_[0] eq 'PTR') {
		if ($ptrtohost{$lcowner}) {
		    $ptrtohost{$lcowner} .= " " . $_[1];
		} else {
		    $ptrtohost{$lcowner} = $_[1];
		}
	    }
	}
    }

    close(zone);
}

sub cmpaddr {
    local (@addr1) = split(/\./, $a);
    local (@addr2) = split(/\./, $b);

    for ($i = 0; $i < 4; $i++) {
	if ($addr1[$i] != $addr2[$i]) {
	    return($addr1[$i] - $addr2[$i]);
	}
    }
    return(0);
}

if (open(bootfile, "</etc/named.boot") != 1) {
    die "can't open /etc/named.boot\n";
}

while ($cmd = &getnonblank(bootfile)) {
    split(/[ \t\n]+/, $cmd);
    if ($_[0] eq 'directory') {
	$directory = $_[1];
    } elsif ($_[0] eq 'primary') {
	&parsezone( $_[1], $_[2]);
    } elsif ($_[0] eq 'secondary') {
	&parsezone( $_[1], $_[$#_]);
    }
}

foreach $addr (keys(%addrtohost)) {
    $ptr = $ptrtohost{ join('.', reverse(split(/\./, $addr))) .
	'.in-addr.arpa.' };
    ($lcptr = $ptr) =~ y/A-Z/a-z/;
    $hostname = $addrtohost{$addr};
    @hostname = split(/ /, $hostname);
    ($lchostname = $hostname) =~ y/A-Z/a-z/;
    @lchostname = split(/ /, $lchostname);
    @unmatched = ();

    if (! $ptr ) {
	print "address ", $addr,
	    " (", $hostname, ") has no matching PTR record\n";
    } else {
	foreach (@lchostname) {
	    if ( $lcptr ne $_ ) {
		push( @unmatched, shift( @hostname ) );
	    }
	}
	if ( $#lchostname == $#unmatched ) {
	    print "address ", $addr, " has name ", $hostname, " and PTR ",
		$ptr, "\n";
	} elsif ( $#unmatched > 1 ) {
	    print "address ", $addr, " has extra names ",
		join(' ', @unmatched), "\n";
	} elsif ( $#unmatched > 0 ) {
	    print "address ", $addr, " has extra name ", $unmatched[0], "\n";
	}
    }
}

foreach $ptr (keys(%ptrtohost)) {
    $hostname = $ptrtohost{$ptr};
    ($lchostname = $hostname) =~ y/A-Z/a-z/;
    @parts = split(/\./, $ptr);

    if ($parts[$#parts] =~ /^arpa$/i && $parts[$#parts - 1] =~ /^in-addr$/i) {

	if ( ! $hosttoaddr{$lchostname} ) {
	    if ( $ptrtohost{$lchostname} ) {
		if ( $ptrtohost{$lchostname} ne $ptr ) {
		    print "mismatched PTRs for ", $ptr, " and ", $hostname,
			"\n";
		}
	    } else {
		print "PTR ", $ptr, " refers to unknown host ", $hostname, "\n";
	    }
	} else {
	    $addr = $parts[3] . '.' . $parts[2] . '.' . $parts[1] .
		'.' . $parts[0];

	    if ( ! $addrtohost{$addr} ) {
		print "PTR ", $ptr, " to ", $hostname,
		    " has an invalid address\n";
	    } elsif ( $hostname ne $addrtohost{$addr} ) {
# duplicate warning
#		print "PTR ", $ptr, " to ", $hostname, " has address of host ",
#		    $addrtohost{$addr}, "\n";
	    }
	}
    } elsif ( ! $ptrtohost{$lchostname} ) {
	print "network PTR ", $ptr, " to ", $hostname, " is dangling\n";
    }
}

print "                        Hosts Sorted by Name\n\n\n";

foreach $host (sort(keys(%hosttoaddr))) {
    $count = 0;
    foreach $addr (split(/ /, $hosttoaddr{$host})) {
	$count++;
	printf("%-16s", $addr);
    }
    for (; $count < 2; $count++) {
	printf("%-16s", "");
    }
    printf "  %s", $host;
    if ($hosttocname{$host}) {
	print ' ', $hosttocname{$host};
    }
    print "\n";
}

print "\f                        Hosts Sorted by Address\n\n\n";

foreach $addr (sort(cmpaddr keys(%addrtohost))) {
    printf "%-32s  %s", $addr, $addrtohost{$addr};
    if ($hosttocname{$addrtohost{$addr}}) {
	print ' ', $hosttocname{$addrtohost{$addr}};
    }
    print "\n";
}
