#!/usr/local/bin/perl # hostscript - reads domain RRs, does sanity checking, generates table # (C)opyright 1996 The LeftBank Operation, Ofer Inbar # # $Id: hostscript,v 1.22 1998/01/27 22:39:42 cos Exp $ # # usage: hostscript [-option] inputfiles # hosts - output a sorted hosts table in /etc/hosts format (default) # equiv - output a hosts.equiv file for all hosts, simple names & FQDNs # ptr - output PTR records for the A records in the input # cw - output a sendmail.cw file for all hosts and their aliases # yp - find inconsistencies between the input data and yp hosts # mx - list xterms with MX RRs and non-xterms without MX RRs # # recommended usage # for debugging: hostscript -none *.hosts *.rev - # for host table: hostscript *.hosts *.rev > /etc/hosts # for *.rev file: hostscript -ptr .hosts > .rev # # warnings are sent to stderr, and a hosts table to stdout # debug messages begin with "DEBUG" and are sent to stderr # fatal errors are sent to stderr followed by "aborted" on a separate line # ----------------------------------------------------------------------------- # some assumptions this script makes: # - a CNAME always comes after the A record for the host it points at # - a PTR always comes after the A record for the host it points at # - we don't want multiple IPs for the same name, so warn about those # ----------------------------------------------------------------------------- # warnings and errors from first pass: # * invalid record: looked like an RR but didn't parse # * no hostname: first RR read had no hostname field # + unusual net: IP number not in specified network # - multiple IPs: multiple A records for same hostname # * IP# conflict: multiple A records with same IP number # - unusual MX: MX unusual priority or mailhost isn't hop # - early CNAME: CNAME cname points to (as yet) unknown host # + duplicate CNAME: CNAME <1> <2> cname points to both hosts 1 and 2 # * host is CNAME: CNAME cname host has an A record # - invalid PTR: PTR record didn't parse correctly # (either name didn't end in .ksr.com. or IP wasn't two numbers with one .) # + PTR wrong name: PTR points to different host than A # + PTR wrong IP #: PTR and A have different IPs for host # + PTR w/o host: no A records exist for this host or ip # * duplicate PTR: two PTRs for the same IP number # # warnings and errors from second pass: # + CNAME / A: CNAME A host has CNAME and A (illegal) # + CNAME / MX: CNAME MX host has CNAME and MX (illegal) # + CNAME chain:

->

->

