#!/bin/sh
# Script to simplify dynamic DNS updates with bind's nsupdate
# Ben Winslow <rain at bluecherry dot net>, June 2003

: "
-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA1

"

DEBUG=0
PROGNAME=$0
KEYCHECK=1
PROGBASENAME=$(basename $PROGNAME)

usage() {
	guess_nameserver
	echo "Usage: $PROGNAME <options>" >&2
	echo "Options:" >&2
	echo "	-k <filename.key>	Key filename to use (both .key and" >&2
	echo "				.private must exist)" >&2
	echo "	-s <nameserver>		Send updates to this nameserver" >&2
	echo "				(default: $NAMESERVER)" >&2
	echo "	-z <zone>		Zone to use for relative names" >&2
	echo "				(default: $ZONE)" >&2
	echo "	-T <ttl>		Time to live (default: $TTL""s)" >&2
	echo "	-n <rname>		Record name" >&2
	echo "				(default: $RNAME)" >&2
	echo "	-t <rtype>		Record type (default: $RTYPE)" >&2
	echo "	-d <rdata>		Record data (required)" >&2
	echo "	-N			Don't delete old record (if any)" >&2
	echo "	-A			Don't add new record" >&2
	echo "	-D <level>		Debugging level (default: $DEBUG)" >&2
	echo "	-h			This help" >&2
}

debug_echo() {
	if [ $DEBUG -ge $1 ]; then
		shift
		echo "(DEBUG:$PROGBASENAME) $@" >&2
	fi
}

debug_level() {
	if [ $DEBUG -ge $1 ]; then
		true
	else
		false
	fi
}

fill_defaults() {
	ZONE=${ZONE:-"`hostname | cut -d. -f2-`"}
	RNAME=${RNAME:-"`hostname | cut -d. -f1`"}
	NAMESERVER=${NAMESERVER:-"[GUESS]"}
	RTYPE=${RTYPE:-'A'}
	TTL=${TTL:-'600'}
	DELETE=${DELETE:-'1'}
	ADDNEW=${ADDNEW:-'1'}
}

guess_nameserver() {
	if [ $NAMESERVER != "[GUESS]" ]; then
		return
	fi

	debug_echo 2 "no nameserver specified, guessing..."
	# stripping the trailing . shouldn't matter for a properly
	# configured domain, but I'm playing it safe here
	TRYDIG=`dig +short -t NS $ZONE 2>/dev/null | sort | head -1 | sed 's/\.$//'`

	if [ -z "$TRYDIG" ]; then
		debug_echo 2 "unable to look up nameserver with dig, using ns1.<zone>"
		NAMESERVER="ns1.$ZONE"
	else
		debug_echo 2 "found nameserver with dig"
		NAMESERVER=$TRYDIG
	fi
	debug_echo 2 "using name server: $NAMESERVER"
}

# -----
# Fill in the defaults
fill_defaults

# Parse the command line
while getopts 'k:s:z:T:n:t:D:d:hNA' opt; do
	case "$opt" in
		k)
			KEYFILE="$OPTARG"
			debug_echo 2 "using key filename: $KEYFILE"
			;;
		s)
			NAMESERVER="$OPTARG"
			debug_echo 2 "using name server: $NAMESERVER"
			;;
		z)
			ZONE="$OPTARG"
			debug_echo 2 "using zone: $ZONE"
			;;
		T)
			TTL="$OPTARG"
			debug_echo 2 "using TTL: $TTL"
			;;
		n)
			RNAME="$OPTARG"
			debug_echo 2 "using record name: $RNAME"
			;;
		t)
			RTYPE="$OPTARG"
			debug_echo 2 "using record type: $RTYPE"
			;;
		d)
			RDATA="$OPTARG"
			debug_echo 2 "using record data: $RDATA"
			;;
		N)
			DELETE=0
			debug_echo 2 "not deleting existing record"
			;;
		A)
			ADDNEW=0
			debug_echo 2 "not adding new record"
			;;
		D)
			DEBUG=$OPTARG
			debug_echo 3 "using debug level: $DEBUG"
			;;
		\?|\*)
			usage
			exit 1
			;;
		h)
			usage
			exit 0
			;;
	esac
