#!/bin/sh
#	program:	axfr
#	release:	1.5.0
#	input:		zone name on command line,
#			optional nameserver to direct queries to.
#	output:		the entire zone transfer output from dig, or the word
#			"ERROR" (distinguishable from  real data because it
#			has no period on the end and no comment on front).
#	exit value:	0 if succeeds, 1 if fails (and "ERROR" output).
#	notes:		This tool optionally keeps a cache of old zones it has downloaded
#			and does a SOA serial number comparison to see if it can
#			use the old zone dump, or if it should get a new one.
#			The cache lives in /usr/local/domtools/lib/zonecache.
#
# 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.
#

type=AXFR

# Set to 1 if we are using a zone cache or 0 if we aren't (Makefile sets this):
ZONECACHEEXISTS=1

bn=`basename $0`
TMPERR=/tmp/$bn.err$$
TMPZONE=/tmp/$bn.zone$$

if test $# -lt 1 -o $# -gt 2; then
	echo "usage: $0 [@nameserver] zone.dom.ain." >&2
	echo 'ERROR'
	exit 1
fi

zone=
nameservers=
cachesubdir=
while test $# -gt 0; do
	case "$1" in
		@*)  nameservers=`echo $1 | sed -e 's/@//'` cachesubdir="@$nameservers" ;;
		*)  zone="$1" ;;
	esac
	shift
done
if test x"$zone" = x""; then
	echo "$0: No domain name specified; aborting." >&2
	echo "usage: $0 [@nameserver] dom.ain." >&2
	echo 'ERROR'
	exit 1
fi

if test "`/usr/local/domtools/bin/type $zone`" = "forward"; then
	echo "$0: argument $zone is not a domain name." >&2
	echo 'ERROR'
	exit 1
fi

xfernewzone=0
error=0

# Convert zone to lowercase and make sure there's a period on the end

lczone=`echo $zone | tr '[A-Z]' '[a-z]' | sed -e 's/\([^\.]\)$/\1./'`
#echo "$0:DEBUG: the zone is $lczone" >&2

# Convert the lowercase zone name to a filename

if test x"$lczone" = x"."; then
	file="ROOT"		# don't let the file be named "." !!
else
	file="$lczone"
fi

# Determine cache file path - it may be in a subdirectory depending on
# if we were called specifying a literal nameserver to query, or not.

if test x"$cachesubdir" = x""; then
	cachefile="/usr/local/domtools/lib/zonecache/$file"
else
	cachefile="/usr/local/domtools/lib/zonecache/$cachesubdir/$file"
fi

# If we don't have the dump for this zone stored in the cache (or we
# aren't using the cache at all), remember to transfer the zone.

if test $ZONECACHEEXISTS -eq 0 -o ! -s $cachefile; then
#echo "$0:DEBUG: zone $lczone is not in cache, so transfer it there." >&2
	xfernewzone=1

else

# We already have the zone dump, but it might not be up to date.

#echo "$0:DEBUG: Already have this zone in our cache." >&2

# Get the current serial number for this zone.

	set `/usr/local/domtools/bin/soa $zone`
	if test $? -ne 0; then
		echo "$0: cannot retrieve soa record for zone $zone" >&2
		echo 'ERROR'
		exit 1
	fi
	if test $# -lt 3; then
		echo "$0: soa command returned too few fields for zone $zone" >&2
		echo 'ERROR'
		exit 1
	fi
	newserial="$3"
#echo "$0:DEBUG: the new SOA serial number is $newserial" >&2

# Get the old serial number from our old zone dump.  If can't find SOA record
# in old zone dump, assume it's trashed and get a new dump.

	set `/usr/local/domtools/bin/digparse < $cachefile | awk '$2=="SOA" {print;exit}'` extraarg
	if test $# -lt 5; then
# 8/21/94 pab - Commented out because some NS records point to computers
# that don't think they're nameservers for the given domain.  We should silently
# axfr a new copy of the zone when this happens.
#		echo "$0: /usr/local/domtools/bin/digparse returned too few fields for SOA for zone $zone" >&2
#		echo 'ERROR'
#		exit 1
		xfernewzone=1		# transfer a new copy for sure
		error=1			# but don't look at "oldserial".
#echo "$0:DEBUG: the old SOA serial number is undetermined." >&2
	fi
	if test $error -eq 0; then
		oldserial="$5"
#echo "$0:DEBUG: the old SOA serial number is $oldserial" >&2
	fi