chain of CNAME references (illegal) # - xterm has MX: MX an NCD xterm has an MX record # - missing MX: no MX record for host (not an xterm) # + missing PTR: no PTR from this ip to this host # # * fatal error, no host table generated # + warning, don't trust the host table unles you know what's going on # - informational warning, you can trust the host table # ----------------------------------------------------------------------------- $domain = "leftbank.com."; $network = "192.31.227"; $mailhosts = "lbo sneaker zax"; eval "print STDERR \$die='Unknown parameter $1\n' if !defined \$$1; \$$1=\$';" while ($ARGV[0] =~ /^(\w+)=/ && shift(@ARGV)); exit 255 if $die; # process any FOO=bar switches $domain = "\L$domain"; $mailhosts = "\L $mailhosts "; $output = "-hosts"; ($ARGV[0] =~ /^-\w+$/) && ($output = shift); #warn "output type: $output\n"; $_ = $network; $netclass = "none"; $netclass = "A" if /^\d+$/; $netclass = "B" if /^\d+\.\d+$/; $netclass = "C" if /^\d+\.\d+\.\d+$/; die "Invalid network: $network\n" if ($netclass eq "none"); #warn "netclass: $netclass\n"; while(<>) { $comment = $prev; $prev = $_; # store previous line in case of comment s/;.*//; # strip off comments to avoid spuriously matching /\sin\s/ tr/A-Z/a-z/; # canonicalize to lowercase /^debug\s*$/i && ($debug = 1-$debug); /^quit\s*$/i && die "QUIT"; if ($debug) { /^show host\s+(.*)/i && warn "DEBUG ip{$1}=$ip{$1}:mx{$1}=$mx{$1}\n" . "DEBUG cptr{$1}=$cptr{$1}:cnames{$1}=$cnames{$1}\n"; /^show ip\s+(.*)/i && warn "DEBUG name{$1}=$name{$1}:ptr{$1}=$ptr{$1}\n"; /^help$/i && warn "DEBUG show host list ip, mx, cptr, cnames\n" . "DEBUG show ip list name, ptr\n" . "DEBUG debug toggle debug mode\n" . "DEBUG help this help message\n" . "DEBUG quit abort this run\n" . "DEBUG finish reading stdin and continue\n"; } #next unless /\sin\s/; # skip blank lines, SOA fields, etc. next if /^\s*$/; next if /^;/; next if /^debug\s*$/i; next if /^help\s*$/i; next if /^show host\s+/i; next if /^show ip\s+/i; /^(\S*)\s+in\s+(\w+)\s+(\S.*)\s*$/ || "invalid record: $_\n"; next if $1 eq "localhost" && $2 eq "a" && $3 eq "127.0.0.1"; next if $1 eq "1" && $2 eq "ptr" && $3 eq "localhost."; $host = $1 unless $1 eq ""; # die "no hostname: $_ \n" if $host eq ""; # $host = "" if $host eq "@"; $rrtype = $2; $rrdata = $3; RRSWITCH: { $bad = $known = ""; if ($rrtype eq "a") { $rrdata =~ /\d+\.\d+\.\d+\.\d+/; $ip = $&; warn "unusual net: $host \t$ip\n" unless ($ip =~ /^$network\./); warn "multiple IPs: $host \t$ip\t$ip{$host}\n" if defined $ip{$host}; die "IP# conflict: $ip\t$host \t$name{$ip}\naborted\n" if defined $name{$ip}; $ip{$host} .= "$ip "; $name{$ip} = ($host eq "@" ? "" : $host); $comment{$host} = "\t# $1" if $comment =~ /^;\s+(.*)$/; $known=1; last RRSWITCH; } if ($rrtype eq "ptr") { $rrdata =~ s/^([\-\w\d]*)\.*$domain\s*$/$1/ || $rrdata =~ s/^([\-\w\d\.]*\w)\.*$domain\s*$/$1/ || ($bad=1); if ($netclass eq "C") {$host =~ s/^(\d+)$/$network.$1/ || ($bad=1)} if ($netclass eq "B") {$host =~ s/^(\d+)\.(\d+)$/$network.$2.$1/ || ($bad=1)} if ($netclass eq "A") {$host =~ s/^(\d+)\.(\d+)\.(\d+)$/$network.$3.$2.$1/ || ($bad=1)} $ip = $host; $host = $rrdata; # PTRs are backwards if ($host eq "") {$host = $domain}; if ($bad) {warn "invalid PTR: $_";} else { if (defined($ip{$host})) { warn "PTR wrong name: $ip\t$host \tA: $name{$ip}\n" unless $name{$ip} eq $host; warn "PTR wrong IP #: $ip\t$host \tA: $ip{$host}\n" if (index($ip{$host},$ip) < 0); } else { warn "PTR w/o host: $ip\t$host\tA: $name{$ip}\n"; } die "duplicate PTR: $ip\t$host \t$ptr{$ip}\naborted\n" if defined($ptr{$ip}); $ptr{$ip} = $host; $rrdata = $ip; # so debug prints the correct information } $known=1; last RRSWITCH; } if ($rrtype eq "cname") { $rrdata =~ s/\.$domain\s*$//; warn "duplicate CNAME:$host \tCNAME\t$rrdata\t$cptr{$host}\n" if defined $cptr{$host}; warn "early CNAME: $host \tCNAME\t$rrdata\n" unless defined $ip{$rrdata}; die "host is CNAME: $host \tCNAME\t$rrdata\tA $ip{$host}\naborted\n" if defined $ip{$host}; $cnames{$rrdata} .= " $host"; $cptr{$host} = $rrdata; $known=1; last RRSWITCH; } if ($rrtype eq "mx") { $rrdata =~ /(\d+)\s+([\.\w-]+)/; $pref = $1; $mx = $2; $mx =~ /^([\w-]+)\.(.*)/; $mailhost = $1; $mxdomain = $2; warn sprintf("unusual MX: %-14s MX %3s \t%s\n", $host, $pref, $mx) unless (($mxdomain eq $domain) && ($pref=="10" || $pref=="20") && ($mailhosts =~ / $mailhost /)); $mx{$host} = $mx; $known=1; last RRSWITCH; } $known=1 if $rrtype eq "soa" || $rrtype eq "ns" || $rrtype eq "txt"; } warn "unrecognized RR: $_" unless $known; warn "DEBUG host: $host\t$rrtype\trrdata: $rrdata\n" if $debug; } # we now have the following tables built: # $ptr{IP number} IP -> hostname, from PTR records # $name{IP number} IP -> hostname, from A records # $ip{hostname} hostname -> IP, from A records # $cnames{hostname} hostname -> cnames, from CNAME records # $mx{hostname} hostname -> mailserver, from MX records # $cptr{hostname} hostname alias -> canonical name, from CNAME records # $comment{hostname} hostname -> optional comment # make sure no name has both a CNAME record and other data foreach $host (keys %cptr) { warn "CNAME / A: $host \tCNAME $cptr{$host} \tA $ip{$host}\n" if defined $ip{$host}; warn "CNAME / MX: $host \tCNAME $cptr{$host} \tMX $mx{$host}\n" if defined $mx{$host}; warn "CNAME chain: $host -> $cptr{$host} -> $cptr{$cptr{$host}}\n" if defined $cptr{$cptr{$host}}; } # check for PTR records for all IP numbers unless ($output eq "-ptr") { while (($host, $_) = each %ip) { chop; @ipnums = split; foreach $ip (@ipnums) { warn "missing PTR: $ip\t$host \t$_\n" unless $ptr{$ip} eq $host; } warn "DEBUG host:$host \tipnums:$_\n" if $debug; } } if ($output eq "-yp") { warn "building yp host tables...\n"; open (HOSTS, "ypcat hosts |"); while() { /($network\.\d+\.\d+)\s+(.+)\s*$/ || (warn "bad host entry: $_", next); $ip = $1; @names = split(/\s+/,$2); $host = shift(@names); $ypname{$ip} = $host; $ypip{$host} = $ip; $ypcnames{$host} = join(" ",@names) if (@names > 0); $ypcnames = join(" ",@names) if $debug; warn "DEBUG adding $ip $host \t$ypcnames\n" if $debug; } warn "yp host tables done, generating inconsistency list on stdout...\n"; foreach $ip (sort byip keys %ypname) { $host = $ypname{$ip}; if (defined($name{$ip})) { print "yp name mismatch: $ip \t$host \tdns: $name{$ip}\n" unless $host eq $name{$ip}; } else { print "missing from dns: $ip \t$host\n"; } } foreach $ip (sort byip keys %name) { $host = $name{$ip}; if (defined($ypname{$ip})) { print "yp name mismatch: $ip \t$host \typ: $ypname{$ip}\n" unless $host eq $ypname{$ip}; } else { print "missing from yp : $ip \t$host\n"; } } foreach $host (keys %cnames, keys %cnames) { @ypcnames = split(/\s/,$ypcnames{$host}); @dnscnames = split(/\s/,$cnames{$host}); shift @dnscnames; warn "checking $host : $ypcnames{$host} : $cnames{$host} :\n" if $debug; foreach $cname (@ypcnames) { print "missing CNAME : $host \t$cname\n" unless $cnames{$host} =~ /\b$cname\b/; } foreach $cname (@dnscnames) { print "missing yp cname: $host \t$cname\n" unless $ypcnames{$host} =~ /\b$cname\b/; } } warn "inconsistency list done.\n"; } if ($output eq "-ptr") { warn "generating PTR records on stdout...\n"; foreach $_ (sort byip keys %name) { ($a,$b,$c,$d) = split(/\./); $samenet = ("$a.$b.$c.$d" =~ /^$network\./); warn "unusual net: $name{$_} \t$_\n" unless $samenet; $fqdn = $name{$_} . ".$domain"; $fqdn =~ s/^\@//; $fqdn =~ s/^\.//; if ($netclass eq "C") {print "$d\t\tIN\tPTR\t$fqdn\n" if $samenet} if ($netclass eq "B") {print "$d.$c\t\tIN\tPTR\t$fqdn\n" if $samenet} if ($netclass eq "A") {print "$d.$c.$b\t\tIN\tPTR\t$fqdn\n" if $samenet} } warn "PTR records done.\n"; } elsif ($output eq "-mx") { warn "generating list of xterms with MX RRs ...\n"; open (ETHERS, "ypcat ethers |"); while () { next unless /^0:0:A7:\S+\s+([\w\-]+)\s*$/i; warn "DEBUG $1\t$mx{$1}\t$_" if $debug; print "xterm has MX: $1 MX $mx{$1}\n" if defined $mx{$1}; $mx{$1} = "xterm - no mxhost"; } close ETHERS; warn "generating list of non-xterms without MX RRs ...\n"; while (($host, $_) = each %ip) { print "missing MX: $host\n" unless defined($mx{$host}); } warn "done.\n"; } elsif ($output eq "-equiv") { warn "generating hosts.equiv list on stdout ... \n"; chop ($dom = $domain); foreach $ip (sort byip keys %name) { print "$name{$ip} +\n$name{$ip}.$dom +\n" } warn "hosts.equiv list done.\n"; } elsif ($output eq "-cw") { warn "generating sendmail.cw list on stdout ... \n"; chop ($dom = $domain); foreach $ip (sort byip keys %name) { print "$name{$ip}\n$name{$ip}.$dom\n"; foreach $cname (split(/\s+/,$cnames{$name{$ip}})) { print "$cname\n$cname.$dom\n" if $cname } } warn "sendmail.cw list done.\n"; } elsif ($output eq "-hosts") { warn "generating hosts table on stdout ... \n"; foreach $ip (sort byip keys %name) { print "$ip\t$name{$ip}$cnames{$name{$ip}}$comment{$name{$ip}}\n" } warn "hosts table done.\n"; } sub byip { @ip1 = split(/\./,$a); @ip2 = split(/\./,$b); if ($ip1[2] == $ip2[2]) { $ip1[3] <=> $ip2[3]; } else { $ip1[2] <=> $ip2[2]; } } __END__ sub by_ipaddr { $c = (&ipaddr_to_str($a) cmp &ipaddr_to_str($b)); warn "$c\t$a\t$b\n"; (&ipaddr_to_str($a) cmp &ipaddr_to_str($b)); # || ($a cmp $b); } sub ipaddr_to_str { local($_) = @_; local($r) = $ipaddr_to_str{$_}; unless ($r) { $r = $ipaddr_to_str{$_} = pack("C*", split(/\./, $_, 4)); } }