/* dns.c

   Domain Name Service subroutines. */

/*
 * Copyright (c) 2004-2006 by Internet Systems Consortium, Inc. ("ISC")
 * Copyright (c) 2001-2003 by Internet Software Consortium
 *
 * Permission to use, copy, modify, and distribute this software for any
 * purpose with or without fee is hereby granted, provided that the above
 * copyright notice and this permission notice appear in all copies.
 *
 * THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES
 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR
 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
 * OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 *
 *   Internet Systems Consortium, Inc.
 *   950 Charter Street
 *   Redwood City, CA 94063
 *   <info@isc.org>
 *   http://www.isc.org/
 *
 * This software has been written for Internet Systems Consortium
 * by Ted Lemon in cooperation with Nominum, Inc.
 * To learn more about Internet Systems Consortium, see
 * ``http://www.isc.org/''.  To learn more about Nominum, Inc., see
 * ``http://www.nominum.com''.
 */

#ifndef lint
static char copyright[] =
"$Id: dns.c,v 1.40 2006/07/19 17:14:55 dhankins Exp $ Copyright (c) 2004-2006 Internet Systems Consortium.  All rights reserved.\n";
#endif /* not lint */

#include "dhcpd.h"
#include "arpa/nameser.h"
#include "dst/md5.h"

/* This file is kind of a crutch for the BIND 8 nsupdate code, which has
 * itself been cruelly hacked from its original state.   What this code
 * does is twofold: first, it maintains a database of zone cuts that can
 * be used to figure out which server should be contacted to update any
 * given domain name.   Secondly, it maintains a set of named TSIG keys,
 * and associates those keys with zones.   When an update is requested for
 * a particular zone, the key associated with that zone is used for the
 * update.
 *
 * The way this works is that you define the domain name to which an
 * SOA corresponds, and the addresses of some primaries for that domain name:
 *
 *	zone FOO.COM {
 *	  primary 10.0.17.1;
 *	  secondary 10.0.22.1, 10.0.23.1;
 *	  key "FOO.COM Key";
 * 	}
 *
 * If an update is requested for GAZANGA.TOPANGA.FOO.COM, then the name
 * server looks in its database for a zone record for "GAZANGA.TOPANGA.FOO.COM",
 * doesn't find it, looks for one for "TOPANGA.FOO.COM", doesn't find *that*,
 * looks for "FOO.COM", finds it. So it
 * attempts the update to the primary for FOO.COM.   If that times out, it
 * tries the secondaries.   You can list multiple primaries if you have some
 * kind of magic name server that supports that.   You shouldn't list
 * secondaries that don't know how to forward updates (e.g., BIND 8 doesn't
 * support update forwarding, AFAIK).   If no TSIG key is listed, the update
 * is attempted without TSIG.
 *
 * The DHCP server tries to find an existing zone for any given name by
 * trying to look up a local zone structure for each domain containing
 * that name, all the way up to '.'.   If it finds one cached, it tries
 * to use that one to do the update.   That's why it tries to update
 * "FOO.COM" above, even though theoretically it should try GAZANGA...
 * and TOPANGA... first.
 *
 * If the update fails with a predefined or cached zone (we'll get to
 * those in a second), then it tries to find a more specific zone.   This
 * is done by looking first for an SOA for GAZANGA.TOPANGA.FOO.COM.   Then
 * an SOA for TOPANGA.FOO.COM is sought.   If during this search a predefined
 * or cached zone is found, the update fails - there's something wrong
 * somewhere.
 *
 * If a more specific zone _is_ found, that zone is cached for the length of
 * its TTL in the same database as that described above.   TSIG updates are
 * never done for cached zones - if you want TSIG updates you _must_
 * write a zone definition linking the key to the zone.   In cases where you
 * know for sure what the key is but do not want to hardcode the IP addresses
 * of the primary or secondaries, a zone declaration can be made that doesn't
 * include any primary or secondary declarations.   When the DHCP server
 * encounters this while hunting up a matching zone for a name, it looks up
 * the SOA, fills in the IP addresses, and uses that record for the update.
 * If the SOA lookup returns NXRRSET, a warning is printed and the zone is
 * discarded, TSIG key and all.   The search for the zone then continues as if
 * the zone record hadn't been found.   Zones without IP addresses don't
 * match when initially hunting for a predefined or cached zone to update.
 *
 * When an update is attempted and no predefined or cached zone is found
 * that matches any enclosing domain of the domain being updated, the DHCP
 * server goes through the same process that is done when the update to a
 * predefined or cached zone fails - starting with the most specific domain
 * name (GAZANGA.TOPANGA.FOO.COM) and moving to the least specific (the root),
 * it tries to look up an SOA record.   When it finds one, it creates a cached
 * zone and attempts an update, and gives up if the update fails.
 *
 * TSIG keys are defined like this:
 *
 *	key "FOO.COM Key" {
 *		algorithm HMAC-MD5.SIG-ALG.REG.INT;
 *		secret <Base64>;
 *	}
 *
 * <Base64> is a number expressed in base64 that represents the key.
 * It's also permissible to use a quoted string here - this will be
 * translated as the ASCII bytes making up the string, and will not
 * include any NUL termination.  The key name can be any text string,
 * and the key type must be one of the key types defined in the draft
 * or by the IANA.  Currently only the HMAC-MD5... key type is
 * supported.
 */

