#!PERLPROG
#
# digstd
#
# Perl program that runs DiG (version 2.x, 8.x, 9.x) and converts the output
# into lines of this form for easy parsing:
#
#	HOST	RR	RR-ARGS
# i.e.
#	fire.domtools.com.  A  10.0.0.10
#	fire.domtools.com.  MX  10  flame.domtools.com.
#
# It understands $ORIGIN and @ and can expand @, blank LHS's, and non-dot-terminated
# LHS and RHSs.  No comments or blank lines are printed.
#
# Example Usages:
# 	digstd dom.ain. A
# 	digstd @server.dom.ain. dom.ain. A
# 	digstd A dom.ain.
# 	digstd @server.dom.ain. dom.ain. ANY
# 	digstd dom.ain. any
#	digstd @server.dom.ain. AXFR dom.ain.
#	digstd 1.0.0.127.in-addr.ARPA. ptr
#
# Digstd runs whichever DiG happens to be in the user's path.
# There are many differences in output formats between DiG 2.x, 8.x, and 9.x,
# digstd handles them all.  Some examples:
#
# 2.x doesn't always display the class ("IN").
# 8.x abbreviates time fields with letters like "h"=hours, "m"=minutes, "s"=seconds,
#     and uses "@" for default origin and sometimes leaves domain off entirely so you
#     have to carry forward the current domain name from lines above.
# 9.x output is perfect in every way.
#
# Output from digstd regardless of version of DiG:
#
#     domtools.com.       SOA      fire.domtools.com. hostmaster.domtools.com.  2000111600 28815 3615 2073600 86400
#     domtools.com.       NS       ns0.domtools.com.
#
# However the seconds are left as-is, so they'll contain h,m,s abbreviations
# when using BIND 8.x (but not 2.x, or 9.x!)  We don't care because Dlint doesn't use them.
#
# Copyright (C) 1993-2000 Paul A. Balyoz <pab@domtools.com>
#
#    This program is free software; you can redistribute it and/or modify
#    it under the terms of the GNU General Public License as published by
#    the Free Software Foundation; either version 2 of the License, or
#    (at your option) any later version.
#
#    This program is distributed in the hope that it will be useful,
#    but WITHOUT ANY WARRANTY; without even the implied warranty of
#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#    GNU General Public License for more details.
#
#    You should have received a copy of the GNU General Public License
#    along with this program; if not, write to the Free Software
#    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
#

# RRs that have a domain name for their rightmost field
# (we tack the default domain onto domains that don't end in ".")
%rhs_is_domain = (
	"NS"=>1,
	"PTR"=>1,
	"MX"=>1,
	"CNAME"=>1,
	"SRV"=>1,
);

# DNS Class Table
%classes = (
	"IN"=>1,	# Internet
	"CH"=>1,	# Chaos
);


#
# Parse command line arguments
#

if ($#ARGV < 0) {
	print STDERR "usage: $0 digoptions\n";
	exit 1;
}


#
# Check if dig is installed and get the version number.
# If version < 2.1, fail.  If version 9 or greater, set special settings.
# All dig's prior to BIND 9 you could just run "dig" to get the answer,
# but version 9.0.1 doesn't print the version unless you do a real query!
#
@ver = `dig localhost any`;
$_ = (grep (/DiG/,@ver))[0];
if (/DiG\s+([0-9.]+)/) {
	$ver = $1;
}

if ($ver < 2.1) {
        print ";; This program requires DiG version 2.1 or newer, which I cannot find.\n";
        exit 3;
}

#
# Options - why'd they change so many of these in BIND 9?
#    The +nostats option is not documented in BIND 9.0.1 but
#    existed in the older versions, and seems to work fine.
#
if ($ver >= 9.0) {
        $digopts = '+ret=2 +noauthority +noadditional +noquestion +nostats +nocmd';
} else {
        $digopts = '+ret=2 +noauthor +noaddit +noques +noHeader +noheader +cl +noqr +nostats +nocmd';
}


# Launch dig with user's arguments and our own

$cmd = "dig @ARGV $digopts";
#print "$cmd\n";
if (!open (DIG,"$cmd |")) {
	print ";; cannot launch dig: $!";
	exit 3;
}

