home *** CD-ROM | disk | FTP | other *** search
/ PC-Online 1996 May / PCOnline_05_1996.bin / linux / source / n / bind / bind-4.001 / bind-4~ / bind-4.9.3-BETA9 / contrib / dnswalk-1.8.1 / dnswalk < prev    next >
Text File  |  1994-07-01  |  14KB  |  447 lines

  1. #!/usr/local/bin/perl
  2. # dnswalk    Walk through a DNS tree, pulling out zone data and
  3. # dumping it in a directory tree
  4. #
  5. # $Id: dnswalk,v 1.9 1994/06/27 14:13:24 barr Exp barr $
  6. #
  7. # check data collected for legality using standard resolver
  8. #
  9. # invoke as dnswalk domain > logfile
  10. # Options:
  11. #    -r    Recursively descend subdomains of domain
  12. #    -f    Force a zone transfer, ignore existing axfr file
  13. #    -i    Suppress check for invalid characters in a domain name.
  14. #    -a    turn on warning of duplicate A records.
  15. #    -d    Debugging
  16. #    -m    Check only if the domain has been modified.  (Useful only if
  17. #          dnswalk has been run previously.)
  18. #    -F    Enable "facist" checking.  (See man page)
  19. #    -l    Check lame delegations
  20. #    -D dir  Use 'dir' as base directory for saved axfr files
  21.  
  22. require "getopts.pl";
  23.  
  24. do Getopts("D:rfiadmFl");
  25.  
  26. # Where all zone transfer information is saved.  You can change this to
  27. # something like /tmp/dnswalk if you don't want to clutter up the current
  28. # directory
  29. if ($opt_D) {
  30.     $basedir = $opt_D;
  31. } else {
  32.     $basedir = ".";
  33. }
  34. ($domain = $ARGV[0]) =~ tr/A-Z/a-z/;
  35. if ($domain !~ /\.$/) {
  36.     die "Usage: dnswalk domain\ndomain MUST end with a '.'\n";
  37. }
  38. if (! -d $basedir) {
  39.     mkdir($basedir,0777) || die "Cannot create $basedir: $!\n";
  40. }
  41.  
  42. &dowalk($domain);
  43.  
  44. exit;
  45.  
  46. sub dowalk {
  47.     local (@subdoms);
  48.     local (@sortdoms);
  49.     local ($domain)=$_[0];
  50.     $modified=0;
  51. #    ($file,@subdoms)=&doaxfr($domain);  /* perl bug */
  52.     @subdoms=&doaxfr($domain);
  53.     $file=shift(@subdoms);
  54.     if ($file && !($opt_m && !$modified)) {
  55.         &checkfile($file,$domain);
  56.     }
  57.     else {
  58.         print STDERR "skipping...\n";
  59.     }
  60.     @sortdoms = sort byhostname @subdoms;
  61.     local ($subdom);
  62.     if ($opt_r) {
  63.         foreach $subdom (@sortdoms) {
  64.             &dowalk($subdom);
  65.         }
  66.     }
  67. }
  68. # try to get a zone transfer, trying each listed authoratative server if
  69. # if fails.
  70. sub doaxfr {
  71.     local ($domain)=@_[0];
  72.     local (%subdoms)=();
  73.     local ($serial);
  74.     local ($foundsoa)=0;    # attempt to make up for dig's poor 
  75.                 # error handling
  76.     ($path=&host2path($domain)) =~ tr/A-Z/a-z/;
  77.     local(@servers) = &getauthservers($domain);
  78.     &printerr("warning: $domain has only one authoratative nameserver\n") if (scalar(@servers) == 1);
  79.     &printerr("warning: $domain has NO authoritative nameservers!\n") if (scalar(@servers) == 0);
  80.     if ((-f "$basedir/$path/axfr") && (!$main'opt_f)) {
  81.         open(DIG,"<$basedir/$path/axfr") || die "cannot open $basedir/$path/axfr: $!\n";
  82.         while (<DIG>) {
  83.             chop;
  84.             if (/(\d+)\s*; ?serial/) {
  85.                 $serial=$1;
  86.             }
  87.             if (/(\S+)\s*\d+\s+NS/) {
  88.                 $1 =~ tr/A-Z/a-z/;
  89.                 if ((!&equal($1,$domain)) && ( !$subdoms{$1})) {
  90.                     $subdoms{$1}=1;
  91.                 }
  92.             }
  93.         }
  94.         # if there's no serial number in file, assume it is corrupt
  95.         if ($serial) {
  96.             foreach $server (@servers) {
  97.                 $authserno=&getserno($domain,$server);
  98.                 last if ($authserno);
  99.             }
  100.             if ($authserno <= $serial) {
  101.                 print STDERR "Using existing zone transfer info for $domain\n";
  102.                 return ("$basedir/$path/axfr", keys %subdoms);
  103.             }
  104.         }
  105.     }
  106.     &mkdirpath($path);
  107.     SERVER:
  108.     foreach $server (@servers) {
  109.     $foundsoa=0;
  110.         $SIG{'INT'}="nop;";
  111.         print STDERR "Getting zone transfer of $domain from $server...";
  112.         open(DIG,"dig axfr $domain \@$server 2>/dev/null |");
  113.         open(DIGOUT,">$basedir/$path/axfr") || die "cannot open $basedir/$path/axfr: $!\n";
  114.     @subdoms=undef;
  115.         while (<DIG>) {
  116.             if (/(\S+)\s*\d+\s+NS/) {
  117.                 $1 =~ tr/A-Z/a-z/;
  118.                 if ((!&equal($1,$domain)) && ( !$subdoms{$1})) {
  119.                     $subdoms{$1}=1;
  120.                 }
  121.             }
  122.             elsif (/\S+\s*\d+\s+SOA/) {
  123.                 $foundsoa=1;
  124.             }
  125.             print DIGOUT $_;
  126.         }
  127.         if ($? || !$foundsoa) {
  128.             print STDERR "failed.\n";
  129.             close(DIGOUT);
  130.             next SERVER;
  131.         }
  132.         print STDERR "done.\n";
  133.         close(DIGOUT);
  134.         close(DIG);
  135.         last SERVER;
  136.     } # foreach #
  137.     $SIG{'INT'}=undef;
  138.     if ($? || !$foundsoa) {
  139.         print STDERR "All zone transfer attempts of $domain failed\n";
  140.         unlink("$basedir/$path/axfr");
  141.         &rmdirpath($path);
  142.         return undef;
  143.     }
  144.     $modified=1;
  145.     return ("$basedir/$path/axfr", keys %subdoms);
  146. }
  147.  
  148. # returns "edu/psu/pop" given "pop.psu.edu"
  149. sub host2path {
  150.     join('/',reverse(split(/\./,$_[0])));
  151. }
  152.  
  153. # makes sure all directories exist in "foo/bar/baz"
  154. sub mkdirpath {
  155.     local (@path)=split(/\//,$_[0]);
  156.     local ($dir)=$basedir;
  157.     foreach $p (@path) {
  158.     $dir .= "/".$p;
  159.     if (! -d $dir) {
  160.             mkdir($dir, 0777) || die "Cannot mkdir $dir: $!\n";
  161.     }
  162.     }
  163. }
  164.  
  165. # remove empty directories in path
  166. sub rmdirpath {
  167.     local (@path)=split(/\//,$_[0]);
  168.     local (@dirs);
  169.     local ($dir)=$basedir;
  170.     foreach $p (@path) {
  171.       push(@dirs, ($dir .= "/".$p));
  172.     }
  173.     foreach $p (reverse(@dirs)) {
  174.         last if !rmdir($p);
  175.     }
  176. }
  177.  
  178. sub getserno {
  179.     local ($serno)="";
  180.     $SIG{'INT'}="nop;";
  181.     open(DIG,"dig soa $_[0] \@$_[1] 2>/dev/null|");
  182.     while (<DIG>) {
  183.         if (/(\d+)\s*; ?serial/) { 
  184.             $serno=$1;
  185.         }
  186.     }
  187.     close(DIG);
  188.     $SIG{'INT'}=undef;
  189.     return $serno;
  190. }
  191.  
  192.  
  193. sub getauthservers {
  194.     local ($domain)=$_[0];
  195.     local ($master)=&getmaster($domain);
  196.     local ($foundmaster)=0;
  197.     local ($s);
  198.     open(DIG,"dig +noau ns $_[0] 2>/dev/null|");
  199.     local(@servers)=();
  200.     local(@servhash)=();
  201.     while (<DIG>) {
  202.         chop;
  203.     tr/A-Z/a-z/;
  204.         if (/\S+\s+\d+\s+ns\s+(\S+)/) {
  205.         $s=$1;
  206.         if ($s eq $master) {
  207.         $foundmaster=1;   # make sure the master is at the top
  208.         } else {
  209.                 push(@servers,$s) if ($servhash{$s}++<1);
  210.         }
  211.         }
  212.     }
  213.     close(DIG);
  214.     if ($foundmaster) {
  215.     unshift(servers,$master);
  216.     }
  217.     return @servers;
  218. }
  219.  
  220.  
  221. sub getmaster {  # return 'master' server for zone
  222.     local ($master)="";
  223.     $SIG{'INT'}="nop;";
  224.     open(DIG,"dig soa $_[0] 2>/dev/null|");
  225.     while (<DIG>) {
  226.         if (/.*\t\d+\tSOA\t(\S+) /) { 
  227.             $master=$1;
  228.         }
  229.     }
  230.     close(DIG);
  231.     $SIG{'INT'}=undef;
  232.     return $master;
  233. }
  234.  
  235. # open result of zone tranfer and check lots of nasty things
  236. # here's where the fun begins
  237. sub checkfile {
  238.     open(FILE,"<$_[0]") || die "Cannot open $_[0]: $!\n";
  239.     undef $errlist;
  240.     print "Checking $domain\n";
  241.     local (%glues)=();    # look for duplicate glue (A) records
  242.     local ($name, $aliases, $addrtype, $length, @addrs);
  243.     local ($prio,$mx);
  244.     local ($soa,$contact);
  245.     local ($lastns);    # last NS record we saw
  246.     local (@keys);    # temp variable
  247.     $soa=undef;
  248.     $doubledom = $domain . $domain;
  249.     $doubledom =~ s/(\W)/\\\1/g;    # quote string so it's a regexp
  250.     while (<FILE>) {
  251.         chop;
  252.         if (/^;/) {
  253.             if (/(.*[Ee][Rr][Rr][Oo][Rr].*)/) {
  254.                 # print any dig errors
  255.                 print $1 ."\n";
  256.                 next;
  257.             }
  258.         }
  259.         next if /^$/;    # skip blanks
  260.     # check to see if there is a "foo.bar.baz.bar.baz."
  261.     # probably a trailing-dot death.
  262.     if (/$doubledom/) {
  263.         &printerr(" $_: domain occurred twice, forgot trailing '.'?\n");
  264.     }
  265.         split(/\t/);
  266.         # 0=key 2=rrtype 3=value (4=value if 2=MX)
  267.         next if ($_[0] =~ /;/);
  268.         if (($_[0] =~ /[^\*][^-A-Za-z0-9.]/) && (!$opt_i)) {
  269.             &printerr(" $_[0]: invalid character(s) in name\n");
  270.         }
  271.         if ($_[2] eq "SOA") {
  272.             print STDERR 's' if $opt_d;
  273.         if (! $soa) {  # avoid duplicate SOA's.  Argh.
  274.                ($soa,$contact) = $_[3] =~ /(\S+)\s+(\S+)/;
  275.                print "SOA=$soa    contact=$contact\n";
  276.         }
  277.         } elsif ($_[2] eq "PTR") {
  278.             print STDERR 'p' if $opt_d;
  279.             if (scalar((@keys=split(/\./,$_[0]))) == 6 ) {
  280.                 # check if forward name exists, but only if reverse is
  281.                 # a full IP addr
  282.                 # skip ".0" networks
  283.                 if ($keys[0] ne "0") {
  284.                     if (!(($name, $aliases, $addrtype, $length, @addrs)=gethostbyname($_[3])) && !$?) {
  285.                         &printerr(" gethostbyname($_[3]): $!\n");
  286.                     }
  287.                     else {
  288.                         if (!$name) {
  289.                             &printerr(" $_[0] PTR $_[3]: unknown host\n");
  290.                         }
  291.                         elsif (!&equal(($name.'.'),$_[3])) {
  292.                             &printerr(" $_[0] PTR $_[3]: CNAME (to $name)\n");
  293.                         }    
  294.                         elsif (!&matchaddrlist($_[0])) {
  295.                             &printerr(" $_[0] PTR $_[3]: A record not found\n");
  296.                         }
  297.                     }
  298.                 }
  299.             }
  300.         } elsif (($_[2] eq "A") ) {
  301.             print STDERR 'a' if $opt_d;
  302. # check to see that a reverse PTR record exists
  303.             if (!(($name,$aliases,$addrtype,$length,@addrs)=gethostbyaddr(pack('C4', split(/\./,$_[3])),2)) && !$?) {
  304.                 &printerr(" gethostbyaddr($_[3]): $!\n");
  305.             }
  306.             else {
  307.                 if (!$name) {
  308.                     &printerr(" $_[0] A $_[3]: no PTR record\n");
  309.                 }
  310.                 elsif ($opt_F && !&equal($name.".",$_[0])) {
  311.                     &printerr(" $_[0] A $_[3]: points to $name\n") if ((split(/\./,$name,1))[0] ne "localhost");
  312.                 }
  313.                 if ($main'opt_a) {
  314.                     # keep list in %glues, report any duplicates
  315.                     if ($glues{$_[3]} eq "") {
  316.                         $glues{$_[3]}=$_[0];
  317.                     }
  318.                     elsif (($glues{$_[3]} eq $_[0]) && (!&equal($lastns,$domain))) {
  319.                             &printerr(" $_[0]: possible duplicate A record (glue of $lastns?)\n");
  320.                     }
  321.                 }
  322.             }
  323.         } elsif ($_[2] eq "NS") {
  324.             $lastns=$_[0];
  325.             print STDERR 'n' if $opt_d;
  326.             # check to see if object of NS is real
  327.             &checklamer($_[0],$_[3]) if ($main'opt_l);
  328.             if (!(($name, $aliases, $addrtype, $length, @addrs)=gethostbyname($_[3])) && !$?) {
  329.                 &printerr(" gethostbyname($_[3]): $!\n");
  330.             }
  331.             else {
  332.                 if (!$name) {
  333.                     &printerr(" $_[0] NS $_[3]: unknown host\n");
  334.                 } elsif (!&equal(($name.'.'),$_[3])) {
  335.                     &printerr(" $_[0] NS $_[3]: CNAME (to $name)\n");
  336.                 }
  337.             }
  338.         } elsif ($_[2] eq "MX") {
  339.             print STDERR 'm' if $opt_d;
  340.             # check to see if object of mx is real
  341.             ($prio,$mx)=split(/ /,$_[3]);
  342.             if (!(($name, $aliases, $addrtype, $length, @addrs)=gethostbyname($mx)) && !$?) {
  343.                 &printerr(" gethostbyname($mx): $!\n");
  344.             }
  345.             else {
  346.                 if (!$name) {
  347.                     &printerr(" $_[0] MX $_[3]: unknown host\n");
  348.                 }
  349.                 elsif (!&equal(($name.'.'),$mx)) {
  350.                     &printerr(" $_[0] MX $_[3]: CNAME (to $name)\n");
  351.                 }
  352.             }
  353.         } elsif ($_[2] eq "CNAME") {
  354.             print STDERR 'c' if $opt_d;
  355.             if (!(($name, $aliases, $addrtype, $length, @addrs)=gethostbyname($_[3])) && !$?) {
  356.                 &printerr(" gethostbyname($_[3]): $!\n");
  357.             }
  358.             else {
  359.                 if (!$name) {
  360.                     &printerr(" $_[0] CNAME $_[3]: unknown host\n");
  361.                 } elsif (!&equal(($name.'.'),$_[3])) {
  362.                     &printerr(" $_[0] CNAME $_[3]: CNAME (to $name)\n");
  363.                 }
  364.             }
  365.         }
  366.     }
  367.     print STDERR "\n" if $opt_d;
  368.     close(FILE);
  369. }
  370.  
  371. # prints an error message, suppressing duplicates
  372. sub printerr {
  373.     local ($err)=$_[0];
  374.     if ($errlist{$err}==undef) {
  375.     print $err;
  376.     print STDERR "!" if $opt_d;
  377.     $errlist{$err}=1;
  378.     } else {
  379.     print STDERR "." if $opt_d;
  380.     }
  381. }
  382.  
  383. sub equal {
  384.     # Do case-insensitive string comparisons
  385.     local ($one)= $_[0];
  386.     local ($two)= $_[1];
  387.     $one =~ tr/A-Z/a-z/;
  388.     $two =~ tr/A-Z/a-z/;
  389.     return ($one eq $two);
  390. }
  391.  
  392. sub matchaddrlist {
  393.     local($match)=pack('C4', reverse(split(/\./,$_[0],4)));
  394.     local($found)=0;
  395.     foreach $i (@addrs) {
  396.         $found=1 if ($i eq $match);
  397.     }
  398.     return $found;
  399. }
  400.  
  401. # there's a better way to do this, it just hasn't evolved from
  402. # my brain to this program yet.
  403. sub byhostname {
  404.     @c = reverse(split(/\./,$a));
  405.     @d = reverse(split(/\./,$b));
  406.     for ($i=0;$i<=(($#c > $#d) ? $#c : $#d) ;$i++) {
  407.         next if $c[$i] eq $d[$i];
  408.         return -1 if $c[$i] eq "";
  409.         return  1 if $d[$i] eq "";
  410.         if ($c[$i] eq int($c[$i])) {
  411.             # numeric
  412.             return $c[$i] <=> $d[$i];
  413.         }
  414.         else {
  415.             # string
  416.             return $c[$i] cmp $d[$i];
  417.         }
  418.     }
  419.     return 0;
  420. }
  421.  
  422. sub checklamer {
  423.     local ($isauth)=0;
  424.     local ($error)=0;
  425.     # must check twice, since first query may be authoratative
  426.     # trap stderr here and print if non-empty
  427.     open(DIG,"dig soa +noaa $_[0] \@$_[1] 2>&1 1>/dev/null |");
  428.     while (<DIG>) {
  429.         print " $_[0] NS $_[1]: nameserver error (lame?):\n" if !$error;
  430.     print;
  431.     $error=1;
  432.     }
  433.     close(DIG);
  434.     return if $error;
  435.     open(DIG,"dig soa +noaa $_[0] \@$_[1] 2>/dev/null|");
  436.     while (<DIG>) {
  437.         if (/;; flags.*aa.*;/) { 
  438.             $isauth=1;
  439.         }
  440.     }
  441.     if (!$isauth) {
  442.         print " $_[0] NS $_[1]: lame NS delegation\n";
  443.     }
  444.     close(DIG);
  445.     return;
  446. }
  447.