dns_zone_hash_t *dns_zone_hash;

#if defined (NSUPDATE)
isc_result_t find_tsig_key (ns_tsig_key **key, const char *zname,
			    struct dns_zone *zone)
{
	isc_result_t status;
	ns_tsig_key *tkey;

	if (!zone)
		return ISC_R_NOTFOUND;

	if (!zone -> key) {
		return ISC_R_KEY_UNKNOWN;
	}
	
	if ((!zone -> key -> name ||
	     strlen (zone -> key -> name) > NS_MAXDNAME) ||
	    (!zone -> key -> algorithm ||
	     strlen (zone -> key -> algorithm) > NS_MAXDNAME) ||
	    (!zone -> key) ||
	    (!zone -> key -> key) ||
	    (zone -> key -> key -> len == 0)) {
		return ISC_R_INVALIDKEY;
	}
	tkey = dmalloc (sizeof *tkey, MDL);
	if (!tkey) {
	      nomem:
		return ISC_R_NOMEMORY;
	}
	memset (tkey, 0, sizeof *tkey);
	tkey -> data = dmalloc (zone -> key -> key -> len, MDL);
	if (!tkey -> data) {
		dfree (tkey, MDL);
		goto nomem;
	}
	strcpy (tkey -> name, zone -> key -> name);
	strcpy (tkey -> alg, zone -> key -> algorithm);
	memcpy (tkey -> data,
		zone -> key -> key -> value, zone -> key -> key -> len);
	tkey -> len = zone -> key -> key -> len;
	*key = tkey;
	return ISC_R_SUCCESS;
}

void tkey_free (ns_tsig_key **key)
{
	if ((*key) -> data)
		dfree ((*key) -> data, MDL);
	dfree ((*key), MDL);
	*key = (ns_tsig_key *)0;
}
#endif

isc_result_t enter_dns_zone (struct dns_zone *zone)
{
	struct dns_zone *tz = (struct dns_zone *)0;

	if (dns_zone_hash) {
		dns_zone_hash_lookup (&tz,
				      dns_zone_hash, zone -> name, 0, MDL);
		if (tz == zone) {
			dns_zone_dereference (&tz, MDL);
			return ISC_R_SUCCESS;
		}
		if (tz) {
			dns_zone_hash_delete (dns_zone_hash,
					      zone -> name, 0, MDL);
			dns_zone_dereference (&tz, MDL);
		}
	} else {
		if (!dns_zone_new_hash(&dns_zone_hash, DNS_HASH_SIZE, MDL))
			return ISC_R_NOMEMORY;
	}
	dns_zone_hash_add (dns_zone_hash, zone -> name, 0, zone, MDL);
	return ISC_R_SUCCESS;
}

isc_result_t dns_zone_lookup (struct dns_zone **zone, const char *name)
{
	struct dns_zone *tz = (struct dns_zone *)0;
	int len;
	char *tname = (char *)0;
	isc_result_t status;

	if (!dns_zone_hash)
		return ISC_R_NOTFOUND;

	len = strlen (name);
	if (name [len - 1] != '.') {
		tname = dmalloc ((unsigned)len + 2, MDL);
		if (!tname)
			return ISC_R_NOMEMORY;;
		strcpy (tname, name);
		tname [len] = '.';
		tname [len + 1] = 0;
		name = tname;
	}
	if (!dns_zone_hash_lookup (zone, dns_zone_hash, name, 0, MDL))
		status = ISC_R_NOTFOUND;
	else
		status = ISC_R_SUCCESS;

	if (tname)
		dfree (tname, MDL);
	return status;
}

