#! /bin/bash 

# dddns - Delegated Dynamic DNS
#
# (c) 2004 Leading Edge Business Solutions and Andrew McGill
# Distribution permitted under terms of GPL licence, as contained
# in the file COPYING
#
ID='$Id: dddns,v 1.55 2007/01/11 11:01:39 andrewm Exp $'
#
# Send a dynamic NS update for our current IP address to local NS
# and to the parent NS.
# 
# It really helps if the local clock is synchronised with the
# server clock (otherwise NSUPDATE doesn't work).

# Set the path - if we run from /etc/ppp/ip-up.d/ then this is
# necessary.
PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin

# Cache the delegation for 10 minutes (but don't cache the address records
# which are served from there)
NSTTL=600
ATTL=10

# TCP connections by `nsupdate':
V="-v"

BINDIR=$( dirname $0)

# Try all the PPP0 interfaces (including funny names) and then give up
INTERFACES="ppp0 ippp0 modem0 dsl0 ppp1 ippp1 modem1 dsl1"

# Be verbose about what we are doing
VERBOSE=1
QUIET=

# Don't change this part, unless you really want to
VARRUN="/var/run/dddns"
ETCDDDNS="/etc/dddns"

# If we're running with split-access, it checks which interfaces are up
ACTIVEINTERFACES="/var/run/split-access/interfaces"

# Figure out our version number
VERSION=$( echo "$ID" | cut -f "3" -d " " )

# No hard coded IP here - but you can put one in the config file if you are
# feeling nutty
IP=

# FAILED remembers if the update failed
FAILED=

# The nsupdate command
NSUPDATE="nsupdate"

function echored()   { echo "$RED$@$NORMAL"; }
function echoblue()  { echo "$BLUE$@$NORMAL"; }
function echogreen() { echo "$GREEN$@$NORMAL"; }

# Use highlighting for terminal output only (tty detects whether
# its input is a terminal)
RED=
BLUE=
GREEN=
NORMAL=
if tty -s 0>&1 ; then
	RED='[31m'     # vt100/xterm/linux
	BLUE='[34m'    # vt100/xterm/linux
	GREEN='[32m'   # vt100/xterm/linux
	NORMAL='[0m'   # vt100/xterm/linux
	[ $1 ] || {
		echoblue "DDDNS $VERSION. Use dddns --help for help${NORMAL}"
	}
fi

# do a NS delegation to us - the whole DDDNS hog
RRTYPE="NS"


[ -d $VARRUN ] || mkdir $VARRUN 2>/dev/null
[ -d $ETCDDDNS ] || mkdir $ETCDDDNS 2>/dev/null
# Use current directory if /var/run/dddns and /etc/dddns are not writeable
if [ ! -w $VARRUN ] ; then
	echored "Warning: Using `pwd` instead of $VARRUN" 2>&1
	VARRUN=`pwd`
fi
if [ ! -w $ETCDDDNS ] ; then
	echored "Warning: Using `pwd` instead of $ETCDDDNS" 2>&1
	ETCDDDNS=`pwd`
fi

# And here's where we get the goods, if not from somewhere else
CONFIGFILE="$ETCDDDNS/dddns.conf"
ACTIVE_CONFIGFILE=

function findnamedfiles()
{
	NAMEDCONF=named.conf
	for FILE in \
		/etc/named.conf \
		/etc/named/named.conf \
		/etc/bind/named.conf ; do
		[ -w $FILE ] && NAMEDCONF=$FILE
	done

	NAMEDRELATIVE="."
	VARNAMED="."
	for DIR in /var/named/chroot/var/named \
		/var/named \
		/var/lib/named \
		/var/cache/bind \
		/var/cache/named \
				; do
		if [ -d $DIR/dyn ] ; then VARNAMED=$DIR/dyn ; NAMEDRELATIVE="dyn";
		elif [ -d $DIR/dynamic ] ; then VARNAMED=$DIR/dynamic; NAMEDRELATIVE="dynamic"
		elif [ -d $DIR ] ; then VARNAMED=$DIR 
		fi
	done
}

function changed_delegated()
{
	# example: yourdomain.dddns.co.za
	DOMAIN="$(echo $DELEGATED | sed 's/^[^.]*\.//' )"
	# example: remove a leading empty label
	DELEGATED="$(echo $DELEGATED | sed 's/^\.//' )"
	IPFILEREMOTE=$VARRUN/lastip.remote.$DELEGATED
	IPFILELOCAL=$VARRUN/lastip.local.$DELEGATED
}