# Main Loop - read dig output lines and handle them.

while (<DIG>) {
	chomp;
	next if /^\s*;/;		# skip blank & comment lines
	next if /^\s*$/;

	@f = split;

	if ($f[0] eq '$ORIGIN') {		# literally the string '$ORIGIN'
		$origin = $f[1];
		$origin .= "." if $origin !~ /\.$/;	# append "." if missing
		$origin = "" if $origin eq ".";		# use "" for root domain
		next;
	}
	elsif ($f[0] !~ /\.$/) {	# record name is missing its ending period!
		if ($f[0] eq "@") {
			$f[0] = "$origin";	# expand "@" into domain name
		} else {
			$f[0] .= ".$origin";	# append domain name onto name
		}
	}

	if (/^\s/) {				# empty LHS, use curr. domain name
		unshift @f, $domain;
	}
	else {
		$domain = $f[0];		# memorize this LHS for future lines
	}

	splice(@f,2,1)  if $classes{uc($f[2])};	# Get rid of Class if exists (DiG 8 & newer)


# By this point the records have been standardized like this --
#	$f[0] = LHS   (canonical domain name)
#	$f[1] = TTL
#	$f[2] = RRTYPE
#	$f[3]..$f[$#f] = data (1 or more fields)

	$rr = uc($f[2]);

	if ($rhs_is_domain{$rr} && $f[$#f] !~ /\.$/) {		# empty RHS domain name
		if ($f[$#f] eq "@") {
			$f[$#f] = "$origin";			# "@" is just the origin
		} else {
			$f[$#f] .= ".$origin";			# otherwise append origin
		}
	}

# By this point any domain names in the data fields have been canonicalized.
# (we expanded Dig 2.x's "@" symbols and unqualified names to fqdn's)

#
# If we see a RR continuation marker (left-paren)
# then read and parse the rest of the continuation lines.
# The line looked like this:
# @                       4H IN SOA       pallas hostmaster.pallas (
#
	if (/\(\s*$/) {
		undef($f[$#f]);			# remove the "(" thing
		while (<DIG>) {
			chomp;

# Remove comments from the line.  DiG 2.1 puts parentheses in the comments!

			s/;.*//;

# Next, handle all other data lines in the continuation,
# including the right-paren line.  Expect no comments.
# Those lines look like this:
#                                         712120828       ; serial
#                                         1H              ; refresh
#                                         5M              ; retry
#                                         1W              ; expiry
#                                         4H )            ; minimum

#			if (/\s*([^\);\s]+)\s*\)?\s*;?.*/) {
#} ugh
			if (/\s*([^\)\s]+)\s*\)?.*/) {
				$f[$#f+1] = $1;
			}
			last if /\)/;		# end continuation line
		}
	}

	if ($rr eq "SOA") {
		if ($f[3] !~ /\.$/) {	# record name is missing its ending period!
			if ($f[3] eq "@") {
				$f[3] = "$origin";	# expand "@" into domain name
			} else {
				$f[3] .= ".$origin";	# append domain name onto name
			}
		}
		if ($f[4] !~ /\.$/) {	# record name is missing its ending period!
			if ($f[4] eq "@") {
				$f[4] = "$origin";	# expand "@" into domain name
			} else {
				$f[4] .= ".$origin";	# append domain name onto name
			}
		}
		if ($f[5] eq "(") {
			splice(@f,5,1);		# remove grouping symbols (BIND 9)
		}
		if ($f[$#f] eq ")") {
			splice(@f,$#f,1);	# remove grouping symbols (BIND 9)
		}
	}

# Print resulting data line

	$nspaces = 32 - length($f[0]);
	$nspaces = 1 if $nspaces < 1;
	print $f[0], " " x $nspaces;

	#$str = "$f[1] $f[2]";
	$str = "$f[2]";			# don't bother printing TTL
	$nspaces = 8 - length($str);
	$nspaces = 1 if $nspaces < 1;
	print "$str", " " x $nspaces;

	for ($i=3; $i<=$#f; $i++) {
		print " ",$f[$i];
	}
	print "\n";
}

close DIG;

exit 0;