int dns_zone_dereference (ptr, file, line)
	struct dns_zone **ptr;
	const char *file;
	int line;
{
	int i;
	struct dns_zone *dns_zone;

	if (!ptr || !*ptr) {
		log_error ("%s(%d): null pointer", file, line);
#if defined (POINTER_DEBUG)
		abort ();
#else
		return 0;
#endif
	}

	dns_zone = *ptr;
	*ptr = (struct dns_zone *)0;
	--dns_zone -> refcnt;
	rc_register (file, line, ptr, dns_zone, dns_zone -> refcnt, 1, RC_MISC);
	if (dns_zone -> refcnt > 0)
		return 1;

	if (dns_zone -> refcnt < 0) {
		log_error ("%s(%d): negative refcnt!", file, line);
#if defined (DEBUG_RC_HISTORY)
		dump_rc_history (dns_zone);
#endif
#if defined (POINTER_DEBUG)
		abort ();
#else
		return 0;
#endif
	}

	if (dns_zone -> name)
		dfree (dns_zone -> name, file, line);
	if (dns_zone -> key)
		omapi_auth_key_dereference (&dns_zone -> key, file, line);
	if (dns_zone -> primary)
		option_cache_dereference (&dns_zone -> primary, file, line);
	if (dns_zone -> secondary)
		option_cache_dereference (&dns_zone -> secondary, file, line);
	dfree (dns_zone, file, line);
	return 1;
}

#if defined (NSUPDATE)
isc_result_t find_cached_zone (const char *dname, ns_class class,
			       char *zname, size_t zsize,
			       struct in_addr *addrs,
			       int naddrs, int *naddrout,
			       struct dns_zone **zcookie)
{
	isc_result_t status = ISC_R_NOTFOUND;
	const char *np;
	struct dns_zone *zone = (struct dns_zone *)0;
	struct data_string nsaddrs;
	int ix;

	/* The absence of the zcookie pointer indicates that we
	   succeeded previously, but the update itself failed, meaning
	   that we shouldn't use the cached zone. */
	if (!zcookie)
		return ISC_R_NOTFOUND;

	/* We can't look up a null zone. */
	if (!dname || !*dname)
		return ISC_R_INVALIDARG;

	/* For each subzone, try to find a cached zone. */
	for (np = dname; np; np = strchr (np, '.')) {
		np++;
		status = dns_zone_lookup (&zone, np);
		if (status == ISC_R_SUCCESS)
			break;
	}

	if (status != ISC_R_SUCCESS)
		return status;

	/* Make sure the zone is valid. */
	if (zone -> timeout && zone -> timeout < cur_time) {
		dns_zone_dereference (&zone, MDL);
		return ISC_R_CANCELED;
	}

	/* Make sure the zone name will fit. */
	if (strlen (zone -> name) > zsize) {
		dns_zone_dereference (&zone, MDL);
		return ISC_R_NOSPACE;
	}
	strcpy (zname, zone -> name);

	memset (&nsaddrs, 0, sizeof nsaddrs);
	ix = 0;

	if (zone -> primary) {
		if (evaluate_option_cache (&nsaddrs, (struct packet *)0,
					   (struct lease *)0,
					   (struct client_state *)0,
					   (struct option_state *)0,
					   (struct option_state *)0,
					   &global_scope,
					   zone -> primary, MDL)) {
			int ip = 0;
			while (ix < naddrs) {
				if (ip + 4 > nsaddrs.len)
					break;
				memcpy (&addrs [ix], &nsaddrs.data [ip], 4);
				ip += 4;
				ix++;
			}
			data_string_forget (&nsaddrs, MDL);
		}
	}
	if (zone -> secondary) {
		if (evaluate_option_cache (&nsaddrs, (struct packet *)0,
					   (struct lease *)0,
					   (struct client_state *)0,
					   (struct option_state *)0,
					   (struct option_state *)0,
					   &global_scope,
					   zone -> secondary, MDL)) {
			int ip = 0;
			while (ix < naddrs) {
				if (ip + 4 > nsaddrs.len)
					break;
				memcpy (&addrs [ix], &nsaddrs.data [ip], 4);
				ip += 4;
				ix++;
			}
			data_string_forget (&nsaddrs, MDL);
		}
	}