# Load a config file -- unless it is already in memory.  This allows us to
# override things, and still load it later .. or something
function loadconfigfile()
{
	[ "$ACTIVE_CONFIGFILE" == "$1" ] && return
	IP=
	ACTIVE_CONFIGFILE="$1"
	CONFIGFILE="$1"
	source $CONFIGFILE && changed_delegated
}


# Make up a secret key, using rndc-confgen
function make_secret_key()
{
	# make a new key, and keep it in the key file
	if [ ! -x /usr/sbin/rndc-confgen ] ; then
		echo 1>&2 "/usr/sbin/rndc-confgen missing - please make $KEYFILE yourself"
	fi
	/usr/sbin/rndc-confgen |
		sed '/secret/ {s/";//;s/.*"//; q; } ; d'
}

# Get our IP for current value of $INTERFACE
function getpppipaddr()
{
#	ifconfig |
#	awk '(/^'"$INTERFACE"'/) { A=1 }
#	(/inet addr/ && A) {
#		print gensub(".*inet addr:([0-9.]*).*","\\1","");
#		exit;
#	}'
            [ "$persistIFCONFIG" ] || persistIFCONFIG="$(ifconfig)"
            echo "$persistIFCONFIG" |
            awk '(/^'"$INTERFACE"'/) { A=1 }
            (/inet addr/ && A) {
                    sub(".*inet addr:","");
                    sub(" .*","");
                    print $0;
                    exit;
            }'
}

# Run a command if it exists
function trycmd()
{
	[ "$(type -p $1)" ] && "$@"
}

#  Extract SOA from the results of various versions of 'host -t SOA'
function extractsoa()
{
	sed '/has SOA record/ {s/.*has SOA record *\([a-zA-Z0-9.]*\) .*/\1/ ; q; }
             / SOA / { s/.* SOA  *\([a-zA-Z0-9.]*\) .*/\1/ ; q; }
	     /start of authority/ { s/.*start of authority[ 	]*\([a-zA-Z0-9.]*\) .*/\1/ ; q; }
	     d; q ;
	     ';
}

# Find an IP address -- the IP address MUST be the first argument
# e.g. digforip www.domain.com @server
function digforip()
{
	dig "$@" | awk '/^'"$1"'/ { print $5; exit; }'
}

# Usage digforsoa
function digforsoa()
{
	dig "$@" | awk '/^'"$1"'/ { print $5; exit; }'
}

# Set SOASERVER to SOA of $DOMAIN
function getsoaserver()
{
	[ "$SOASERVER" ] ||
	SOASERVER="$( digforsoa $DOMAIN SOA )"
	[ "$SOASERVER" ] || {
		SOASERVER="127.0.0.1"
		echored "Error: DNS server for domain $DOMAIN not found - using 127.0.0.1"
	}
}

# Set our time so that the update may suceed
function updatetime()
{
	getsoaserver
	echogreen "Trying to synchronise clock to $SOASERVER"
	trycmd netdate udp $SOASERVER ||
	trycmd netdate tcp $SOASERVER ||
	trycmd rdate $SOASERVER ||
	trycmd ntpdate $SOASERVER ||
	echored "No net time protocol available (install netdate, rdate or ntpdate)"
}

# Send an update and explain miscellaneous errors
function send_nsupdate_noverb()
{
	NS_RESPONSE="$( $NSUPDATE "$@" 2>&1 >/dev/null || echo Update failed)"
	echo "$NS_RESPONSE" | sed '/^$/ d'
	NS_RESULT=1
	case "$NS_RESPONSE" in
		*NOERROR*)	echogreen "All's well" ; NS_RESULT=0 ;;
		*BADTIME*|*clock*)
				echored "Your clock is not synchronised with the server clock"
				updatetime
				;;
		*FORMERR*) 	echored "Format error (network?)" ;;
		*SERVFAIL*)	echored "Server failure (something's wrong, I guess)" ;;
		*NXDOMAIN*)	echored "No such domain exists" ;;
		*NOTIMP*)	echored "Not implemented (probably the wrong server)" ;;
		*REFUSED*)	echored "The server says no way" ;;
		*YXDOMAIN*)	echored "Name exists when it should not" ;;
		*YXRRSET*)	echored "RR set exists when it should not" ;;
		*NXRRSET*)	echored "RR set that should exist doesn't" ;;
		*NOTZONE*)	echored "Out of zone information" ;;
		*BADSIG*)	echored "The key secret is incorrect" ;;
		*BADKEY*)	echored "The key name is incorrect (to the DNS server)" ;;
		*BADMODE*)	echored "Bad TKEY Mode" ;;
		*BADNAME*)	echored "Duplicate key name" ;;
		*BADALG*)	echored "Algorithm not supported" ;;
		*NOTAUTH*)	echored "Server not authoritative for zone (wrong server)" ;;
		*"timed out"*)	echored "Network problems. That didn't work" ;;
		*failed*)	echored "What's that?"
				updatetime
				;;
		*)		# no error - assume okay
				NS_RESULT=0
				;;
	esac
	return $NS_RESULT
}