done

# Guess the nameserver to use if neccesary
guess_nameserver

# FIXME: check for extra args and display usage?

if [ -z "$RDATA" -a x$ADDNEW = x1 ]; then
	echo "$PROGNAME: record data (-d) is a required parameter" >&2
	exit 1
fi

# make sure the keys exist
if [ x$KEYCHECK = x1 -a ! -z "$KEYFILE" ]; then
	PRIVATE=`echo "$KEYFILE" | sed 's/key$/private/'`
	if [ ! -f $KEYFILE ]; then
		echo "$PROGNAME: $KEYFILE does not exist" >&2
		exit 2
	fi
	if [ ! -f $PRIVATE ]; then
		echo "$PROGNAME: $PRIVATE does not exist" >&2
		exit 2
	fi
fi

# FIXME: this is ugly.  need a portable way to get a safe temp file
CMDS="server $NAMESERVER
zone $ZONE"

if [ x$DELETE = x1 ]; then
	CMDS="
$CMDS
update delete $RNAME $TTL $RTYPE"
fi

if [ x$ADDNEW = x1 ]; then
	CMDS="
$CMDS
update add $RNAME $TTL $RTYPE $RDATA"
fi

if debug_level 3; then
	CMDS="
$CMDS
show"
fi

CMDS="
$CMDS
send
quit
"

debug_echo 3 "Using the following nsupdate script: $CMDS"

ARGS=""
if debug_level 2; then
	ARGS="$ARGS -d"
fi

if [ ! -z "$KEYFILE" ]; then
	ARGS="$ARGS -k '$KEYFILE'"
fi

echo "$CMDS" | eval nsupdate $ARGS > /dev/null
RET=$?

case "$RET" in
	0)
		;;
	1)
		echo "$PROGNAME: nsupdate failed to parse update script" >&2
		exit 3
		;;
	2)
		echo "$PROGNAME: server refused update or communication failure" >&2
		echo "$PROGNAME: try running with -D 2 for details" >&2
		exit 4
		;;
	*)
		echo "$PROGNAME: nsupdate returned $RET" >&2
		exit 5
		;;
esac

exit 0
-----BEGIN PGP SIGNATURE-----
Version: GnuPG v1.2.2 (GNU/Linux)

iQIVAwUBPtvzeW4qBP1BCi3PAQKGKw//RrkIdBBgmy78UO4g4v6fHyb770H21NjH
w5Ny39LBz2yTIW/9Mi3tgCkPJbBnOYHI/Z+3hvmgCVksseQL2Ifg6C3ryoDbsFzO
/qQ+kd3rAKAUHjzeTC02ugnO9vK1p36a/ID/hLfXr9kFckAERbkfz0K7lWF31/Uj
krBo3T96DFl++hV40uwyqOaIbqKch9yh1OAxUUlLpkDXZjQWP6Td0/tS0vJDhQCG
CQr2EwZttyF0XiRiFZOMRK4NcdypcBMvDwlyvYcnWwrz5EU/eeunwVR1dKrcy0Vu
RSjzrF4W1xytiZJ5RlzQmX0eYy0GOFgcU3Lz4Cko/EaTDXL0GkXALDP9yLza2xQW
m8outoLNYsmNmB3i3R1KLNPRWItmURIO+VYcvc7US8zoLBA2O+GTdyGVCQ3qVRfk
zAPpNZ8JPZtpBUSjo/hIcnPx+4OjB1Cu0jyZdy7NFrE5fiit+fvUa/90tyJOdN+z
Ck+3dRqK59hbU4S926ybc5rO/Pa5jSM+dL2j0CX40teaWG/ZondnAmig1selhjcb
lNSfVVdQncZkrYt4EAYnE+OC7jTUB9QthCD3HwEIq48wEYN+YodtaSyjl0rM+33j
orvF3wlj9r7GGrY2WaGrn60G19+Tm1WPJkvcHL44wyLOsPl2IgKKW4vZ8J+bugz3
3+w4CRIpB5c=
=5PxJ
-----END PGP SIGNATURE-----