	/* It's not an error for zcookie to have a value here - actually,
	   it's quite likely, because res_nupdate cycles through all the
	   names in the update looking for their zones. */
	if (!*zcookie)
		dns_zone_reference (zcookie, zone, MDL);
	dns_zone_dereference (&zone, MDL);
	if (naddrout)
		*naddrout = ix;
	return ISC_R_SUCCESS;
}

void forget_zone (struct dns_zone **zone)
{
	dns_zone_dereference (zone, MDL);
}

void repudiate_zone (struct dns_zone **zone)
{
	/* XXX Currently we're not differentiating between a cached
	   XXX zone and a zone that's been repudiated, which means
	   XXX that if we reap cached zones, we blow away repudiated
	   XXX zones.   This isn't a big problem since we're not yet
	   XXX caching zones... :'} */

	(*zone) -> timeout = cur_time - 1;
	dns_zone_dereference (zone, MDL);
}

void cache_found_zone (ns_class class,
		       char *zname, struct in_addr *addrs, int naddrs)
{
	isc_result_t status = ISC_R_NOTFOUND;
	struct dns_zone *zone = (struct dns_zone *)0;
	struct data_string nsaddrs;
	int ix = strlen (zname);

	if (zname [ix - 1] == '.')
		ix = 0;

	/* See if there's already such a zone. */
	if (dns_zone_lookup (&zone, zname) == ISC_R_SUCCESS) {
		/* If it's not a dynamic zone, leave it alone. */
		if (!zone -> timeout)
			return;
		/* Address may have changed, so just blow it away. */
		if (zone -> primary)
			option_cache_dereference (&zone -> primary, MDL);
		if (zone -> secondary)
			option_cache_dereference (&zone -> secondary, MDL);
	} else if (!dns_zone_allocate (&zone, MDL))
		return;

	if (!zone -> name) {
		zone -> name =
			dmalloc (strlen (zname) + 1 + (ix != 0), MDL);
		if (!zone -> name) {
			dns_zone_dereference (&zone, MDL);
			return;
		}
		strcpy (zone -> name, zname);
		/* Add a trailing '.' if it was missing. */
		if (ix) {
			zone -> name [ix] = '.';
			zone -> name [ix + 1] = 0;
		}
	}

	/* XXX Need to get the lower-level code to push the actual zone
	   XXX TTL up to us. */
	zone -> timeout = cur_time + 1800;
	
	if (!option_cache_allocate (&zone -> primary, MDL)) {
		dns_zone_dereference (&zone, MDL);
		return;
	}
	if (!buffer_allocate (&zone -> primary -> data.buffer,
			      naddrs * sizeof (struct in_addr), MDL)) {
		dns_zone_dereference (&zone, MDL);
		return;
	}
	memcpy (zone -> primary -> data.buffer -> data,
		addrs, naddrs * sizeof *addrs);
	zone -> primary -> data.data =
		&zone -> primary -> data.buffer -> data [0];
	zone -> primary -> data.len = naddrs * sizeof *addrs;

	enter_dns_zone (zone);
}

/* Have to use TXT records for now. */
#define T_DHCID T_TXT