function send_nsupdate()
{
        if [ $VERBOSE ] ; then
            echored "## nsupdate $@"
            tee /dev/tty | send_nsupdate_noverb "$@"
        else
            send_nsupdate_noverb "$@"
        fi
}

# Send the local NS update - return 0=OK or 1=FAIL
function donsupdatelocal()
{
# if we're writing an A record to the parent, then there's nothing to do locally
[ "$RRTYPE" = "NS" ] || return 0
# Update the local server
case "$IP" in 
	"")	[ $QUIET ] || echogreen "No IP address for $DELEGATED"
		return 1
		;;
	$LASTIPLOCAL)
		[ $QUIET ] || echogreen "No local change for $DELEGATED - $IP"
		return 0
		;;
	10.67.*)
		# this is an exception - allowed for testing 
		;;
	192.168.*|172.16.*|10.*)
		if ! [ "$ALLOWPRIVATE" = 1 ] ; then
		[ $QUIET ] || echored "Error: Won't register private IP address for $DELEGATED - $IP"
		return 1
		fi
		;;
esac

# Anything else is a genuine update:

[ $QUIET ] || echogreen "DDDNS $VERSION updating $DELEGATED at 127.0.0.1 to $IP ($INTERFACE)"
# Update the local server with our new IP address
echo "server 127.0.0.1
zone $DELEGATED
update delete $DELEGATED.        A
update add    $DELEGATED.     $ATTL A $IP
send" | send_nsupdate $V -y "$KEYSTRING"

# Check whether the local update succeeded - usually this
# fails on account of named being unable to create a
# journal file in $VARNAMED.
[ $QUIET ] || echogreen "DDDNS $VERSION checking $DELEGATED at 127.0.0.1"
NEWIP=`digforip $DELEGATED A @127.0.0.1`
if [ "$NEWIP" = "$IP" ] ; then 
	echo $IP > $IPFILELOCAL
	UPDATEDLOCAL=1
else
	echored "DDDNS $VERSION local update failed for $DELEGATED"
	findnamedfiles
	echo "Recommended: 1. run dddns --install and restart bind9"
	echo "Recommended: 2. check permissions for $VARNAMED/$DELEGATED.jnl"
	FAILED=1
	return 1
fi

return 0
}


function donsupdateremote()
{
# UPDATE REMOTE
if [ "$IP" = "" ] ; then
	[ $QUIET ] || echogreen "I have no idea what the IP address is"
	return 1
elif [ "$FAILED" ] ; then
	[ $QUIET ] || echored "Update failed. Not updating remote to $IP ($INTERFACE) "
elif [ "$IP" = "$LASTIPREMOTE" ] ; then
	[ $QUIET ] || echogreen "No remote change: $DELEGATED - $IP"
else
	[ $QUIET ] || echogreen "DDDNS $VERSION updating $DELEGATED on remote to $IP"

	LAST=`date`

	# Update public server to reflect our IP address
	getsoaserver
	case $RRTYPE in
	    NS)
		CHECK="a-$DELEGATED @$SOASERVER +norecurse"
		UPDATE="server $SOASERVER
zone $DOMAIN
update delete a-$DELEGATED.    A
update delete b-$DELEGATED.    A
update delete $DELEGATED.    NS
update delete last-$DELEGATED.  TXT
update add    last-$DELEGATED. 10 TXT \"$LAST\"
update add    $DELEGATED. $NSTTL NS a-$DELEGATED.
update add    $DELEGATED. $NSTTL NS b-$DELEGATED.
update add    a-$DELEGATED. $NSTTL A $IP
update add    b-$DELEGATED. $NSTTL A $IP
send"
	    ;;
	    A)
	    	CHECK="$DELEGATED @$SOASERVER +norecurse"
		UPDATE="server $SOASERVER
