#!/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);