int get_dhcid (struct data_string *id,
	       int type, const u_int8_t *data, unsigned len)
{
	unsigned char buf[MD5_DIGEST_LENGTH];
	MD5_CTX md5;
	int i;

	/* Types can only be 0..(2^16)-1. */
	if (type < 0 || type > 65535)
		return 0;

	/* Hexadecimal MD5 digest plus two byte type and NUL. */
	if (!buffer_allocate (&id -> buffer,
			      (MD5_DIGEST_LENGTH * 2) + 3, MDL))
		return 0;
	id -> data = id -> buffer -> data;

	/*
	 * DHCP clients and servers should use the following forms of client
	 * identification, starting with the most preferable, and finishing
	 * with the least preferable.  If the client does not send any of these
	 * forms of identification, the DHCP/DDNS interaction is not defined by
	 * this specification.  The most preferable form of identification is
	 * the Globally Unique Identifier Option [TBD].  Next is the DHCP
	 * Client Identifier option.  Last is the client's link-layer address,
	 * as conveyed in its DHCPREQUEST message.  Implementors should note
	 * that the link-layer address cannot be used if there are no
	 * significant bytes in the chaddr field of the DHCP client's request,
	 * because this does not constitute a unique identifier.
	 *   -- "Interaction between DHCP and DNS"
	 *      <draft-ietf-dhc-dhcp-dns-12.txt>
	 *      M. Stapp, Y. Rekhter
	 */

	/* Put the type in the first two bytes. */
	id -> buffer -> data [0] = "0123456789abcdef" [type >> 4];
	id -> buffer -> data [1] = "0123456789abcdef" [type % 15];

	/* Mash together an MD5 hash of the identifier. */
	MD5_Init (&md5);
	MD5_Update (&md5, data, len);
	MD5_Final (buf, &md5);

	/* Convert into ASCII. */
	for (i = 0; i < MD5_DIGEST_LENGTH; i++) {
		id -> buffer -> data [i * 2 + 2] =
			"0123456789abcdef" [(buf [i] >> 4) & 0xf];
		id -> buffer -> data [i * 2 + 3] =
			"0123456789abcdef" [buf [i] & 0xf];
	}
	id -> len = MD5_DIGEST_LENGTH * 2 + 2;
	id -> buffer -> data [id -> len] = 0;
	id -> terminated = 1;

	return 1;
}

/* Now for the DDNS update code that is shared between client and
   server... */