zone $DOMAIN
update delete $DELEGATED.    A
update add    $DELEGATED. 600 A $IP
update delete last-$DELEGATED.  TXT
update add    last-$DELEGATED. 10 TXT \"$LAST\"
send"
	esac
	if echo "$UPDATE" | send_nsupdate  -y "$KEYSTRING" ; then

		# Give the update a chance to happen ...
		sleep 5
		# Check whether the remote update suceeded - assume that it is
		# atomic.  If this fails, it is usually because the key is invalid
		# or because clocks are really out of sync.
		# FIXME - there's a bug here - if this check fails (e.g.
		# because of network overload), then dddns will send
		# unnecessary updates every 5 minutes.
		[ $QUIET ] || echogreen "DDDNS $VERSION checking $DELEGATED at $SOASERVER"
		NEWIP=`digforip $CHECK`
		if [ "$NEWIP" = "$IP" ] ; then 
			echo $IP > $IPFILEREMOTE
		else
			echored "DDDNS $VERSION remote update failed for $DELEGATED ($NEWIP)"
			FAILED=1
		fi
	else
		echored "DDDNS $VERSION remote update reported error for $DELEGATED ($NEWIP)"
	fi
fi
}

# Send NS updates to (hopefully) the correct server
function donsupdate()
{
getvars
UPDATEDLOCAL=
donsupdatelocal && donsupdateremote

if [ "$FAILED" ] ; then
	# Something was wrong - we already said what it was
	false
elif [ "$UPDATEDLOCAL" ] ; then
	# We updated something ...
	true
else
	# who knows what we did - it was probably fun
	true
fi
}

function doversionupdate()
{
getvars
donsupdatelocal

[ $QUIET ] || echogreen "DDDNS $VERSION updating $DELEGATED at 127.0.0.1 to $IP ($INTERFACE)"
# Update the local server with our new IP address
echo "server 127.0.0.1
zone $DELEGATED
update delete $DELEGATED.        NS ns1.$DELEGATED
update delete $DELEGATED.        NS ns2.$DELEGATED
update delete ns1.$DELEGATED.        A
update delete ns2.$DELEGATED.        A
update add    $DELEGATED.     $ATTL NS a-$DELEGATED
update add    $DELEGATED.     $ATTL NS b-$DELEGATED
send" | send_nsupdate -y "$KEYSTRING"
}

# This sets up the actual variables submitted via nsupdate
# You should have set the names and interfaces before calling this
function getvars()
{
	# Get the IP for the current $INTERFACE -- unless we have one already
	for INTERFACE in $INTERFACES ; do
		[ -z "$IP" ] && {
                        LINKNAMEFILE=/var/run/split-access/linkname-$INTERFACE
                        if [ -s $LINKNAMEFILE ] ; then
                            INTERFACE=$(cat $LINKNAMEFILE )
                        fi
			IP=`getpppipaddr`  # for $INTERFACE
			if [ -z "$IP" -a -e $ACTIVEINTERFACES ] ; then
				if ! grep $INTERFACE $ACTIVEINTERFACES >/dev/null ; then
					echored "Warning: Interface $INTERFACE, IP=$IP not in $ACTIVEINTERFACES"
					IP=
					continue
				fi
			fi
			if [ "$IP" ] ; then
				break
			fi
		}
	done
	if [ ! "$IP" ] ; then
		echored 1>&2 "Error: No configured interfaces are active ($INTERFACES) "
	fi
	# Get a key if we don't have one
	LASTIPREMOTE=`cat $IPFILEREMOTE 2>/dev/null`
	LASTIPLOCAL=`cat $IPFILELOCAL 2>/dev/null`
	KEYSTRING="$DOMAIN:$KEY"
}

# Show named configuration and zone for remote
function showremote()
{
findnamedfiles
cat << EOF

$GREEN####################################################$NORMAL
$GREEN# Suggested remote $NAMEDCONF$NORMAL
key "$DOMAIN" { algorithm hmac-md5; secret "$KEY"; };
zone "$DOMAIN" in {
	type master;
	file "$DOMAIN.zone";
	allow-update { key "$DOMAIN" ; };
};

$GREEN;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;$NORMAL
$GREEN; Suggested remote /var/lib/named/$DOMAIN.zone$NORMAL
@ IN SOA rock.ledge.co.za dddns-admin.ledge.co.za 2003120401 10800 3600 604800 86400
 		IN NS rock.ledge.co.za.
		IN NS ns2.ledge.co.za.
$DELEGATED.	IN NS a-$DELEGATED.
$DELEGATED.	IN NS b-$DELEGATED.
a-$DELEGATED.	IN A $IP
b-$DELEGATED.	IN A $IP
EOF
}