# Compare serial numbers only if we didn't encounter an error above.
# (If our previously stored zone dump is too old, we need to get a new one).

	if test $error -eq 0; then
		if test $newserial -gt $oldserial; then
#echo "$0:DEBUG: our cache copy is too old, so transfer a new one." >&2
			xfernewzone=1
		fi
	fi
fi

# Download the new zone dump now, if we need to.
# If they literally specified a nameserver, we store the zone file in
# a subdirectory named "@nameserver" in the zone cache directory.

if test $xfernewzone -eq 1; then
	if test x"$nameservers" = x""; then
		nameservers=`/usr/local/domtools/bin/ns $zone`
		if test $? -ne 0 -o x"$nameservers" = x""; then
			echo "$0: cannot find any nameservers for zone $zone" >&2
			echo 'ERROR'
			exit 1
		fi
	fi

#echo "$0:DEBUG: nameservers to get it from: $nameservers" >&2
	gotit=0
	for ns in $nameservers; do
#echo "$0:DEBUG: trying $ns..." >&2
#
# OK, time for a dissertation on types of failures.  First, Dig can return a
# non-zero exit value, which means error.  But it returns 0 for other errors.
# So we also need to check stderr, to see if dig tried to tell us something,
# like if the machine we queried isn't really a nameserver for that domain.
# But other errors return nothing on stderr.  In fact, in one case, a machine
# is a nameserver for other zones but not for the one being queried, even
# though the root nameservers have it listed as the primary nameserver!
# Either Dig or the wrong-nameserver-machine freak out, resulting in three
# lines of comments returned on stdout by dig, nothing on stderr, an exit
# status of 0 is returned!  So I had to figure out the minimum number of lines
# that Dig could possibly return for a good query -- it looks like 11 or so.
# The line below that says  `wc -l < /usr/local/domtools/lib/zonecache/$lczone$$` -gt 10	 is doing
# this check.  Why doesn't the wrong-nameserver-machine return an error
# or something?	 It does come back right away, but doesn't tell Dig what
# is going on!	Argh.
#
# We make zone cache files world-writable so anyone running axfr can overwrite
# it with newer data.
#
		dig @$ns $zone $type > $TMPZONE 2> $TMPERR
		status=$?
#echo "$0:DEBUG:	status is $status" >&2
		if test $status -eq 0; then
			if test `wc -l < $TMPERR` -eq 0 -a `wc -l < $TMPZONE` -gt 10; then
#echo "$0:DEBUG:	got it." >&2
				if test $ZONECACHEEXISTS -eq 1; then
					# If user gave a specific nameserver, we keep track of its
					# zone files in a separate subdirectory - create it now.
					if test x"$cachesubdir" != x""; then
						if test ! -d /usr/local/domtools/lib/zonecache/$cachesubdir; then
							mkdir /usr/local/domtools/lib/zonecache/$cachesubdir
							chmod 777 /usr/local/domtools/lib/zonecache/$cachesubdir
						fi
					fi
					# This looks a bit roundabout because zone files can be huge.
					# Since "cp" of a large file can take some time, we copy it
					# to the right file system first using a temporary name, then
					# move it very quickly into place in 1 atomic operation.
					cp $TMPZONE /usr/local/domtools/lib/zonecache/$lczone$$
					chmod 666 /usr/local/domtools/lib/zonecache/$lczone$$
					mv /usr/local/domtools/lib/zonecache/$lczone$$ $cachefile
				fi
				gotit=1
				break
			fi
# 8/21/94 pab - Commented out so we can skip possibly bogus NS records, and try
# the next NS record in turn for this zone.  Required because some zone I just
# encountered had an "IN NS 1.2.3.4." type record (IP instead of domain name! ugh!)
#		elif test $status -eq 3; then
#			echo "$0: nameserver $ns says $zone cannot be transferred." >&2
#			echo 'ERROR'
#			exit 1
		fi
	done
	if test $gotit -ne 1; then
		if test $status -ne 0; then
			# Dig reported an error.
			echo "$0: no authoritative nameserver for $zone could transfer that zone to us." >&2
		else
			# Dig reported no error, yet returned too few lines.
			echo "$0: unknown failure when trying to transfer zone $zone from any authoritative nameserver." >&2
		fi
		echo 'ERROR'
		rm -f $TMPERR $TMPZONE
		exit 1
	fi

fi

# Output the entire zone

if test $ZONECACHEEXISTS -eq 1; then
	/usr/local/domtools/bin/digparse < $cachefile
else
	/usr/local/domtools/bin/digparse < $TMPZONE
fi

rm -f $TMPERR $TMPZONE
exit $status