isc_result_t
ddns_update_a(struct data_string *ddns_fwd_name, struct iaddr ddns_addr,
	      struct data_string *ddns_dhcid, unsigned long ttl,
	      unsigned rrsetp, unsigned conflict)
{
	ns_updque updqueue;
	ns_updrec *updrec;
	isc_result_t result;
	char ddns_address [16];
	const char *logstr;

	if (ddns_addr.len != 4)
		return ISC_R_INVALIDARG;

	/* %Audit% Cannot exceed 16 bytes. %2004.06.17,Safe% */
	sprintf (ddns_address, "%u.%u.%u.%u",
		  ddns_addr.iabuf[0], ddns_addr.iabuf[1],
		  ddns_addr.iabuf[2], ddns_addr.iabuf[3]);

	/*
	 * When a DHCP client or server intends to update an A RR, it first
	 * prepares a DNS UPDATE query which includes as a prerequisite the
	 * assertion that the name does not exist.  The update section of the
	 * query attempts to add the new name and its IP address mapping (an A
	 * RR), and the DHCID RR with its unique client-identity.
	 *   -- "Interaction between DHCP and DNS"
	 */

	ISC_LIST_INIT (updqueue);

	/*
	 * A RR does not exist.
	 */
	updrec = minires_mkupdrec (S_PREREQ,
				   (const char *)ddns_fwd_name -> data,
				   C_IN, T_A, 0);
	if (!updrec) {
		result = ISC_R_NOMEMORY;
		goto error;
	}

	updrec -> r_data = (unsigned char *)0;
	updrec -> r_size = 0;
	updrec -> r_opcode = rrsetp ? NXRRSET : NXDOMAIN;

	ISC_LIST_APPEND (updqueue, updrec, r_link);


	/*
	 * Add A RR.
	 */
	updrec = minires_mkupdrec (S_UPDATE,
				   (const char *)ddns_fwd_name -> data,
				   C_IN, T_A, ttl);
	if (!updrec) {
		result = ISC_R_NOMEMORY;
		goto error;
	}

	updrec -> r_data = (unsigned char *)ddns_address;
	updrec -> r_size = strlen (ddns_address);
	updrec -> r_opcode = ADD;

	ISC_LIST_APPEND (updqueue, updrec, r_link);


	/*
	 * Add DHCID RR.
	 */
	updrec = minires_mkupdrec (S_UPDATE,
				   (const char *)ddns_fwd_name -> data,
				   C_IN, T_DHCID, ttl);
	if (!updrec) {
		result = ISC_R_NOMEMORY;
		goto error;
	}

	updrec -> r_data = ddns_dhcid -> data;
	updrec -> r_size = ddns_dhcid -> len;
	updrec -> r_opcode = ADD;

	ISC_LIST_APPEND (updqueue, updrec, r_link);


	/*
	 * Attempt to perform the update.
	 */
	result = minires_nupdate (&resolver_state, ISC_LIST_HEAD (updqueue));

#ifdef DEBUG_DNS_UPDATES
	print_dns_status ((int)result, &updqueue);
#endif

	/*
	 * If this update operation succeeds, the updater can conclude that it
	 * has added a new name whose only RRs are the A and DHCID RR records.
	 * The A RR update is now complete (and a client updater is finished,
	 * while a server might proceed to perform a PTR RR update).
	 *   -- "Interaction between DHCP and DNS"
	 */

	if (result == ISC_R_SUCCESS) {
		log_info ("Added new forward map from %.*s to %s",
			  (int)ddns_fwd_name -> len,
			  (const char *)ddns_fwd_name -> data, ddns_address);
		goto error;
	}


	/*
	 * If the first update operation fails with YXDOMAIN, the updater can
	 * conclude that the intended name is in use.  The updater then
	 * attempts to confirm that the DNS name is not being used by some
	 * other host. The updater prepares a second UPDATE query in which the
	 * prerequisite is that the desired name has attached to it a DHCID RR
	 * whose contents match the client identity.  The update section of
	 * this query deletes the existing A records on the name, and adds the
	 * A record that matches the DHCP binding and the DHCID RR with the
	 * client identity.
	 *   -- "Interaction between DHCP and DNS"
	 */

	if (result != (rrsetp ? ISC_R_YXRRSET : ISC_R_YXDOMAIN)) {
		log_error ("Unable to add forward map from %.*s to %s: %s",
			   (int)ddns_fwd_name -> len,
			   (const char *)ddns_fwd_name -> data, ddns_address,
			   isc_result_totext (result));
		goto error;
	}

	while (!ISC_LIST_EMPTY (updqueue)) {
		updrec = ISC_LIST_HEAD (updqueue);
		ISC_LIST_UNLINK (updqueue, updrec, r_link);
		minires_freeupdrec (updrec);
	}

	/* If we're doing conflict resolution, we use a set of prereqs.  If
	 * not, we delete the DHCID in addition to all A rrsets.
	 */
	if (conflict) {
		/*
		 * DHCID RR exists, and matches client identity.
		 */
		updrec = minires_mkupdrec (S_PREREQ,
					   (const char *)ddns_fwd_name -> data,
					   C_IN, T_DHCID, 0);
		if (!updrec) {
			result = ISC_R_NOMEMORY;
			goto error;
		}
	
		updrec -> r_data = ddns_dhcid -> data;
		updrec -> r_size = ddns_dhcid -> len;
		updrec -> r_opcode = YXRRSET;

		ISC_LIST_APPEND (updqueue, updrec, r_link);
	} else {
		/*
		 * Conflict detection override: delete DHCID RRs.
		 */
		updrec = minires_mkupdrec(S_UPDATE, ddns_fwd_name->data,
					  C_IN, T_DHCID, 0);

		if (!updrec) {
			result = ISC_R_NOMEMORY;
			goto error;
		}

		updrec->r_data = NULL;
		updrec->r_size = 0;
		updrec->r_opcode = DELETE;

		ISC_LIST_APPEND(updqueue, updrec, r_link);
	}


	/*
	 * Delete A RRset.
	 */
	updrec = minires_mkupdrec (S_UPDATE,
				   (const char *)ddns_fwd_name -> data,
				   C_IN, T_A, 0);
	if (!updrec) {
		result = ISC_R_NOMEMORY;
		goto error;
	}

	updrec -> r_data = (unsigned char *)0;
	updrec -> r_size = 0;
	updrec -> r_opcode = DELETE;

	ISC_LIST_APPEND (updqueue, updrec, r_link);


	/*
	 * Add A RR.
	 */
	updrec = minires_mkupdrec (S_UPDATE,
				   (const char *)ddns_fwd_name -> data,
				   C_IN, T_A, ttl);
	if (!updrec) {
		result = ISC_R_NOMEMORY;
		goto error;
	}

	updrec -> r_data = (unsigned char *)ddns_address;
	updrec -> r_size = strlen (ddns_address);
	updrec -> r_opcode = ADD;

	ISC_LIST_APPEND (updqueue, updrec, r_link);


	/*
	 * Attempt to perform the update.
	 */
	result = minires_nupdate (&resolver_state, ISC_LIST_HEAD (updqueue));

	switch (result) {
	    case ISC_R_SUCCESS:
		logstr = NULL;
		break;

	    case ISC_R_YXRRSET:
	    case ISC_R_YXDOMAIN:
		logstr = "DHCID mismatch, belongs to another client.";
		break;

	    case ISC_R_NXRRSET:
	    case ISC_R_NXDOMAIN:
		logstr = "Has an A record but no DHCID, not mine.";
		break;

	    default:
		logstr = isc_result_totext(result);
		break;
	}

	if (logstr != NULL)
		log_error("Forward map from %.*s to %s FAILED: %s",
				   (int)ddns_fwd_name -> len,
				   (const char *)ddns_fwd_name -> data,
				   ddns_address, logstr);
	else
		log_info("Added new forward map from %.*s to %s",
			  (int)ddns_fwd_name -> len,
			  (const char *)ddns_fwd_name -> data, ddns_address);

#if defined (DEBUG_DNS_UPDATES)
	print_dns_status ((int)result, &updqueue);
#endif

	/*
	 * If this query succeeds, the updater can conclude that the current
	 * client was the last client associated with the domain name, and that
	 * the name now contains the updated A RR. The A RR update is now
	 * complete (and a client updater is finished, while a server would
	 * then proceed to perform a PTR RR update).
	 *   -- "Interaction between DHCP and DNS"
	 */

	/*
	 * If the second query fails with NXRRSET, the updater must conclude
	 * that the client's desired name is in use by another host.  At this
	 * juncture, the updater can decide (based on some administrative
	 * configuration outside of the scope of this document) whether to let
	 * the existing owner of the name keep that name, and to (possibly)
	 * perform some name disambiguation operation on behalf of the current
	 * client, or to replace the RRs on the name with RRs that represent
	 * the current client. If the configured policy allows replacement of
	 * existing records, the updater submits a query that deletes the
	 * existing A RR and the existing DHCID RR, adding A and DHCID RRs that
	 * represent the IP address and client-identity of the new client.
	 *   -- "Interaction between DHCP and DNS"
	 */

  error:
	while (!ISC_LIST_EMPTY (updqueue)) {
		updrec = ISC_LIST_HEAD (updqueue);
		ISC_LIST_UNLINK (updqueue, updrec, r_link);
		minires_freeupdrec (updrec);
	}

	return result;
}