function showdelegatedzone()
{
	[ "$1" ] && IP=$1
# Zone file for $DELEGATED
	cat <<EOF
\$TTL $NSTTL
$DELEGATED.	$NSTTL IN SOA $DELEGATED. root 2003120401 2D 4H 6W 60
$DELEGATED.	$NSTTL IN NS a-$DELEGATED.
$DELEGATED.	$NSTTL IN NS b-$DELEGATED.
a-$DELEGATED.	$NSTTL IN A $IP
b-$DELEGATED.	$NSTTL IN A $IP
$DELEGATED.	$ATTL IN A $IP

EOF
}

function showlocalkeyconfig()
{
	findnamedfiles
	cat <<EOF
# DDNS additions `date`
key "$DOMAIN" {
	algorithm hmac-md5;
	secret "$KEY"; };
EOF
}
function showlocalnamedconf()
{
	findnamedfiles
	cat <<EOF
# DDNS additions `date`
zone "$DELEGATED" in {
	type master;
	file "$NAMEDRELATIVE/$DELEGATED";
	allow-update { key "$DOMAIN" ; }; };
# options {
#	version "";
#	allow-recursion { 127.0.0.0/8; 192.168.0.0/16; 172.16.0.0/12; 10.0.0.0/8; };
# }
EOF
}

# Show named configuration and zone for local
function showlocal()
{
findnamedfiles
cat <<EOF
$GREEN####################################################$NORMAL
$GREEN# Suggested local $NAMEDCONF$NORMAL
EOF
showlocalkeyconfig
showlocalnamedconf
cat <<EOF
$GREEN;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;$NORMAL
$GREEN; Suggested local $VARNAMED/$DELEGATED$NORMAL
EOF
showdelegatedzone
}


function sendrequestmail()
{
	echo -n "DDDNS: Enter your e-mail address: "
	read -e EMAIL || return
	echo "From: $EMAIL
To: dddns-admin@ledge.co.za
Subject: request: $DELEGATED

This is an automatically generated request for DDDNS hosting with
the following details:

Domain: $DOMAIN
Subdomain: $DELEGATED
Key: $KEY

    makedyndnszone $DOMAIN $KEY

hostname: `hostname -f`
IP address: $IP
Config file $CONFIGFILE:
$(cat $CONFIGFILE | grep -v '^#\|^$' )
" | tee /dev/tty | sendmail -f $EMAIL dddns-admin@ledge.co.za
echo "
DDDNS has sent mail to dddns-admin@ledge.co.za requesting updateable access to
the domain $DELEGATED.  You can expect a friendly reply from a real person
soon, especially if your mail is correctly configured."
}

