#!BOURNESHELL
#	program:	axfr
#	input:		zone name on command line.
#	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 ZONEDIR.

AWK=DOMLIB/axfroutany.awk
type=AXFR

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

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

if test $# -ne 1; then
	echo "usage: $0 zone.dom.ain." >&2
	echo 'ERROR'
	exit 1
fi

zone=$1

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

# 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 ! -s ZONEDIR/$lczone -o $ZONECACHEEXISTS -eq 0; 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 `DOMBIN/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 `awk -f $AWK ZONEDIR/$lczone | 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: $AWK 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 test $xfernewzone -eq 1; then
	nameservers=`DOMBIN/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

#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 < ZONEDIR/$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
					# 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 using a temporary name, then
					# move it very quickly into place in 1 atomic operation.
					cp $TMPZONE ZONEDIR/$lczone$$
					chmod 666 ZONEDIR/$lczone$$
					mv ZONEDIR/$lczone$$ ZONEDIR/$lczone
				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
		echo "$0: failure in trying to receive entire zone $zone" >&2
		if test $status -ne 0; then
			echo "$0: no nameserver for $zone could transfer that zone to us." >&2
		fi
		echo 'ERROR'
		rm -f ZONEDIR/$lczone$$ $TMPERR $TMPZONE
		exit 1
	fi

fi

# Output the entire zone

if test $ZONECACHEEXISTS -eq 1; then
	awk -f $AWK ZONEDIR/$lczone
else
	awk -f $AWK $TMPZONE
fi

rm -f $TMPERR $TMPZONE
exit $status