isc_result_t ddns_remove_a (struct data_string *ddns_fwd_name,
			    struct iaddr ddns_addr,
			    struct data_string *ddns_dhcid)
{
	ns_updque updqueue;
	ns_updrec *updrec;
	isc_result_t result = SERVFAIL;
	char ddns_address [16];

	if (ddns_addr.len != 4)
		return ISC_R_INVALIDARG;

	/* %Audit% Cannot exceed 16 bytes. %2004.06.17,Safe% */
	sprintf (ddns_address, "%u.%u.%u.%u",
		  ddns_addr.iabuf[0], ddns_addr.iabuf[1],
		  ddns_addr.iabuf[2], ddns_addr.iabuf[3]);

	/*
	 * The entity chosen to handle the A record for this client (either the
	 * client or the server) SHOULD delete the A record that was added when
	 * the lease was made to the client.
	 *
	 * In order to perform this delete, the updater prepares an UPDATE
	 * query which contains two prerequisites.  The first prerequisite
	 * asserts that the DHCID RR exists whose data is the client identity
	 * described in Section 4.3. The second prerequisite asserts that the
	 * data in the A RR contains the IP address of the lease that has
	 * expired or been released.
	 *   -- "Interaction between DHCP and DNS"
	 */

	ISC_LIST_INIT (updqueue);

	/*
	 * DHCID RR exists, and matches client identity.
	 */
	updrec = minires_mkupdrec (S_PREREQ,
				   (const char *)ddns_fwd_name -> data,
				   C_IN, T_DHCID,0);
	if (!updrec) {
		result = ISC_R_NOMEMORY;
		goto error;
	}

	updrec -> r_data = ddns_dhcid -> data;
	updrec -> r_size = ddns_dhcid -> len;
	updrec -> r_opcode = YXRRSET;

	ISC_LIST_APPEND (updqueue, updrec, r_link);


