#!/usr/bin/perl -- # -*- perl -*-

# DNSSEC Walker, by Simon Josefsson <sjosefsson@rsasecurity.com>

# Description:
# ------------
#
# Get zonefile for zone with given domain name, even if zonetransfers
# are disabled.  Requires that the zone uses "NXT" (DNSSEC) records.
#
# Theory of operation:
# --------------------
#
# Fetch NXT record on zone name, walk the NXT chain until all records
# are fetched.
#
# Left as an exercise for reader:
# -------------------------------
#
# Support classes != "IN"
#
# Version history:
# ----------------
#
# 2001-01-03 version 1.0 released

use strict;

my $debug;

# dig(): dig for given $domain and $type, printing answer section to
# stdout.  If $type is NXT, returns ($nxtname, @types) where $nxtname
# is next domain name in DNSSEC order and $types is existing types for
# $domain.

sub dig (@_)
{
  my ($domain) = shift;
  my ($type) = shift;
  my ($nxtdomain, @nxttypes);
  my (@out, $str);

  @out = split '\n', `/usr/local/bin/dig $domain $type +dnssec +noauthority +noadditional +noquestion`;

  while (@out) {
    $str = shift @out;

    $_ = $str;
    next if /^;/;
    next if /^[ \t]*$/;

    if (/$domain.?[ \t]+[0-9]+[ \t]+IN[ \t]+NXT[ \t]+([^ \t]+)[ \t](.*)$/) {
      $nxtdomain = $1;
      @nxttypes = split ' ', $2;
    }

    print "$str\n";
  }
  return ($nxtdomain, @nxttypes);
}

# run_dig(): find all types for $domain and print them, and return the
# next domain name after $domain

sub run_dig (@_) {
  my ($domain) = shift;
  my ($nxtdomain);
  my (@types, $type);

  print "Getting types for `$domain' and the next domain name...\n" if $debug;
  ($nxtdomain, @types) = dig($domain, "NXT");
  print "Domain `$domain' have types `@types' and next domain is `$nxtdomain'...\n" if $debug;

  $type = shift @types;
  while ($type) {

    # NXT records are fetched explicitly above, and SIG records should
    # be included with data.

    if ($type ne "NXT" && $type ne "SIG") {

      print "Digging for domain `$domain' and type `$type'...\n" if $debug;
      dig($domain, $type);
    }

    $type = shift @types;
  }

  return $nxtdomain;
}

# main

my ($start_name) = $ARGV[0];
my ($nxt_name);
my ($str);

if ($start_name eq "-d") {
  $debug = 1;
  $start_name = $ARGV[1];
}

if (!$start_name) {
  print "Usage: walker [-d] <any domain name in zone>\n";
  exit(1);
}

print "Canonicalizing `$start_name'...\n" if $debug;
$_ = `/usr/local/bin/dig $start_name`;
if (/;; QUESTION SECTION:[ \t\r\n]+;([^ \t]+)[ \t]/) {
  $start_name = $1;
} else {
  die "Cannot canonicalize `$start_name', check that your DiG is version 9.x";
}
print "Canonical name is `$start_name'...\n" if $debug;

$nxt_name = $start_name;

print "Walking `$start_name'...\n" if $debug;
do {
  print "Current node `$nxt_name'...\n" if $debug;
  $nxt_name = run_dig ($nxt_name);
} while ($start_name ne $nxt_name);
print "Walking `$start_name'...done\n" if $debug;