# Install zone on local machine
function doinstall()
{
	findnamedfiles
	echogreen "Configuring $NAMEDCONF"
	echo 1>&2 "Writing zone file to $VARNAMED/$DELEGATED"
	showdelegatedzone 127.0.0.1 > $VARNAMED/$DELEGATED

	# Set ownership - but if you are running as root, you deserve what you
	# get anyhow
	chown named $VARNAMED/$DELEGATED 2>/dev/null
	chgrp named $VARNAMED/$DELEGATED 2>/dev/null
	
	if [ -e "$VARNAMED/$DELEGATED.inc" ] ; then
		echo 1>&2 "$VARNAMED/$DELEGATED.inc already exists"
		echo 1>&2 "Not adding include \"$VARNAMED/$DELEGATED.inc\"; to $NAMEDCONF"
		echo 1>&2 "rm $VARNAMED/$DELEGATED.inc to override this."
		echo 1>&2 "You can use the 'local' option to show the suggested configuration"
	else
		echo 1>&2 "Writing to $NAMEDCONF: include $NAMEDRELATIVE/$DELEGATED"
		{
		echo
		echo "# DDDNS additions `date`"
		echo "include \"$NAMEDRELATIVE/$DELEGATED.inc\";"
		} >> $NAMEDCONF
		# The key file is a separate file, since we don't want to
		# load it twice (that's an error to named)
		grep "$DOMAIN.key" $NAMEDCONF >& /dev/null ||
		echo "include \"$NAMEDRELATIVE/$DOMAIN.key\";" >> $NAMEDCONF

		echo 1>&2 "Creating $VARNAMED/$DELEGATED.inc"
		showlocalnamedconf > $VARNAMED/$DELEGATED.inc
		echo 1>&2 "Creating $VARNAMED/$DOMAIN.key"
		showlocalkeyconfig > $VARNAMED/$DOMAIN.key

	fi

	echogreen "Configuring ip-up"
	# ip-up - update our name
	if [ -d /etc/ppp/ip-up.d ] ; then
		echo 1>&2 "Linking to $BINDIR/dddns in /etc/ppp/ip-up.d"
		ln -sf $BINDIR/dddns /etc/ppp/ip-up.d
	elif [ -x /etc/ppp/ip-up.local ] ; then
		if grep dddns /etc/ppp/ip-up.local >& /dev/null ; then
			echo 1>&2 "Not changing /etc/ppp/ip-up.local"
		else
			echo 1>&2 "Changing /etc/ppp/ip-up.local"
			echo $BINDIR'/dddns "$@"' >> /etc/ppp/ip-up.local
		fi
	elif [ -x /etc/ppp/ip-up ] ; then
		if grep dddns /etc/ppp/ip-up >& /dev/null ; then
			echo 1>&2 "Not changing /etc/ppp/ip-up"
		else
			echo 1>&2 "Changing /etc/ppp/ip-up"
			echo $BINDIR'/dddns "$@"' >> /etc/ppp/ip-up
		fi
	fi

	echogreen "Configuring cron"
	echogreen "Configuring 5-minute check in /etc/cron.d or /etc/crontab"
	# crontab check every 5 minutes - it might have failed
	if [ -d /etc/cron.d ] ; then
		[ -e /etc/cron.d/dddns ] ||
		echo "*/5 * * * * root $BINDIR/dddns >& $VARRUN/lastrun" > /etc/cron.d/dddns
	else
		grep dddns /etc/crontab >& /dev/null ||
		echo "*/5 * * * * root $BINDIR/dddns >& $VARRUN/lastrun" >> /etc/crontab
	fi
	killall -1 cron
}

function writedelegateddomain()
{
	DELEGATED="$1"
	[ "$DELEGATED" ] || {
		echo 1>&2 "You need to specify a domain, e.g."
		echo 1>&2 "dddns --name dddns.example.dyn.ledge.co.za"
		return
	}
	echo "DELEGATED=$DELEGATED" > $DNSNAMEFILE
	echo 2>&1 "Wrote name of $DELEGATED to $DNSNAMEFILE"
}

function do_manual_updates()
{
UPDATE="server $SOASERVER
zone $DOMAIN"
	echo "
This is the manual NSUPDATE function, for the zone $DOMAIN
Type commands like this (not forgetting the trailing dots on domains)
${GREEN}update delete www.$DOMAIN.    A${NORMAL}
${GREEN}update delete www.$DOMAIN.    CNAME${NORMAL}
${GREEN}update add    www.$DOMAIN.    10800 CNAME $DELEGATED.${NORMAL}
${GREEN}update delete $DOMAIN.        MX${NORMAL}
${GREEN}update add    $DOMAIN.        10800 MX 10 $DELEGATED.${NORMAL}
${GREEN}update add    $DOMAIN.        10800 MX 20 timeout.$DOMAIN.${NORMAL}
${GREEN}update add    timeout.$DOMAIN.  10800 A 196.0.0.0${NORMAL}

The following commands are sent at the top of your update:
# nsupdate $V -y \"$KEYSTRING\"
${GREEN}$UPDATE${NORMAL}
Type your update commands now, and end with an empty line:"
{
echo "$UPDATE"
while read -e LINE ; do
	[ "$LINE" ] || break
	case "$LINE" in 
		# helpfully add "update" to add or delete
		update*)  echo "$LINE" ;;
		add*)     echo "update $LINE" ;;
		delete*)  echo "update $LINE" ;;
		del*)     echo "update ${LINE/del/delete}" ;;
		*)        echo "update add $LINE" ;;
	esac
done 
echo
} | send_nsupdate $V -y "$KEYSTRING" 
echo "Interactive update complete"
}