	/*
	 * A RR matches the expiring lease.
	 */
	updrec = minires_mkupdrec (S_PREREQ,
				   (const char *)ddns_fwd_name -> data,
				   C_IN, T_A, 0);
	if (!updrec) {
		result = ISC_R_NOMEMORY;
		goto error;
	}

	updrec -> r_data = (unsigned char *)ddns_address;
	updrec -> r_size = strlen (ddns_address);
	updrec -> r_opcode = YXRRSET;

	ISC_LIST_APPEND (updqueue, updrec, r_link);


	/*
	 * Delete appropriate A RR.
	 */
	updrec = minires_mkupdrec (S_UPDATE,
				   (const char *)ddns_fwd_name -> data,
				   C_IN, T_A, 0);
	if (!updrec) {
		result = ISC_R_NOMEMORY;
		goto error;
	}

	updrec -> r_data = (unsigned char *)ddns_address;
	updrec -> r_size = strlen (ddns_address);
	updrec -> r_opcode = DELETE;

	ISC_LIST_APPEND (updqueue, updrec, r_link);

	/*
	 * Attempt to perform the update.
	 */
	result = minires_nupdate (&resolver_state, ISC_LIST_HEAD (updqueue));
	print_dns_status ((int)result, &updqueue);

	/*
	 * If the query fails, the updater MUST NOT delete the DNS name.  It
	 * may be that the host whose lease on the server has expired has moved
	 * to another network and obtained a lease from a different server,
	 * which has caused the client's A RR to be replaced. It may also be
	 * that some other client has been configured with a name that matches
	 * the name of the DHCP client, and the policy was that the last client
	 * to specify the name would get the name.  In this case, the DHCID RR
	 * will no longer match the updater's notion of the client-identity of
	 * the host pointed to by the DNS name.
	 *   -- "Interaction between DHCP and DNS"
	 */

	if (result != ISC_R_SUCCESS) {
		/* If the rrset isn't there, we didn't need to do the
		   delete, which is success. */
		if (result == ISC_R_NXRRSET || result == ISC_R_NXDOMAIN)
			result = ISC_R_SUCCESS;	
		goto error;
	}

	while (!ISC_LIST_EMPTY (updqueue)) {
		updrec = ISC_LIST_HEAD (updqueue);
		ISC_LIST_UNLINK (updqueue, updrec, r_link);
		minires_freeupdrec (updrec);
	}

	/* If the deletion of the A succeeded, and there are no A records
	   left for this domain, then we can blow away the DHCID record
	   as well.   We can't blow away the DHCID record above because
	   it's possible that more than one A has been added to this
	   domain name. */
	ISC_LIST_INIT (updqueue);

	/*
	 * A RR does not exist.
	 */
	updrec = minires_mkupdrec (S_PREREQ,
				   (const char *)ddns_fwd_name -> data,
				   C_IN, T_A, 0);
	if (!updrec) {
		result = ISC_R_NOMEMORY;
		goto error;
	}

	updrec -> r_data = (unsigned char *)0;
	updrec -> r_size = 0;
	updrec -> r_opcode = NXRRSET;

	ISC_LIST_APPEND (updqueue, updrec, r_link);

	/*
	 * Delete appropriate DHCID RR.
	 */
	updrec = minires_mkupdrec (S_UPDATE,
				  (const char *)ddns_fwd_name -> data,
				   C_IN, T_DHCID, 0);
	if (!updrec) {
		result = ISC_R_NOMEMORY;
		goto error;
	}

	updrec -> r_data = ddns_dhcid -> data;
	updrec -> r_size = ddns_dhcid -> len;
	updrec -> r_opcode = DELETE;

	ISC_LIST_APPEND (updqueue, updrec, r_link);

	/*
	 * Attempt to perform the update.
	 */
	result = minires_nupdate (&resolver_state, ISC_LIST_HEAD (updqueue));
	print_dns_status ((int)result, &updqueue);

	/* Fall through. */
  error:

	while (!ISC_LIST_EMPTY (updqueue)) {
		updrec = ISC_LIST_HEAD (updqueue);
		ISC_LIST_UNLINK (updqueue, updrec, r_link);
		minires_freeupdrec (updrec);
	}

	return result;
}


#endif /* NSUPDATE */

HASH_FUNCTIONS (dns_zone, const char *, struct dns_zone, dns_zone_hash_t,
		dns_zone_reference, dns_zone_dereference, do_case_hash)
