4.4BSD/usr/src/contrib/bind-4.9/contrib/dnsparse/dns2hosts
#!/usr/bin/perl
#
# $Id: dns2hosts,v 2.0 90/09/11 11:07:27 hakanson Rel $
#
# Convert DNS master (RFC-1035) files to /etc/hosts format.
# Marion Hakanson (hakanson@cse.ogi.edu)
# Oregon Graduate Institute of Science and Technology
#
# Copyright (c) 1990, Marion Hakanson.
#
# You may distribute under the terms of the GNU General Public License
# as specified in the README file that comes with the dnsparse kit.
#
# Other than "-d defdom" (to specify a default domain different from that
# found in /etc/resolv.conf), command line args are DNS master file names,
# as described for the dns_init() subroutine in dnsparse.pl. The output
# is printed to the standard output stream.
#
# This Perl program looks at the A and CNAME records encountered in the
# DNS master files, and produces an /etc/hosts file which includes the
# same host-to-address, address-to-host, and alias-to-host mappings.
# Note that some such mappings are necessarily dependent on the order in
# which they are encountered in the DNS master files. The DNS system
# is not sensitive to the order, but /etc/hosts files are, thus one
# should take care that the command line arguments are presented in
# the same order from one invocation to the next, if one expects the
# two resulting output files to be comparable.
#
# A large part of the complication of this program is due to the notion
# of the "default domain" that the BIND implementation of a DNS resolver
# provides. Namely, this allows a user to type a short alias and have
# it match to a fully qualified domain name (FQDN) somewhere in the
# machine's default domain. E.g. given "admin" and a default domain
# of "cse.ogi.edu", the resolver will try "admin.cse.ogi.edu" -- if no
# match is found, it will try "admin.ogi.edu" (but it will not try
# just the first-level domain, "admin.edu").
#
# Thus, the aliases and canonical names are set up in the output file
# to mimic the behavior of a system using the BIND resolver. Among
# other things, this means that host numbers will always be resolved
# to the FQDN of the host. Instead of using PTR records to provide
# the number-to-name mappings, this program assumes that the PTR records
# would be automatically generated from the A records. It further assumes
# that a host is considered to have a single, unique FQDN, and that all
# of its addresses (interfaces) will resolve back to that FQDN. To my
# knowledge, this is the only area where the output hosts file may not
# quite mimic the behavior of the BIND resolver. I would be willing to
# consider a design change if demand warrants it.
do 'getopts.pl'; die "$@, aborted" if $@;
do 'dnsparse.pl'; die "$@, aborted" if $@;
do Getopts('d:');
# Get default domain
if ( defined($opt_d) ) {
$defdom = $opt_d;
} elsif ( open(F, '</etc/resolv.conf') ) {
while ( <F> ) {
$defdom = $1 if ( /\s*domain\s+([^\s]+)/ );
}
close(F);
}
# Set up domains the resolver would try
if ( defined($defdom) ) {
$defdom =~ s/^\.//; # strip leading dot
$defdom = do dns_makefqdn($defdom,'');
@domparts = split(/\./,$defdom); # escaped dots?
while ( $#domparts > 0 ) {
push(@defdoms, join('.',@domparts));
shift(@domparts);
}
}
# Parse the dns db file, collecting names & addrs.
do dns_init(@ARGV);
open(OFILE, ">&STDOUT") || die "Cannot dupe 'STDOUT', aborted";
# Treat loopback address specially, since the DNS is usually set
# up to resolve loopback to "localhost".
$hostsbyaddr{'127.0.0.1'} = 'localhost';
push(@addrs,'127.0.0.1');
rr: while ( (@rr = do dns_getrr()) && @rr ) {
($domain, $ttl, $class, $type, @data) = @rr;
next rr if ( $class ne 'IN' );
case: {
if ( $type eq 'A' ) {
$list = '';
if ( defined($hostsbyaddr{$data[0]}) ) {
$list = $hostsbyaddr{$data[0]} . $dns'delim;
} else {
push(@addrs,$data[0]);
}
$list .= $domain;
$hostsbyaddr{$data[0]} = $list;
$fqdnseen{$domain}++;
last case;
} elsif ( $type eq 'CNAME' ) {
$list = '';
if ( defined($cnamesbyhost{$data[0]}) ) {
$list = $cnamesbyhost{$data[0]} . $dns'delim;
}
$list .= $domain;
$cnamesbyhost{$data[0]} = $list;
$fqdnseen{$domain}++;
last case;
}
}
}
# Go through the names encountered, building abbreviations,
# and attaching them to their fully-qualified forms.
while ( ($fqdn,$cnt) = each %fqdnseen ) {
#print STDERR "$fqdn seen $cnt times\n";
foreach $truncdom (@defdoms) {
$abbrev = $fqdn;
# don't generate abbrev's which wouldn't resolve
if ( $abbrev =~ s/\.$truncdom// ) {
# now we mimic what the resolver would do
foreach $dom (@defdoms) {
if ( defined($fqdnseen{"$abbrev.$dom"}) ) {
$abbrevsbyhost{"$abbrev.$dom"} .= $abbrev . $dns'delim;
#print STDERR "$abbrev -> $abbrev.$dom\n";
# stop with the first one
last;
}
}
}
}
}
# for debugging
#while ( ($key,$val) = each(%abbrevsbyhost) ) {
# print STDERR "|$key";
# @vals = split(/$dns'delim/,$val);
# foreach $val (@vals) {
# print STDERR "|$val";
# }
# print STDERR "|\n";
#}
# Write out the hosts file.
# First write out some commentary about the input source, etc.
$datefmt = '%02d/%02d/%02d %02d:%02d:%02d';
($sec,$min,$hour,$mday,$mon,$year,@rest) = localtime();
$mon++; # 0 == January
$date = sprintf($datefmt, $year, $mon, $mday, $hour, $min, $sec);
chop($host = `hostname`);
print OFILE "#\n";
print OFILE "# Host table (/etc/hosts)\n";
print OFILE "#\n";
print OFILE "# Created on host '$host' at $date\n";
print OFILE "#\n";
print OFILE "# Abbreviations set up for default domain '$defdom'\n";
print OFILE "#\n";
print OFILE "# THIS TABLE WAS GENERATED AUTOMATICALLY.\n";
print OFILE "# ANY CHANGES OR ADDITIONS TO IT WILL BE\n";
print OFILE "# LOST WHEN THE NEXT UPDATE TAKES PLACE.\n";
print OFILE "#\n";
print OFILE "# Built from the following DNS master files:\n";
print OFILE "#\n";
print OFILE "# ModTime\t\tFile\n";
print OFILE "#\tOrigin\n";
while ( $#ARGV >= $[ ) {
($file,$origin) = do dns_commasplit(shift(@ARGV));
$origin = '.' unless ( $origin ); # root
if ( $file eq '' || $file eq '-' ) {
$file eq 'Standard Input';
@ARGV = (); # STDIN is last
$date = 'See Above';
} else {
if ( -r $file ) {
($dev,$ino,$mode,$nlink,$uid,$gid,$rdev,$size,
$atime,$mtime,$ctime,$blksize,$blocks) = stat(_);
($sec,$min,$hour,$mday,$mon,$year,@rest) = localtime($mtime);
$mon++; # 0 == January
$date = sprintf($datefmt, $year, $mon, $mday, $hour, $min, $sec);
} else {
$date = "Not Found";
$origin = $!; # the error message
}
}
print OFILE "# $date\t$file\n";
print OFILE "#\t$origin\n";
}
print OFILE "#\n";
print OFILE "#\n";
# Build up an output record and then write it out using a
# format which may break a long record into multiple lines,
# all starting with the same address and canonical name.
format OFILE =
@<<<<<<<<<<<<<< ~^<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
$addr, $names
.
$: = ' '; # OK to break $names on a blank
foreach $addr ( @addrs ) {
@hosts = split(/$dns'delim/, $hostsbyaddr{$addr});
%nameseen = ();
$names = '';
$canon = $hosts[0]; # save the first one
foreach $host ( @hosts ) {
unless ( defined($nameseen{$host}) ) {
$nameseen{$host} = 1;
$names .= " $host";
if ( defined($abbrevsbyhost{$host}) ) {
@abbrevs = split(/$dns'delim/, $abbrevsbyhost{$host});
foreach $abbrev ( @abbrevs ) {
unless ( defined($nameseen{$abbrev}) ) {
$nameseen{$abbrev} = 1;
$names .= " $abbrev";
}
}
}
}
if ( defined($cnamesbyhost{$host}) ) {
@aliases = split(/$dns'delim/, $cnamesbyhost{$host});
foreach $alias ( @aliases ) {
unless ( defined($nameseen{$alias}) ) {
$nameseen{$alias} = 1;
$names .= " $alias";
if ( defined($abbrevsbyhost{$alias}) ) {
@abbrevs = split(/$dns'delim/, $abbrevsbyhost{$alias});
foreach $abbrev ( @abbrevs ) {
unless ( defined($nameseen{$abbrev}) ) {
$nameseen{$abbrev} = 1;
$names .= " $abbrev";
}
}
}
}
}
}
}
$names =~ s/^ $canon *//; # we'll put it back below
do {
#print STDERR "|$names|\n";
$names = "$canon $names";
write(OFILE); # has side effect of shortening $names
} while ( $names );
}
exit(0);