function writesecretkey()
{
	KEY="$1"
	[ "$KEY" ] || {
		echo 1>&2 "You need to specify a secret, e.g."
		echo 1>&2 "dddns --secret yO5T+pjkCqLNuzlvpwBmNQ=="
		return
	}
	KEYFILE=$ETCDDDNS/keyfile
	echo "$KEY" > $KEYFILE
	echo 2>&1 "Wrote key of $KEY to $KEYFILE"
}

# Set up environment from file $ETCDDDNS/names and run the updates
function fordddnsconfig()
{
	CURRENTCONFIG="$1"
	FUNCTION="$2"
	shift
	shift

	# Config file -- can set anything, but most likely ...
	#	KEY="BnUMnKCAAXRSRtYzmW1Fow=="
	#	DELEGATED="dddns.ledge.dyn.ledge.co.za"
	#	INTERFACES="ppp0 modem0 dsl0 ippp0 ppp1"

	# Reset the IP address before loadnig the config file
	loadconfigfile $CURRENTCONFIG
	$FUNCTION "$@"
}

# Run a function for each config - e.g. install or update
# Returns OK if it did anything
function do_for_all_configs()
{
	FUNCTION=$1
	doRETURN=1
	for CONFIG in $ETCDDDNS/*.conf ; do
		[ $QUIET ] || echoblue "DDDNS $VERSION processing $CONFIG"
		if loadconfigfile $CONFIG ; then
			$FUNCTION "$@"
			doRETURN=0
		fi
	done
	return $doRETURN
}

# Write the environment config to the config file
function write_config_file()
{
	CURRENTCONFIG=$1
	echoblue "DDDNS $VERSION rewriting $CURRENTCONFIG"
cat > $CURRENTCONFIG << EOF 
# dddns.conf -- configuration for dddns

# KEY: Key for updates
KEY="$KEY"

# DELEGATED: Name that we update
DELEGATED="$DELEGATED"

# INTERFACES: Interfaces that we register the IP address of, in order of
# preference
INTERFACES="$INTERFACES"

# NSTTL: TTL of delegation to us at server (>=60 seconds recommended)
NSTTL=$NSTTL

# ATTL: TTL of our address responses (<=60 seconds recommended)
ATTL=$ATTL

# IP: The IP address to register as your dddns IP address.  If you have a
# strange connection and you figure out its address some other way, here's
# where you say so -- run a program, or whatever.  The default is
# to use INTERFACES defined above, which is usually what you want.
# IP=\$( cat /var/log/currentip )
# IP=\$( /usr/local/bin/hackygetipscript.pl 192.168.1.1 )

# SOASERVER: Send remote updates to a specific host.  The default is to
# dig soa yourdomain to find the start of authority.
# SOASERVER=rock.ledge.co.za

# RRTYPE: Record type to create. This should be NS, meaning DDDNS.  If you know
# what you're doing, you can use A, meaning DDNS.  (Note that it's tricky to
# switch from one to the other).  Don't change this.
# RRTYPE="$RRTYPE"
EOF
}

# Automigrate: generate new config file from old version files still using
# /etc/dddns/dddns.dnsname and /etc/dddns/keyfile
[ -r $ETCDDDNS/dddns.dnsname -a ! -r $CONFIGFILE ] && {
		source $ETCDDDNS/dddns.dnsname
		KEY=`cat $ETCDDDNS/keyfile`
		INTERFACES="ppp0 ppp1"
		write_config_file $CONFIGFILE
		# rm $ETCDDDNS/dddns.dnsname
}

[ -r $CONFIGFILE ] && loadconfigfile $CONFIGFILE

# Process command line options
OPTERR=0    # no getopt errors ...
while getopts "vqd:D:ac:" OPTIONCHAR
do
  case $OPTIONCHAR in
    c) CONFIGFILE="$OPTARG"; loadconfigfile $CONFIGFILE ;;
    v) VERBOSE=1 ;; # quiet
    q) QUIET=1 ;; # quiet
    # deprecated: rather change the config file:::
    # Set the delegated name once-off
    d) DELEGATED="$OPTARG.$DOMAIN"; changed_delegated;;
    # Set the delegated name once-off
    D) DELEGATED="$OPTARG"; changed_delegated;; 
    # Send a DDNS A record update
    a) RRTYPE=A; changed_delegated ;;
    *) break ;;
  esac
done
shift $(($OPTIND - 1))

# GENERATE DEFAULTS
# If there's no key, then generate a default configuration file
[ -z "$KEY" ] && {
	KEY=`make_secret_key`
	changed_delegated
	write_config_file $CONFIGFILE
}
[ -z "$DELEGATED" ] && {
	DELEGATED="$( hostname -f | sed 's/^\([^.]*\.[^.]*\).*/\1/' ).dyn.ledge.co.za"
	changed_delegated
	write_config_file $CONFIGFILE
}

# If we are run in /etc/ppp/ip-up.d/
pppBASENAME=${0##*/}
pppINTERFACE=$1
pppDEVICE=$2
pppSPEED=$3
pppLOCALIP=$4
pppREMOTEIP=$5
pppIPPARAM=$6

# Automatic behaviour for ppp0
if [ "$pppLOCALIP" ] ; then
	# If an interface bounced, we need to reload BIND9 so
	# that it binds to the current ppp0 interface - especially if
	# we got the same IP address as previously - it will have
	# unbound, but quite possibly not rebound ...
	trycmd rndc reload ||
		trycmd ndc reload ||
		trycmd /etc/init.d/bind9 reload ||
		trycmd /etc/init.d/named reload ||
		killall -HUP named ||
		echo 1>&2 "Cannot reload named (even tried killall -HUP named)"
	# Now we can see what we're going to update
	# Now register every name we have
	do_for_all_configs donsupdate
	# bye
	exit

fi

case "$1" in 
	"--bind9") getvars; showremote; showlocal ;;
	"--local")  getvars; showlocal;;
	"--remote") getvars; showremote;;
	"--request") getvars;
		echo "Testing local configuration before registration request"
		if donsupdatelocal ; then
			echo "Local update succeeded - sending registration request"
			sendrequestmail
		else
			echo "Local update failed - NOT sending registration request"
		fi
		;;
	[1-9]*)
		# default behaviour - send updates for our current IP address
		IP=$1
		donsupdate
		;;
	""|--all)
		do_for_all_configs donsupdate || echo "No configuration found - try ${GREEN}$0 --help${NORMAL}"
		;;
	--UPDATE)
		do_for_all_configs doversionupdate || echo "No configuration found - try ${GREEN}$0 --help${NORMAL}"
		;;
	--install)
		do_for_all_configs doinstall
		echo "This function does not 'service named restart' -- you can do that"
		;;
	eth*|ppp*|ippp*)
		# send updates for a particular interface
		INTERFACES="$1"
		donsupdate
		;;
	"--test-FOO")
		IP="$2"
		getvars
		donsupdateremote
		;;
	"--name") 
		case "$2" in
			*.*) DELEGATED="$2";;
			*)   DELEGATED="$2.$DOMAIN";;
		esac
		write_config_file $CONFIGFILE
		;;
	"--secret")
		KEY="$2"
		write_config_file $CONFIGFILE
		;;
	"--interfaces")
		shift
		INTERFACES="$*"
		write_config_file $CONFIGFILE
		;;
	--manual)
		getvars
		getsoaserver
		do_manual_updates
		;;
	# Send updates to the delegated domain
	# this is of dubious value, so it's not documented:
	--manuallocal)
		getvars
		SOASERVER=localhost
		DOMAIN=$DELEGATED
		DELEGATED=mail.$DOMAIN
		do_manual_updates
		;;

	*)  # including --help
		getvars
		echo "${GREEN}DDDNS: Delegated Dynamic DNS${NORMAL} $VERSION by ledge.co.za +27 11 656 0360"
		echo ""
		echo "Usage: $0 [options] [command]"
		echo "Configuration:"
		echo "  -c $GREEN$CONFIGFILE$NORMAL (configuration file)"
		echo "  --name $GREEN$DELEGATED$NORMAL (set dddns name)"
		echo "  --secret $GREEN$KEY$NORMAL (set tsig secret)"
		echo "  --interfaces $GREEN$INTERFACES$NORMAL (i/f to register)"
		echo "  --bind9 (show bind9 config for manual install)"
		echo "  --install (write local zone file and named.conf; setup cron, ip-up.d)"
		echo "  --request (mail DDDNS hosting request to dddns-admin@ledge.co.za)"
		echo "Actions:"
		echo "  --all     register each interface in $ETCDDDNS/*.conf (default)"
		echo "  --manual  send manually typed updates to your zone"
		echo "Options:"
		echo "  -q quiet   -v verbose"
		echo "Status ($DELEGATED):"
		echo "  local ip $LASTIPLOCAL"
		echo "  remote ip $LASTIPREMOTE"
		;;
esac

