/*
 * Copyright (C) 2006-2010 by Internet Systems Consortium, Inc. ("ISC")
 *
 * 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.
 */

#include "dhcpd.h"

#ifdef DHCPv6

/*
 * We use print_hex_1() to output DUID values. We could actually output 
 * the DUID with more information... MAC address if using type 1 or 3, 
 * and so on. However, RFC 3315 contains Grave Warnings against actually 
 * attempting to understand a DUID.
 */

/* 
 * TODO: gettext() or other method of localization for the messages
 *       for status codes (and probably for log formats eventually)
 * TODO: refactoring (simplify, simplify, simplify)
 * TODO: support multiple shared_networks on each interface (this 
 *       will allow the server to issue multiple IPv6 addresses to 
 *       a single interface)
 */

/*
 * DHCPv6 Reply workflow assist.  A Reply packet is built by various
 * different functions; this gives us one location where we keep state
 * regarding a reply.
 */
struct reply_state {
	/* root level persistent state */
	struct shared_network *shared;
	struct host_decl *host;
	struct subnet *subnet; /* Used to match fixed-addrs to subnet scopes. */
	struct option_state *opt_state;
	struct packet *packet;
	struct data_string client_id;

	/* IA level persistent state */
	unsigned ia_count;
	unsigned pd_count;
	unsigned client_resources;
	isc_boolean_t resources_included;
	isc_boolean_t static_lease;
	unsigned static_prefixes;
	struct ia_xx *ia;
	struct ia_xx *old_ia;
	struct option_state *reply_ia;
	struct data_string fixed;

	/* IAADDR/PREFIX level persistent state */
	struct iasubopt *lease;

	/*
	 * "t1", "t2", preferred, and valid lifetimes records for calculating
	 * t1 and t2 (min/max).
	 */
	u_int32_t renew, rebind, prefer, valid;

	/* Client-requested valid and preferred lifetimes. */
	u_int32_t client_valid, client_prefer;

	/* Chosen values to transmit for valid and preferred lifetimes. */
	u_int32_t send_valid, send_prefer;

	/* Preferred prefix length (-1 is any). */
	int preflen;

	/* Index into the data field that has been consumed. */
	unsigned cursor;

	union reply_buffer {
		unsigned char data[65536];
		struct dhcpv6_packet reply;
	} buf;
};

/* 
 * Prototypes local to this file.
 */
static int get_encapsulated_IA_state(struct option_state **enc_opt_state,
				     struct data_string *enc_opt_data,
				     struct packet *packet,
				     struct option_cache *oc,
				     int offset);
static void build_dhcpv6_reply(struct data_string *, struct packet *);
static isc_result_t shared_network_from_packet6(struct shared_network **shared,
						struct packet *packet);
static void seek_shared_host(struct host_decl **hp,
			     struct shared_network *shared);
static isc_boolean_t fixed_matches_shared(struct host_decl *host,
					  struct shared_network *shared);
static isc_result_t reply_process_ia_na(struct reply_state *reply,
					struct option_cache *ia);
static isc_result_t reply_process_ia_ta(struct reply_state *reply,
					struct option_cache *ia);
static isc_result_t reply_process_addr(struct reply_state *reply,
				       struct option_cache *addr);
static isc_boolean_t address_is_owned(struct reply_state *reply,
				      struct iaddr *addr);
static isc_boolean_t temporary_is_available(struct reply_state *reply,
					    struct iaddr *addr);
static isc_result_t find_client_temporaries(struct reply_state *reply);
static isc_result_t reply_process_try_addr(struct reply_state *reply,
					   struct iaddr *addr);
static isc_result_t find_client_address(struct reply_state *reply);
static isc_result_t reply_process_is_addressed(struct reply_state *reply,
					       struct binding_scope **scope,
					       struct group *group);
static isc_result_t reply_process_send_addr(struct reply_state *reply,
					    struct iaddr *addr);
static struct iasubopt *lease_compare(struct iasubopt *alpha,
				      struct iasubopt *beta);
static isc_result_t reply_process_ia_pd(struct reply_state *reply,
					struct option_cache *ia_pd);
static isc_result_t reply_process_prefix(struct reply_state *reply,
					 struct option_cache *pref);
static isc_boolean_t prefix_is_owned(struct reply_state *reply,
				     struct iaddrcidrnet *pref);
static isc_result_t find_client_prefix(struct reply_state *reply);
static isc_result_t reply_process_try_prefix(struct reply_state *reply,
					     struct iaddrcidrnet *pref);
static isc_result_t reply_process_is_prefixed(struct reply_state *reply,
					      struct binding_scope **scope,
					      struct group *group);
static isc_result_t reply_process_send_prefix(struct reply_state *reply,
					      struct iaddrcidrnet *pref);
static struct iasubopt *prefix_compare(struct reply_state *reply,
				       struct iasubopt *alpha,
				       struct iasubopt *beta);
static int find_hosts_by_duid_chaddr(struct host_decl **host,
				     const struct data_string *client_id);
/*
 * This function returns the time since DUID time start for the
 * given time_t value.
 */
static u_int32_t
duid_time(time_t when) {
	/*
	 * This time is modulo 2^32.
	 */
	while ((when - DUID_TIME_EPOCH) > 4294967295u) {
		/* use 2^31 to avoid spurious compiler warnings */
		when -= 2147483648u;
		when -= 2147483648u;
	}

	return when - DUID_TIME_EPOCH;
}


/* 
 * Server DUID.
 *
 * This must remain the same for the lifetime of this server, because
 * clients return the server DUID that we sent them in Request packets.
 *
 * We pick the server DUID like this:
 *
 * 1. Check dhcpd.conf - any value the administrator has configured 
 *    overrides any possible values.
 * 2. Check the leases.txt - we want to use the previous value if 
 *    possible.
 * 3. Check if dhcpd.conf specifies a type of server DUID to use,
 *    and generate that type.
 * 4. Generate a type 1 (time + hardware address) DUID.
 */
static struct data_string server_duid;

/*
 * Check if the server_duid has been set.
 */
isc_boolean_t
server_duid_isset(void) {
	return (server_duid.data != NULL);
}

/*
 * Return the server_duid.
 */
void
copy_server_duid(struct data_string *ds, const char *file, int line) {
	data_string_copy(ds, &server_duid, file, line);
}

/*
 * Set the server DUID to a specified value. This is used when
 * the server DUID is stored in persistent memory (basically the
 * leases.txt file).
 */
void
set_server_duid(struct data_string *new_duid) {
	/* INSIST(new_duid != NULL); */
	/* INSIST(new_duid->data != NULL); */

	if (server_duid_isset()) {
		data_string_forget(&server_duid, MDL);
	}
	data_string_copy(&server_duid, new_duid, MDL);
}


/*
 * Set the server DUID based on the D6O_SERVERID option. This handles
 * the case where the administrator explicitly put it in the dhcpd.conf 
 * file.
 */
isc_result_t
set_server_duid_from_option(void) {
	struct option_state *opt_state;
	struct option_cache *oc;
	struct data_string option_duid;
	isc_result_t ret_val;

	opt_state = NULL;
	if (!option_state_allocate(&opt_state, MDL)) {
		log_fatal("No memory for server DUID.");
	}

	execute_statements_in_scope(NULL, NULL, NULL, NULL, NULL,
				    opt_state, &global_scope, root_group, NULL);

	oc = lookup_option(&dhcpv6_universe, opt_state, D6O_SERVERID);
	if (oc == NULL) {
		ret_val = ISC_R_NOTFOUND;
	} else {
		memset(&option_duid, 0, sizeof(option_duid));
		if (!evaluate_option_cache(&option_duid, NULL, NULL, NULL,
					   opt_state, NULL, &global_scope,
					   oc, MDL)) {
			ret_val = ISC_R_UNEXPECTED;
		} else {
			set_server_duid(&option_duid);
			data_string_forget(&option_duid, MDL);
			ret_val = ISC_R_SUCCESS;
		}
	}

	option_state_dereference(&opt_state, MDL);

	return ret_val;
}

/*
 * DUID layout, as defined in RFC 3315, section 9.
 * 
 * We support type 1 (hardware address plus time) and type 3 (hardware
 * address).
 *
 * We can support type 2 for specific vendors in the future, if they 
 * publish the specification. And of course there may be additional
 * types later.
 */
static int server_duid_type = DUID_LLT;

/* 
 * Set the DUID type.
 */
void
set_server_duid_type(int type) {
	server_duid_type = type;
}

/*
 * Generate a new server DUID. This is done if there was no DUID in 
 * the leases.txt or in the dhcpd.conf file.
 */
isc_result_t
generate_new_server_duid(void) {
	struct interface_info *p;
	u_int32_t time_val;
	struct data_string generated_duid;

	/*
	 * Verify we have a type that we support.
	 */
	if ((server_duid_type != DUID_LL) && (server_duid_type != DUID_LLT)) {
		log_error("Invalid DUID type %d specified, "
			  "only LL and LLT types supported", server_duid_type);
		return DHCP_R_INVALIDARG;
	}

	/*
	 * Find an interface with a hardware address.
	 * Any will do. :)
	 */
	for (p = interfaces; p != NULL; p = p->next) {
		if (p->hw_address.hlen > 0) {
			break;
		}
	}
	if (p == NULL) {
		return ISC_R_UNEXPECTED;
	}

	/*
	 * Build our DUID.
	 */
	memset(&generated_duid, 0, sizeof(generated_duid));
	if (server_duid_type == DUID_LLT) {
		time_val = duid_time(time(NULL));
		generated_duid.len = 8 + p->hw_address.hlen - 1;
		if (!buffer_allocate(&generated_duid.buffer,
				     generated_duid.len, MDL)) {
			log_fatal("No memory for server DUID.");
		}
		generated_duid.data = generated_duid.buffer->data;
		putUShort(generated_duid.buffer->data, DUID_LLT);
		putUShort(generated_duid.buffer->data + 2,
			  p->hw_address.hbuf[0]);
		putULong(generated_duid.buffer->data + 4, time_val);
		memcpy(generated_duid.buffer->data + 8,
		       p->hw_address.hbuf+1, p->hw_address.hlen-1);
	} else if (server_duid_type == DUID_LL) {
		generated_duid.len = 4 + p->hw_address.hlen - 1;
		if (!buffer_allocate(&generated_duid.buffer,
				     generated_duid.len, MDL)) {
			log_fatal("No memory for server DUID.");
		}
		generated_duid.data = generated_duid.buffer->data;
		putUShort(generated_duid.buffer->data, DUID_LL);
		putUShort(generated_duid.buffer->data + 2,
			  p->hw_address.hbuf[0]);
		memcpy(generated_duid.buffer->data + 4,
		       p->hw_address.hbuf+1, p->hw_address.hlen-1);
	} else {
		log_fatal("Unsupported server DUID type %d.", server_duid_type);
	}

	set_server_duid(&generated_duid);
	data_string_forget(&generated_duid, MDL);

	return ISC_R_SUCCESS;
}

/*
 * Get the client identifier from the packet.
 */
isc_result_t
get_client_id(struct packet *packet, struct data_string *client_id) {
	struct option_cache *oc;

	/*
	 * Verify our client_id structure is empty.
	 */
	if ((client_id->data != NULL) || (client_id->len != 0)) {
		return DHCP_R_INVALIDARG;
	}

	oc = lookup_option(&dhcpv6_universe, packet->options, D6O_CLIENTID);
	if (oc == NULL) {
		return ISC_R_NOTFOUND;
	}

	if (!evaluate_option_cache(client_id, packet, NULL, NULL,
				   packet->options, NULL,
				   &global_scope, oc, MDL)) {
		return ISC_R_FAILURE;
	}

	return ISC_R_SUCCESS;
}

/*
 * Message validation, defined in RFC 3315, sections 15.2, 15.5, 15.7:
 *
 *    Servers MUST discard any Solicit messages that do not include a
 *    Client Identifier option or that do include a Server Identifier
 *    option.
 */
int
valid_client_msg(struct packet *packet, struct data_string *client_id) {
	int ret_val;
	struct option_cache *oc;
	struct data_string data;

	ret_val = 0;
	memset(client_id, 0, sizeof(*client_id));
	memset(&data, 0, sizeof(data));

	switch (get_client_id(packet, client_id)) {
		case ISC_R_SUCCESS:
			break;
		case ISC_R_NOTFOUND:
			log_debug("Discarding %s from %s; "
				  "client identifier missing",
				  dhcpv6_type_names[packet->dhcpv6_msg_type],
				  piaddr(packet->client_addr));
			goto exit;
		default:
			log_error("Error processing %s from %s; "
				  "unable to evaluate Client Identifier",
				  dhcpv6_type_names[packet->dhcpv6_msg_type],
				  piaddr(packet->client_addr));
			goto exit;
	}

	/*
	 * Required by RFC 3315, section 15.
	 */
	if (packet->unicast) {
		log_debug("Discarding %s from %s; packet sent unicast "
			  "(CLIENTID %s)",
			  dhcpv6_type_names[packet->dhcpv6_msg_type],
			  piaddr(packet->client_addr),
			  print_hex_1(client_id->len, client_id->data, 60));
		goto exit;
	}


	oc = lookup_option(&dhcpv6_universe, packet->options, D6O_SERVERID);
	if (oc != NULL) {
		if (evaluate_option_cache(&data, packet, NULL, NULL,
					  packet->options, NULL,
					  &global_scope, oc, MDL)) {
			log_debug("Discarding %s from %s; "
				  "server identifier found "
				  "(CLIENTID %s, SERVERID %s)",
				  dhcpv6_type_names[packet->dhcpv6_msg_type],
				  piaddr(packet->client_addr),
				  print_hex_1(client_id->len,
				  	      client_id->data, 60),
				  print_hex_2(data.len,
				  	      data.data, 60));
		} else {
			log_debug("Discarding %s from %s; "
				  "server identifier found "
				  "(CLIENTID %s)",
				  dhcpv6_type_names[packet->dhcpv6_msg_type],
				  print_hex_1(client_id->len,
				  	      client_id->data, 60),
				  piaddr(packet->client_addr));
		}
		goto exit;
	}

	/* looks good */
	ret_val = 1;

exit:
	if (data.len > 0) {
		data_string_forget(&data, MDL);
	}
	if (!ret_val) {
		if (client_id->len > 0) {
			data_string_forget(client_id, MDL);
		}
	}
	return ret_val;
}

/*
 * Response validation, defined in RFC 3315, sections 15.4, 15.6, 15.8, 
 * 15.9 (slightly different wording, but same meaning):
 *
 *   Servers MUST discard any received Request message that meet any of
 *   the following conditions:
 *
 *   -  the message does not include a Server Identifier option.
 *   -  the contents of the Server Identifier option do not match the
 *      server's DUID.
 *   -  the message does not include a Client Identifier option.
 */
int
valid_client_resp(struct packet *packet,
		  struct data_string *client_id,
		  struct data_string *server_id)
{
	int ret_val;
	struct option_cache *oc;

	/* INSIST((duid.data != NULL) && (duid.len > 0)); */

	ret_val = 0;
	memset(client_id, 0, sizeof(*client_id));
	memset(server_id, 0, sizeof(*server_id));

	switch (get_client_id(packet, client_id)) {
		case ISC_R_SUCCESS:
			break;
		case ISC_R_NOTFOUND:
			log_debug("Discarding %s from %s; "
				  "client identifier missing",
				  dhcpv6_type_names[packet->dhcpv6_msg_type],
				  piaddr(packet->client_addr));
			goto exit;
		default:
			log_error("Error processing %s from %s; "
				  "unable to evaluate Client Identifier",
				  dhcpv6_type_names[packet->dhcpv6_msg_type],
				  piaddr(packet->client_addr));
			goto exit;
	}

	oc = lookup_option(&dhcpv6_universe, packet->options, D6O_SERVERID);
	if (oc == NULL) {
		log_debug("Discarding %s from %s: "
			  "server identifier missing (CLIENTID %s)",
			  dhcpv6_type_names[packet->dhcpv6_msg_type],
			  piaddr(packet->client_addr),
			  print_hex_1(client_id->len, client_id->data, 60));
		goto exit;
	}
	if (!evaluate_option_cache(server_id, packet, NULL, NULL,
				   packet->options, NULL,
				   &global_scope, oc, MDL)) {
		log_error("Error processing %s from %s; "
			  "unable to evaluate Server Identifier (CLIENTID %s)",
			  dhcpv6_type_names[packet->dhcpv6_msg_type],
			  piaddr(packet->client_addr),
			  print_hex_1(client_id->len, client_id->data, 60));
		goto exit;
	}
	if ((server_duid.len != server_id->len) ||
	    (memcmp(server_duid.data, server_id->data, server_duid.len) != 0)) {
		log_debug("Discarding %s from %s; "
			  "not our server identifier "
			  "(CLIENTID %s, SERVERID %s, server DUID %s)",
			  dhcpv6_type_names[packet->dhcpv6_msg_type],
			  piaddr(packet->client_addr),
			  print_hex_1(client_id->len, client_id->data, 60),
			  print_hex_2(server_id->len, server_id->data, 60),
			  print_hex_3(server_duid.len, server_duid.data, 60));
		goto exit;
	}

	/* looks good */
	ret_val = 1;

exit:
	if (!ret_val) {
		if (server_id->len > 0) {
			data_string_forget(server_id, MDL);
		}
		if (client_id->len > 0) {
			data_string_forget(client_id, MDL);
		}
	}
	return ret_val;
}

/*
 * Information request validation, defined in RFC 3315, section 15.12:
 *
 *   Servers MUST discard any received Information-request message that
 *   meets any of the following conditions:
 *
 *   -  The message includes a Server Identifier option and the DUID in
 *      the option does not match the server's DUID.
 *
 *   -  The message includes an IA option.
 */
int
valid_client_info_req(struct packet *packet, struct data_string *server_id) {
	int ret_val;
	struct option_cache *oc;
	struct data_string client_id;
	char client_id_str[80];	/* print_hex_1() uses maximum 60 characters,
				   plus a few more for extra information */

	ret_val = 0;
	memset(server_id, 0, sizeof(*server_id));

	/*
	 * Make a string that we can print out to give more 
	 * information about the client if we need to.
	 *
	 * By RFC 3315, Section 18.1.5 clients SHOULD have a 
	 * client-id on an Information-request packet, but it 
	 * is not strictly necessary.
	 */
	if (get_client_id(packet, &client_id) == ISC_R_SUCCESS) {
		snprintf(client_id_str, sizeof(client_id_str), " (CLIENTID %s)",
			 print_hex_1(client_id.len, client_id.data, 60));
		data_string_forget(&client_id, MDL);
	} else {
		client_id_str[0] = '\0';
	}

	/*
	 * Required by RFC 3315, section 15.
	 */
	if (packet->unicast) {
		log_debug("Discarding %s from %s; packet sent unicast%s",
			  dhcpv6_type_names[packet->dhcpv6_msg_type],
			  piaddr(packet->client_addr), client_id_str);
		goto exit;
	}

	oc = lookup_option(&dhcpv6_universe, packet->options, D6O_IA_NA);
	if (oc != NULL) {
		log_debug("Discarding %s from %s; "
			  "IA_NA option present%s",
			  dhcpv6_type_names[packet->dhcpv6_msg_type],
			  piaddr(packet->client_addr), client_id_str);
		goto exit;
	}
	oc = lookup_option(&dhcpv6_universe, packet->options, D6O_IA_TA);
	if (oc != NULL) {
		log_debug("Discarding %s from %s; "
			  "IA_TA option present%s",
			  dhcpv6_type_names[packet->dhcpv6_msg_type],
			  piaddr(packet->client_addr), client_id_str);
		goto exit;
	}
	oc = lookup_option(&dhcpv6_universe, packet->options, D6O_IA_PD);
	if (oc != NULL) {
		log_debug("Discarding %s from %s; "
			  "IA_PD option present%s",
			  dhcpv6_type_names[packet->dhcpv6_msg_type],
			  piaddr(packet->client_addr), client_id_str);
		goto exit;
	}

	oc = lookup_option(&dhcpv6_universe, packet->options, D6O_SERVERID);
	if (oc != NULL) {
		if (!evaluate_option_cache(server_id, packet, NULL, NULL,
					   packet->options, NULL,
					   &global_scope, oc, MDL)) {
			log_error("Error processing %s from %s; "
				  "unable to evaluate Server Identifier%s",
				  dhcpv6_type_names[packet->dhcpv6_msg_type],
				  piaddr(packet->client_addr), client_id_str);
			goto exit;
		}
		if ((server_duid.len != server_id->len) ||
		    (memcmp(server_duid.data, server_id->data,
		    	    server_duid.len) != 0)) {
			log_debug("Discarding %s from %s; "
				  "not our server identifier "
				  "(SERVERID %s, server DUID %s)%s",
				  dhcpv6_type_names[packet->dhcpv6_msg_type],
				  piaddr(packet->client_addr),
				  print_hex_1(server_id->len,
				  	      server_id->data, 60),
				  print_hex_2(server_duid.len,
				  	      server_duid.data, 60),
				  client_id_str);
			goto exit;
		}
	}

	/* looks good */
	ret_val = 1;

exit:
	if (!ret_val) {
		if (server_id->len > 0) {
			data_string_forget(server_id, MDL);
		}
	}
	return ret_val;
}

/* 
 * Options that we want to send, in addition to what was requested
 * via the ORO.
 */
static const int required_opts[] = {
	D6O_CLIENTID,
	D6O_SERVERID,
	D6O_STATUS_CODE,
	D6O_PREFERENCE,
	0
};
static const int required_opts_NAA[] = {
	D6O_CLIENTID,
	D6O_SERVERID,
	D6O_STATUS_CODE,
	0
};
static const int required_opts_solicit[] = {
	D6O_CLIENTID,
	D6O_SERVERID,
	D6O_IA_NA,
	D6O_IA_TA,
	D6O_IA_PD,
	D6O_RAPID_COMMIT,
	D6O_STATUS_CODE,
	D6O_RECONF_ACCEPT,
	D6O_PREFERENCE,
	0
};
static const int required_opts_agent[] = {
	D6O_INTERFACE_ID,
	D6O_RELAY_MSG,
	0
};
static const int required_opts_IA[] = {
	D6O_IAADDR,
	D6O_STATUS_CODE,
	0
};
static const int required_opts_IA_PD[] = {
	D6O_IAPREFIX,
	D6O_STATUS_CODE,
	0
};
static const int required_opts_STATUS_CODE[] = {
	D6O_STATUS_CODE,
	0
};

/*
 * Extracts from packet contents an IA_* option, storing the IA structure
 * in its entirety in enc_opt_data, and storing any decoded DHCPv6 options
 * in enc_opt_state for later lookup and evaluation.  The 'offset' indicates
 * where in the IA_* the DHCPv6 options commence.
 */
static int
get_encapsulated_IA_state(struct option_state **enc_opt_state,
			  struct data_string *enc_opt_data,
			  struct packet *packet,
			  struct option_cache *oc,
			  int offset)
{
	/* 
	 * Get the raw data for the encapsulated options.
	 */
	memset(enc_opt_data, 0, sizeof(*enc_opt_data));
	if (!evaluate_option_cache(enc_opt_data, packet,
				   NULL, NULL, packet->options, NULL,
				   &global_scope, oc, MDL)) {
		log_error("get_encapsulated_IA_state: "
			  "error evaluating raw option.");
		return 0;
	}
	if (enc_opt_data->len < offset) {
		log_error("get_encapsulated_IA_state: raw option too small.");
		data_string_forget(enc_opt_data, MDL);
		return 0;
	}

	/*
	 * Now create the option state structure, and pass it to the 
	 * function that parses options.
	 */
	*enc_opt_state = NULL;
	if (!option_state_allocate(enc_opt_state, MDL)) {
		log_error("get_encapsulated_IA_state: no memory for options.");
		data_string_forget(enc_opt_data, MDL);
		return 0;
	}
	if (!parse_option_buffer(*enc_opt_state,
				 enc_opt_data->data + offset, 
				 enc_opt_data->len - offset,
				 &dhcpv6_universe)) {
		log_error("get_encapsulated_IA_state: error parsing options.");
		option_state_dereference(enc_opt_state, MDL);
		data_string_forget(enc_opt_data, MDL);
		return 0;
	}

	return 1;
}

static int
set_status_code(u_int16_t status_code, const char *status_message,
		struct option_state *opt_state)
{
	struct data_string d;
	int ret_val;

	memset(&d, 0, sizeof(d));
	d.len = sizeof(status_code) + strlen(status_message);
	if (!buffer_allocate(&d.buffer, d.len, MDL)) {
		log_fatal("set_status_code: no memory for status code.");
	}
	d.data = d.buffer->data;
	putUShort(d.buffer->data, status_code);
	memcpy(d.buffer->data + sizeof(status_code), 
	       status_message, d.len - sizeof(status_code));
	if (!save_option_buffer(&dhcpv6_universe, opt_state, 
				d.buffer, (unsigned char *)d.data, d.len, 
				D6O_STATUS_CODE, 0)) {
		log_error("set_status_code: error saving status code.");
		ret_val = 0;
	} else {
		ret_val = 1;
	}
	data_string_forget(&d, MDL);
	return ret_val;
}

/*
 * We have a set of operations we do to set up the reply packet, which
 * is the same for many message types.
 */
static int
start_reply(struct packet *packet,
	    const struct data_string *client_id, 
	    const struct data_string *server_id,
	    struct option_state **opt_state,
	    struct dhcpv6_packet *reply)
{
	struct option_cache *oc;
	const unsigned char *server_id_data;
	int server_id_len;

	/*
	 * Build our option state for reply.
	 */
	*opt_state = NULL;
	if (!option_state_allocate(opt_state, MDL)) {
		log_error("start_reply: no memory for option_state.");
		return 0;
	}
	execute_statements_in_scope(NULL, packet, NULL, NULL,
				    packet->options, *opt_state,
				    &global_scope, root_group, NULL);

	/*
	 * A small bit of special handling for Solicit messages.
	 *
	 * We could move the logic into a flag, but for now just check
	 * explicitly.
	 */
	if (packet->dhcpv6_msg_type == DHCPV6_SOLICIT) {
		reply->msg_type = DHCPV6_ADVERTISE;

		/*
		 * If:
		 * - this message type supports rapid commit (Solicit), and
		 * - the server is configured to supply a rapid commit, and
		 * - the client requests a rapid commit,
		 * Then we add a rapid commit option, and send Reply (instead
		 * of an Advertise).
		 */
		oc = lookup_option(&dhcpv6_universe,
				   *opt_state, D6O_RAPID_COMMIT);
		if (oc != NULL) {
			oc = lookup_option(&dhcpv6_universe,
					   packet->options, D6O_RAPID_COMMIT);
			if (oc != NULL) {
				/* Rapid-commit in action. */
				reply->msg_type = DHCPV6_REPLY;
			} else {
				/* Don't want a rapid-commit in advertise. */
				delete_option(&dhcpv6_universe,
					      *opt_state, D6O_RAPID_COMMIT);
			}
		}
	} else {
		reply->msg_type = DHCPV6_REPLY;
		/* Delete the rapid-commit from the sent options. */
		oc = lookup_option(&dhcpv6_universe,
				   *opt_state, D6O_RAPID_COMMIT);
		if (oc != NULL) {
			delete_option(&dhcpv6_universe,
				      *opt_state, D6O_RAPID_COMMIT);
		}
	}

	/* 
	 * Use the client's transaction identifier for the reply.
	 */
	memcpy(reply->transaction_id, packet->dhcpv6_transaction_id, 
	       sizeof(reply->transaction_id));

	/* 
	 * RFC 3315, section 18.2 says we need server identifier and
	 * client identifier.
	 *
	 * If the server ID is defined via the configuration file, then
	 * it will already be present in the option state at this point, 
	 * so we don't need to set it.
	 *
	 * If we have a server ID passed in from the caller, 
	 * use that, otherwise use the global DUID.
	 */
	oc = lookup_option(&dhcpv6_universe, *opt_state, D6O_SERVERID);
	if (oc == NULL) {
		if (server_id == NULL) {
			server_id_data = server_duid.data;
			server_id_len = server_duid.len;
		} else {
			server_id_data = server_id->data;
			server_id_len = server_id->len;
		}
		if (!save_option_buffer(&dhcpv6_universe, *opt_state, 
					NULL, (unsigned char *)server_id_data,
					server_id_len, D6O_SERVERID, 0)) {
				log_error("start_reply: "
					  "error saving server identifier.");
				return 0;
		}
	}

	if (client_id->buffer != NULL) {
		if (!save_option_buffer(&dhcpv6_universe, *opt_state, 
					client_id->buffer, 
					(unsigned char *)client_id->data, 
					client_id->len, 
					D6O_CLIENTID, 0)) {
			log_error("start_reply: error saving "
				  "client identifier.");
			return 0;
		}
	}

	/*
	 * If the client accepts reconfiguration, let it know that we
	 * will send them.
	 *
	 * Note: we don't actually do this yet, but DOCSIS requires we
	 *       claim to.
	 */
	oc = lookup_option(&dhcpv6_universe, packet->options,
			   D6O_RECONF_ACCEPT);
	if (oc != NULL) {
		if (!save_option_buffer(&dhcpv6_universe, *opt_state,
					NULL, (unsigned char *)"", 0, 
					D6O_RECONF_ACCEPT, 0)) {
			log_error("start_reply: "
				  "error saving RECONF_ACCEPT option.");
			option_state_dereference(opt_state, MDL);
			return 0;
		}
	}

	return 1;
}

/*
 * Try to get the IPv6 address the client asked for from the
 * pool.
 *
 * addr is the result (should be a pointer to NULL on entry)
 * pool is the pool to search in
 * requested_addr is the address the client wants
 */
static isc_result_t
try_client_v6_address(struct iasubopt **addr,
		      struct ipv6_pool *pool,
		      const struct data_string *requested_addr)
{
	struct in6_addr tmp_addr;
	isc_result_t result;

	if (requested_addr->len < sizeof(tmp_addr)) {
		return DHCP_R_INVALIDARG;
	}
	memcpy(&tmp_addr, requested_addr->data, sizeof(tmp_addr));
	if (IN6_IS_ADDR_UNSPECIFIED(&tmp_addr)) {
		return ISC_R_FAILURE;
	}

	/*
	 * The address is not covered by this (or possibly any) dynamic
	 * range.
	 */
	if (!ipv6_in_pool(&tmp_addr, pool)) {
		return ISC_R_ADDRNOTAVAIL;
	}

	if (lease6_exists(pool, &tmp_addr)) {
		return ISC_R_ADDRINUSE;
	}

	result = iasubopt_allocate(addr, MDL);
	if (result != ISC_R_SUCCESS) {
		return result;
	}
	(*addr)->addr = tmp_addr;
	(*addr)->plen = 0;

	/* Default is soft binding for 2 minutes. */
	result = add_lease6(pool, *addr, cur_time + 120);
	if (result != ISC_R_SUCCESS) {
		iasubopt_dereference(addr, MDL);
	}
	return result;
}

/*
 * Get an IPv6 address for the client.
 *
 * addr is the result (should be a pointer to NULL on entry)
 * packet is the information about the packet from the client
 * requested_iaaddr is a hint from the client
 * client_id is the DUID for the client
 */
static isc_result_t 
pick_v6_address(struct iasubopt **addr, struct shared_network *shared_network,
		const struct data_string *client_id)
{
	struct ipv6_pool *p;
	int i;
	int start_pool;
	unsigned int attempts;
	char tmp_buf[INET6_ADDRSTRLEN];

	/*
	 * No address pools, we're done.
	 */
	if (shared_network->ipv6_pools == NULL) {
		log_debug("Unable to pick client address: "
			  "no IPv6 pools on this shared network");
		return ISC_R_NORESOURCES;
	}
	for (i = 0;; i++) {
		p = shared_network->ipv6_pools[i];
		if (p == NULL) {
			log_debug("Unable to pick client address: "
				  "no IPv6 address pools "
				  "on this shared network");
			return ISC_R_NORESOURCES;
		}
		if (p->pool_type == D6O_IA_NA) {
			break;
		}
	}

	/*
	 * Otherwise try to get a lease from the first subnet possible.
	 *
	 * We start looking at the last pool we allocated from, unless
	 * it had a collision trying to allocate an address. This will
	 * tend to move us into less-filled pools.
	 */
	start_pool = shared_network->last_ipv6_pool;
	i = start_pool;
	do {

		p = shared_network->ipv6_pools[i];
		if ((p->pool_type == D6O_IA_NA) &&
		    (create_lease6(p, addr, &attempts, client_id,
				   cur_time + 120) == ISC_R_SUCCESS)) {
			/*
			 * Record the pool used (or next one if there 
			 * was a collision).
			 */
			if (attempts > 1) {
				i++;
				if (shared_network->ipv6_pools[i] == NULL) {
					i = 0;
				}
			}
			shared_network->last_ipv6_pool = i;

			log_debug("Picking pool address %s",
				  inet_ntop(AF_INET6, &((*addr)->addr),
				  	    tmp_buf, sizeof(tmp_buf)));
			return ISC_R_SUCCESS;
		}

		i++;
		if (shared_network->ipv6_pools[i] == NULL) {
			i = 0;
		}
	} while (i != start_pool);

	/*
	 * If we failed to pick an IPv6 address from any of the subnets.
	 * Presumably that means we have no addresses for the client.
	 */
	log_debug("Unable to pick client address: no addresses available");
	return ISC_R_NORESOURCES;
}

/*
 * Try to get the IPv6 prefix the client asked for from the
 * prefix pool.
 *
 * pref is the result (should be a pointer to NULL on entry)
 * pool is the prefix pool to search in
 * requested_pref is the address the client wants
 */
static isc_result_t
try_client_v6_prefix(struct iasubopt **pref,
		     struct ipv6_pool *pool,
		     const struct data_string *requested_pref)
{
	u_int8_t tmp_plen;
	struct in6_addr tmp_pref;
	struct iaddr ia;
	isc_result_t result;

	if (requested_pref->len < sizeof(tmp_plen) + sizeof(tmp_pref)) {
		return DHCP_R_INVALIDARG;
	}
	tmp_plen = (int) requested_pref->data[0];
	if ((tmp_plen < 3) || (tmp_plen > 128)) {
		return ISC_R_FAILURE;
	}
	memcpy(&tmp_pref, requested_pref->data + 1, sizeof(tmp_pref));
	if (IN6_IS_ADDR_UNSPECIFIED(&tmp_pref)) {
		return ISC_R_FAILURE;
	}
	ia.len = 16;
	memcpy(&ia.iabuf, &tmp_pref, 16);
	if (!is_cidr_mask_valid(&ia, (int) tmp_plen)) {
		return ISC_R_FAILURE;
	}

	if (((int)tmp_plen != pool->units) ||
	    !ipv6_in_pool(&tmp_pref, pool)) {
		return ISC_R_FAILURE;
	}

	if (prefix6_exists(pool, &tmp_pref, tmp_plen)) {
		return ISC_R_ADDRINUSE;
	}

	result = iasubopt_allocate(pref, MDL);
	if (result != ISC_R_SUCCESS) {
		return result;
	}
	(*pref)->addr = tmp_pref;
	(*pref)->plen = tmp_plen;

	/* Default is soft binding for 2 minutes. */
	result = add_lease6(pool, *pref, cur_time + 120);
	if (result != ISC_R_SUCCESS) {
		iasubopt_dereference(pref, MDL);
	}
	return result;
}

/*
 * Get an IPv6 prefix for the client.
 *
 * pref is the result (should be a pointer to NULL on entry)
 * packet is the information about the packet from the client
 * requested_iaprefix is a hint from the client
 * plen is -1 or the requested prefix length
 * client_id is the DUID for the client
 */
static isc_result_t 
pick_v6_prefix(struct iasubopt **pref, int plen,
	       struct shared_network *shared_network,
	       const struct data_string *client_id)
{
	struct ipv6_pool *p;
	int i;
	unsigned int attempts;
	char tmp_buf[INET6_ADDRSTRLEN];

	/*
	 * No prefix pools, we're done.
	 */
	if (shared_network->ipv6_pools == NULL) {
		log_debug("Unable to pick client prefix: "
			  "no IPv6 pools on this shared network");
		return ISC_R_NORESOURCES;
	}
	for (i = 0;; i++) {
		p = shared_network->ipv6_pools[i];
		if (p == NULL) {
			log_debug("Unable to pick client prefix: "
				  "no IPv6 prefix pools "
				  "on this shared network");
			return ISC_R_NORESOURCES;
		}
		if (p->pool_type == D6O_IA_PD) {
			break;
		}
	}

	/*
	 * Otherwise try to get a prefix.
	 */
	for (i = 0;; i++) {
		p = shared_network->ipv6_pools[i];
		if (p == NULL) {
			break;
		}
		if (p->pool_type != D6O_IA_PD) {
			continue;
		}

		/*
		 * Try only pools with the requested prefix length if any.
		 */
		if ((plen >= 0) && (p->units != plen)) {
			continue;
		}

		if (create_prefix6(p, pref, &attempts, client_id,
				   cur_time + 120) == ISC_R_SUCCESS) {
			log_debug("Picking pool prefix %s/%u",
				  inet_ntop(AF_INET6, &((*pref)->addr),
				  	    tmp_buf, sizeof(tmp_buf)),
				  (unsigned) (*pref)->plen);
			return ISC_R_SUCCESS;
		}
	}

	/*
	 * If we failed to pick an IPv6 prefix
	 * Presumably that means we have no prefixes for the client.
	 */
	log_debug("Unable to pick client prefix: no prefixes available");
	return ISC_R_NORESOURCES;
}

/*
 * lease_to_client() is called from several messages to construct a
 * reply that contains all that we know about the client's correct lease
 * (or projected lease).
 *
 * Solicit - "Soft" binding, ignore unknown addresses or bindings, just
 *	     send what we "may" give them on a request.
 *
 * Request - "Hard" binding, but ignore supplied addresses (just provide what
 *	     the client should really use).
 *
 * Renew   - "Hard" binding, but client-supplied addresses are 'real'.  Error
 * Rebind    out any "wrong" addresses the client sends.  This means we send
 *	     an empty IA_NA with a status code of NoBinding or NotOnLink or
 *	     possibly send the address with zeroed lifetimes.
 *
 * Information-Request - No binding.
 *
 * The basic structure is to traverse the client-supplied data first, and
 * validate and echo back any contents that can be.  If the client-supplied
 * data does not error out (on renew/rebind as above), but we did not send
 * any addresses, attempt to allocate one.
 */
/* TODO: look at client hints for lease times */
static void
lease_to_client(struct data_string *reply_ret,
		struct packet *packet, 
		const struct data_string *client_id,
		const struct data_string *server_id)
{
	static struct reply_state reply;
	struct option_cache *oc;
	struct data_string packet_oro;
	isc_boolean_t no_resources_avail;

	/* Locate the client.  */
	if (shared_network_from_packet6(&reply.shared,
					packet) != ISC_R_SUCCESS)
		goto exit;

	/* 
	 * Initialize the reply.
	 */
	packet_reference(&reply.packet, packet, MDL);
	data_string_copy(&reply.client_id, client_id, MDL);

	if (!start_reply(packet, client_id, server_id, &reply.opt_state,
			 &reply.buf.reply))
		goto exit;

	/* Set the write cursor to just past the reply header. */
	reply.cursor = REPLY_OPTIONS_INDEX;

	/*
	 * Get the ORO from the packet, if any.
	 */
	oc = lookup_option(&dhcpv6_universe, packet->options, D6O_ORO);
	memset(&packet_oro, 0, sizeof(packet_oro));
	if (oc != NULL) {
		if (!evaluate_option_cache(&packet_oro, packet, 
					   NULL, NULL, 
					   packet->options, NULL,
					   &global_scope, oc, MDL)) {
			log_error("lease_to_client: error evaluating ORO.");
			goto exit;
		}
	}

	/* 
	 * Find a host record that matches from the packet, if any, and is
	 * valid for the shared network the client is on.
	 */
	if (find_hosts_by_uid(&reply.host, client_id->data, client_id->len,
			      MDL))
		seek_shared_host(&reply.host, reply.shared);

	if ((reply.host == NULL) &&
	    find_hosts_by_option(&reply.host, packet, packet->options, MDL))
		seek_shared_host(&reply.host, reply.shared);

	/*
	 * Check for 'hardware' matches last, as some of the synthesis methods
	 * are not considered to be as reliable.
	 */
	if ((reply.host == NULL) &&
	    find_hosts_by_duid_chaddr(&reply.host, client_id))
		seek_shared_host(&reply.host, reply.shared);

	/* Process the client supplied IA's onto the reply buffer. */
	reply.ia_count = 0;
	oc = lookup_option(&dhcpv6_universe, packet->options, D6O_IA_NA);
	no_resources_avail = ISC_FALSE;
	for (; oc != NULL ; oc = oc->next) {
		isc_result_t status;

		/* Start counting resources (addresses) offered. */
		reply.client_resources = 0;
		reply.resources_included = ISC_FALSE;

		status = reply_process_ia_na(&reply, oc);

		/*
		 * We continue to try other IA's whether we can address
		 * this one or not.  Any other result is an immediate fail.
		 */
		if ((status != ISC_R_SUCCESS) &&
		    (status != ISC_R_NORESOURCES))
			goto exit;

		/*
		 * If any address cannot be given to any IA, then set the
		 * NoAddrsAvail status code.
		 */
		if (reply.client_resources == 0)
			no_resources_avail = ISC_TRUE;
	}
	oc = lookup_option(&dhcpv6_universe, packet->options, D6O_IA_TA);
	for (; oc != NULL ; oc = oc->next) {
		isc_result_t status;

		/* Start counting resources (addresses) offered. */
		reply.client_resources = 0;
		reply.resources_included = ISC_FALSE;

		status = reply_process_ia_ta(&reply, oc);

		/*
		 * We continue to try other IA's whether we can address
		 * this one or not.  Any other result is an immediate fail.
		 */
		if ((status != ISC_R_SUCCESS) &&
		    (status != ISC_R_NORESOURCES))
			goto exit;

		/*
		 * If any address cannot be given to any IA, then set the
		 * NoAddrsAvail status code.
		 */
		if (reply.client_resources == 0)
			no_resources_avail = ISC_TRUE;
	}

	/* Same for IA_PD's. */
	reply.pd_count = 0;
	oc = lookup_option(&dhcpv6_universe, packet->options, D6O_IA_PD);
	for (; oc != NULL ; oc = oc->next) {
		isc_result_t status;

		/* Start counting resources (prefixes) offered. */
		reply.client_resources = 0;
		reply.resources_included = ISC_FALSE;

		status = reply_process_ia_pd(&reply, oc);

		/*
		 * We continue to try other IA_PD's whether we can address
		 * this one or not.  Any other result is an immediate fail.
		 */
		if ((status != ISC_R_SUCCESS) &&
		    (status != ISC_R_NORESOURCES))
			goto exit;

		/*
		 * If any prefix cannot be given to any IA_PD, then
		 * set the NoPrefixAvail status code.
		 */
		if (reply.client_resources == 0)
			no_resources_avail = ISC_TRUE;
	}

	/*
	 * Make no reply if we gave no resources and is not
	 * for Information-Request.
	 */
	if ((reply.ia_count == 0) && (reply.pd_count == 0)) {
		if (reply.packet->dhcpv6_msg_type !=
					    DHCPV6_INFORMATION_REQUEST)
			goto exit;

		/*
		 * Because we only execute statements on a per-IA basis,
		 * we need to execute statements in any non-IA reply to
		 * source configuration.
		 */
		execute_statements_in_scope(NULL, reply.packet, NULL, NULL,
					    reply.packet->options,
					    reply.opt_state, &global_scope,
					    reply.shared->group, root_group);

		/* Bring in any configuration from a host record. */
		if (reply.host != NULL)
			execute_statements_in_scope(NULL, reply.packet, NULL,
						    NULL, reply.packet->options,
						    reply.opt_state,
						    &global_scope,
						    reply.host->group,
						    reply.shared->group);
	}

	/*
	 * RFC3315 section 17.2.2 (Solicit):
	 *
	 * If the server will not assign any addresses to any IAs in a
	 * subsequent Request from the client, the server MUST send an
	 * Advertise message to the client that includes only a Status
	 * Code option with code NoAddrsAvail and a status message for
	 * the user, a Server Identifier option with the server's DUID,
	 * and a Client Identifier option with the client's DUID.
	 *
	 * Section 18.2.1 (Request):
	 *
	 * If the server cannot assign any addresses to an IA in the
	 * message from the client, the server MUST include the IA in
	 * the Reply message with no addresses in the IA and a Status
	 * Code option in the IA containing status code NoAddrsAvail.
	 *
	 * Section 18.1.8 (Client Behavior):
	 *
	 * Leave unchanged any information about addresses the client has
	 * recorded in the IA but that were not included in the IA from
	 * the server.
	 * Sends a Renew/Rebind if the IA is not in the Reply message.
	 */
	if (no_resources_avail && (reply.ia_count != 0) &&
	    (reply.packet->dhcpv6_msg_type == DHCPV6_SOLICIT))
	{
		/* Set the NoAddrsAvail status code. */
		if (!set_status_code(STATUS_NoAddrsAvail,
				     "No addresses available for this "
				     "interface.", reply.opt_state)) {
			log_error("lease_to_client: Unable to set "
				  "NoAddrsAvail status code.");
			goto exit;
		}

		/* Rewind the cursor to the start. */
		reply.cursor = REPLY_OPTIONS_INDEX;

		/*
		 * Produce an advertise that includes only:
		 *
		 * Status code.
		 * Server DUID.
		 * Client DUID.
		 */
		reply.buf.reply.msg_type = DHCPV6_ADVERTISE;
		reply.cursor += store_options6((char *)reply.buf.data +
							reply.cursor,
					       sizeof(reply.buf) -
					       		reply.cursor,
					       reply.opt_state, reply.packet,
					       required_opts_NAA,
					       NULL);
	} else if (no_resources_avail && (reply.ia_count == 0) &&
		   (reply.packet->dhcpv6_msg_type == DHCPV6_SOLICIT))
	{
		/* Set the NoPrefixAvail status code. */
		if (!set_status_code(STATUS_NoPrefixAvail,
				     "No prefixes available for this "
				     "interface.", reply.opt_state)) {
			log_error("lease_to_client: Unable to set "
				  "NoPrefixAvail status code.");
			goto exit;
		}

		/* Rewind the cursor to the start. */
		reply.cursor = REPLY_OPTIONS_INDEX;

		/*
		 * Produce an advertise that includes only:
		 *
		 * Status code.
		 * Server DUID.
		 * Client DUID.
		 */
		reply.buf.reply.msg_type = DHCPV6_ADVERTISE;
		reply.cursor += store_options6((char *)reply.buf.data +
							reply.cursor,
					       sizeof(reply.buf) -
					       		reply.cursor,
					       reply.opt_state, reply.packet,
					       required_opts_NAA,
					       NULL);
	} else {
		/*
		 * Having stored the client's IA's, store any options that
		 * will fit in the remaining space.
		 */
		reply.cursor += store_options6((char *)reply.buf.data +
							reply.cursor,
					       sizeof(reply.buf) -
							reply.cursor,
					       reply.opt_state, reply.packet,
					       required_opts_solicit,
					       &packet_oro);
	}

	/* Return our reply to the caller. */
	reply_ret->len = reply.cursor;
	reply_ret->buffer = NULL;
	if (!buffer_allocate(&reply_ret->buffer, reply.cursor, MDL)) {
		log_fatal("No memory to store Reply.");
	}
	memcpy(reply_ret->buffer->data, reply.buf.data, reply.cursor);
	reply_ret->data = reply_ret->buffer->data;

      exit:
	/* Cleanup. */
	if (reply.shared != NULL)
		shared_network_dereference(&reply.shared, MDL);
	if (reply.host != NULL)
		host_dereference(&reply.host, MDL);
	if (reply.opt_state != NULL)
		option_state_dereference(&reply.opt_state, MDL);
	if (reply.packet != NULL)
		packet_dereference(&reply.packet, MDL);
	if (reply.client_id.data != NULL)
		data_string_forget(&reply.client_id, MDL);
	reply.renew = reply.rebind = reply.prefer = reply.valid = 0;
	reply.cursor = 0;
}

/* Process a client-supplied IA_NA.  This may append options to the tail of
 * the reply packet being built in the reply_state structure.
 */
static isc_result_t
reply_process_ia_na(struct reply_state *reply, struct option_cache *ia) {
	isc_result_t status = ISC_R_SUCCESS;
	u_int32_t iaid;
	unsigned ia_cursor;
	struct option_state *packet_ia;
	struct option_cache *oc;
	struct data_string ia_data, data;

	/* Initialize values that will get cleaned up on return. */
	packet_ia = NULL;
	memset(&ia_data, 0, sizeof(ia_data));
	memset(&data, 0, sizeof(data));
	/* 
	 * Note that find_client_address() may set reply->lease. 
	 */

	/* Make sure there is at least room for the header. */
	if ((reply->cursor + IA_NA_OFFSET + 4) > sizeof(reply->buf)) {
		log_error("reply_process_ia_na: Reply too long for IA.");
		return ISC_R_NOSPACE;
	}


	/* Fetch the IA_NA contents. */
	if (!get_encapsulated_IA_state(&packet_ia, &ia_data, reply->packet,
				       ia, IA_NA_OFFSET)) {
		log_error("reply_process_ia_na: error evaluating ia");
		status = ISC_R_FAILURE;
		goto cleanup;
	}

	/* Extract IA_NA header contents. */
	iaid = getULong(ia_data.data);
	reply->renew = getULong(ia_data.data + 4);
	reply->rebind = getULong(ia_data.data + 8);

	/* Create an IA_NA structure. */
	if (ia_allocate(&reply->ia, iaid, (char *)reply->client_id.data, 
			reply->client_id.len, MDL) != ISC_R_SUCCESS) {
		log_error("reply_process_ia_na: no memory for ia.");
		status = ISC_R_NOMEMORY;
		goto cleanup;
	}
	reply->ia->ia_type = D6O_IA_NA;

	/* Cache pre-existing IA, if any. */
	ia_hash_lookup(&reply->old_ia, ia_na_active,
		       (unsigned char *)reply->ia->iaid_duid.data,
		       reply->ia->iaid_duid.len, MDL);

	/*
	 * Create an option cache to carry the IA_NA option contents, and
	 * execute any user-supplied values into it.
	 */
	if (!option_state_allocate(&reply->reply_ia, MDL)) {
		status = ISC_R_NOMEMORY;
		goto cleanup;
	}

	/* Check & cache the fixed host record. */
	if ((reply->host != NULL) && (reply->host->fixed_addr != NULL)) {
		struct iaddr tmp_addr;

		if (!evaluate_option_cache(&reply->fixed, NULL, NULL, NULL,
					   NULL, NULL, &global_scope,
					   reply->host->fixed_addr, MDL)) {
			log_error("reply_process_ia_na: unable to evaluate "
				  "fixed address.");
			status = ISC_R_FAILURE;
			goto cleanup;
		}

		if (reply->fixed.len < 16) {
			log_error("reply_process_ia_na: invalid fixed address.");
			status = DHCP_R_INVALIDARG;
			goto cleanup;
		}

		/* Find the static lease's subnet. */
		tmp_addr.len = 16;
		memcpy(tmp_addr.iabuf, reply->fixed.data, 16);

		if (find_grouped_subnet(&reply->subnet, reply->shared,
					tmp_addr, MDL) == 0)
			log_fatal("Impossible condition at %s:%d.", MDL);

		reply->static_lease = ISC_TRUE;
	} else
		reply->static_lease = ISC_FALSE;

	/*
	 * Save the cursor position at the start of the IA, so we can
	 * set length and adjust t1/t2 values later.  We write a temporary
	 * header out now just in case we decide to adjust the packet
	 * within sub-process functions.
	 */
	ia_cursor = reply->cursor;

	/* Initialize the IA_NA header.  First the code. */
	putUShort(reply->buf.data + reply->cursor, (unsigned)D6O_IA_NA);
	reply->cursor += 2;

	/* Then option length. */
	putUShort(reply->buf.data + reply->cursor, 0x0Cu);
	reply->cursor += 2;

	/* Then IA_NA header contents; IAID. */
	putULong(reply->buf.data + reply->cursor, iaid);
	reply->cursor += 4;

	/* We store the client's t1 for now, and may over-ride it later. */
	putULong(reply->buf.data + reply->cursor, reply->renew);
	reply->cursor += 4;

	/* We store the client's t2 for now, and may over-ride it later. */
	putULong(reply->buf.data + reply->cursor, reply->rebind);
	reply->cursor += 4;

	/* 
	 * For each address in this IA_NA, decide what to do about it.
	 *
	 * Guidelines:
	 *
	 * The client leaves unchanged any infomation about addresses
	 * it has recorded but are not included ("cancel/break" below).
	 * A not included IA ("cleanup" below) could give a Renew/Rebind.
	 */
	oc = lookup_option(&dhcpv6_universe, packet_ia, D6O_IAADDR);
	reply->valid = reply->prefer = 0xffffffff;
	reply->client_valid = reply->client_prefer = 0;
	for (; oc != NULL ; oc = oc->next) {
		status = reply_process_addr(reply, oc);

		/*
		 * Canceled means we did not allocate addresses to the
		 * client, but we're "done" with this IA - we set a status
		 * code.  So transmit this reply, e.g., move on to the next
		 * IA.
		 */
		if (status == ISC_R_CANCELED)
			break;

		if ((status != ISC_R_SUCCESS) &&
		    (status != ISC_R_ADDRINUSE) &&
		    (status != ISC_R_ADDRNOTAVAIL))
			goto cleanup;
	}

	reply->ia_count++;

	/*
	 * If we fell through the above and never gave the client
	 * an address, give it one now.
	 */
	if ((status != ISC_R_CANCELED) && (reply->client_resources == 0)) {
		status = find_client_address(reply);

		if (status == ISC_R_NORESOURCES) {
			switch (reply->packet->dhcpv6_msg_type) {
			      case DHCPV6_SOLICIT:
				/*
				 * No address for any IA is handled
				 * by the caller.
				 */
				/* FALL THROUGH */

			      case DHCPV6_REQUEST:
				/* Section 18.2.1 (Request):
				 *
				 * If the server cannot assign any addresses to
				 * an IA in the message from the client, the
				 * server MUST include the IA in the Reply
				 * message with no addresses in the IA and a
				 * Status Code option in the IA containing
				 * status code NoAddrsAvail.
				 */
				option_state_dereference(&reply->reply_ia, MDL);
				if (!option_state_allocate(&reply->reply_ia,
							   MDL))
				{
					log_error("reply_process_ia_na: No "
						  "memory for option state "
						  "wipe.");
					status = ISC_R_NOMEMORY;
					goto cleanup;
				}

				if (!set_status_code(STATUS_NoAddrsAvail,
						     "No addresses available "
						     "for this interface.",
						      reply->reply_ia)) {
					log_error("reply_process_ia_na: Unable "
						  "to set NoAddrsAvail status "
						  "code.");
					status = ISC_R_FAILURE;
					goto cleanup;
				}

				status = ISC_R_SUCCESS;
				break;

			      default:
				/*
				 * RFC 3315 does not tell us to emit a status
				 * code in this condition, or anything else.
				 *
				 * If we included non-allocated addresses
				 * (zeroed lifetimes) in an IA, then the client
				 * will deconfigure them.
				 *
				 * So we want to include the IA even if we
				 * can't give it a new address if it includes
				 * zeroed lifetime addresses.
				 *
				 * We don't want to include the IA if we
				 * provide zero addresses including zeroed
				 * lifetimes.
				 */
				if (reply->resources_included)
					status = ISC_R_SUCCESS;
				else
					goto cleanup;
				break;
			}
		}

		if (status != ISC_R_SUCCESS)
			goto cleanup;
	}

	reply->cursor += store_options6((char *)reply->buf.data + reply->cursor,
					sizeof(reply->buf) - reply->cursor,
					reply->reply_ia, reply->packet,
					required_opts_IA, NULL);

	/* Reset the length of this IA to match what was just written. */
	putUShort(reply->buf.data + ia_cursor + 2,
		  reply->cursor - (ia_cursor + 4));

	/*
	 * T1/T2 time selection is kind of weird.  We actually use DHCP
	 * (v4) scoped options as handy existing places where these might
	 * be configured by an administrator.  A value of zero tells the
	 * client it may choose its own renewal time.
	 */
	reply->renew = 0;
	oc = lookup_option(&dhcp_universe, reply->opt_state,
			   DHO_DHCP_RENEWAL_TIME);
	if (oc != NULL) {
		if (!evaluate_option_cache(&data, reply->packet, NULL, NULL,
					   reply->packet->options,
					   reply->opt_state, &global_scope,
					   oc, MDL) ||
		    (data.len != 4)) {
			log_error("Invalid renewal time.");
		} else {
			reply->renew = getULong(data.data);
		}

		if (data.data != NULL)
			data_string_forget(&data, MDL);
	}
	putULong(reply->buf.data + ia_cursor + 8, reply->renew);

	/* Now T2. */
	reply->rebind = 0;
	oc = lookup_option(&dhcp_universe, reply->opt_state,
			   DHO_DHCP_REBINDING_TIME);
	if (oc != NULL) {
		if (!evaluate_option_cache(&data, reply->packet, NULL, NULL,
					   reply->packet->options,
					   reply->opt_state, &global_scope,
					   oc, MDL) ||
		    (data.len != 4)) {
			log_error("Invalid rebinding time.");
		} else {
			reply->rebind = getULong(data.data);
		}

		if (data.data != NULL)
			data_string_forget(&data, MDL);
	}
	putULong(reply->buf.data + ia_cursor + 12, reply->rebind);

	/*
	 * If this is not a 'soft' binding, consume the new changes into
	 * the database (if any have been attached to the ia_na).
	 *
	 * Loop through the assigned dynamic addresses, referencing the
	 * leases onto this IA_NA rather than any old ones, and updating
	 * pool timers for each (if any).
	 */
	if ((status != ISC_R_CANCELED) && !reply->static_lease &&
	    (reply->buf.reply.msg_type == DHCPV6_REPLY) &&
	    (reply->ia->num_iasubopt != 0)) {
		struct iasubopt *tmp;
		struct data_string *ia_id;
		int i;

		for (i = 0 ; i < reply->ia->num_iasubopt ; i++) {
			tmp = reply->ia->iasubopt[i];

			if (tmp->ia != NULL)
				ia_dereference(&tmp->ia, MDL);
			ia_reference(&tmp->ia, reply->ia, MDL);

			/* Commit 'hard' bindings. */
			tmp->hard_lifetime_end_time =
				tmp->soft_lifetime_end_time;
			tmp->soft_lifetime_end_time = 0;
			renew_lease6(tmp->ipv6_pool, tmp);
			schedule_lease_timeout(tmp->ipv6_pool);

#if defined (NSUPDATE)
			/*
			 * Perform ddns updates.
			 */
			oc = lookup_option(&server_universe, reply->opt_state,
					   SV_DDNS_UPDATES);
			if ((oc == NULL) ||
			    evaluate_boolean_option_cache(NULL, reply->packet,
							  NULL, NULL,
							reply->packet->options,
							  reply->opt_state,
							  &tmp->scope,
							  oc, MDL)) {
				ddns_updates(reply->packet, NULL, NULL,
					     tmp, NULL, reply->opt_state);
			}
#endif
		}

		/* Remove any old ia from the hash. */
		if (reply->old_ia != NULL) {
			ia_id = &reply->old_ia->iaid_duid;
			ia_hash_delete(ia_na_active,
				       (unsigned char *)ia_id->data,
				       ia_id->len, MDL);
			ia_dereference(&reply->old_ia, MDL);
		}

		/* Put new ia into the hash. */
		reply->ia->cltt = cur_time;
		ia_id = &reply->ia->iaid_duid;
		ia_hash_add(ia_na_active, (unsigned char *)ia_id->data,
			    ia_id->len, reply->ia, MDL);

		write_ia(reply->ia);
	}

      cleanup:
	if (packet_ia != NULL)
		option_state_dereference(&packet_ia, MDL);
	if (reply->reply_ia != NULL)
		option_state_dereference(&reply->reply_ia, MDL);
	if (ia_data.data != NULL)
		data_string_forget(&ia_data, MDL);
	if (data.data != NULL)
		data_string_forget(&data, MDL);
	if (reply->ia != NULL)
		ia_dereference(&reply->ia, MDL);
	if (reply->old_ia != NULL)
		ia_dereference(&reply->old_ia, MDL);
	if (reply->lease != NULL)
		iasubopt_dereference(&reply->lease, MDL);
	if (reply->fixed.data != NULL)
		data_string_forget(&reply->fixed, MDL);
	if (reply->subnet != NULL)
		subnet_dereference(&reply->subnet, MDL);

	/*
	 * ISC_R_CANCELED is a status code used by the addr processing to
	 * indicate we're replying with a status code.  This is still a
	 * success at higher layers.
	 */
	return((status == ISC_R_CANCELED) ? ISC_R_SUCCESS : status);
}

/*
 * Process an IAADDR within a given IA_xA, storing any IAADDR reply contents
 * into the reply's current ia-scoped option cache.  Returns ISC_R_CANCELED
 * in the event we are replying with a status code and do not wish to process
 * more IAADDRs within this IA.
 */
static isc_result_t
reply_process_addr(struct reply_state *reply, struct option_cache *addr) {
	u_int32_t pref_life, valid_life;
	struct binding_scope **scope;
	struct group *group;
	struct subnet *subnet;
	struct iaddr tmp_addr;
	struct option_cache *oc;
	struct data_string iaaddr, data;
	isc_result_t status = ISC_R_SUCCESS;

	/* Initializes values that will be cleaned up. */
	memset(&iaaddr, 0, sizeof(iaaddr));
	memset(&data, 0, sizeof(data));
	/* Note that reply->lease may be set by address_is_owned() */

	/*
	 * There is no point trying to process an incoming address if there
	 * is no room for an outgoing address.
	 */
	if ((reply->cursor + 28) > sizeof(reply->buf)) {
		log_error("reply_process_addr: Out of room for address.");
		return ISC_R_NOSPACE;
	}

	/* Extract this IAADDR option. */
	if (!evaluate_option_cache(&iaaddr, reply->packet, NULL, NULL, 
				   reply->packet->options, NULL, &global_scope,
				   addr, MDL) ||
	    (iaaddr.len < IAADDR_OFFSET)) {
		log_error("reply_process_addr: error evaluating IAADDR.");
		status = ISC_R_FAILURE;
		goto cleanup;
	}

	/* The first 16 bytes are the IPv6 address. */
	pref_life = getULong(iaaddr.data + 16);
	valid_life = getULong(iaaddr.data + 20);

	if ((reply->client_valid == 0) ||
	    (reply->client_valid > valid_life))
		reply->client_valid = valid_life;

	if ((reply->client_prefer == 0) ||
	    (reply->client_prefer > pref_life))
		reply->client_prefer = pref_life;

	/* 
	 * Clients may choose to send :: as an address, with the idea to give
	 * hints about preferred-lifetime or valid-lifetime.
	 */
	tmp_addr.len = 16;
	memset(tmp_addr.iabuf, 0, 16);
	if (!memcmp(iaaddr.data, tmp_addr.iabuf, 16)) {
		/* Status remains success; we just ignore this one. */
		goto cleanup;
	}

	/* tmp_addr len remains 16 */
	memcpy(tmp_addr.iabuf, iaaddr.data, 16);

	/*
	 * Verify that this address is on the client's network.
	 */
	for (subnet = reply->shared->subnets ; subnet != NULL ;
	     subnet = subnet->next_sibling) {
		if (addr_eq(subnet_number(tmp_addr, subnet->netmask),
			    subnet->net))
			break;
	}

	/* Address not found on shared network. */
	if (subnet == NULL) {
		/* Ignore this address on 'soft' bindings. */
		if (reply->packet->dhcpv6_msg_type == DHCPV6_SOLICIT) {
			/* disable rapid commit */
			reply->buf.reply.msg_type = DHCPV6_ADVERTISE;
			delete_option(&dhcpv6_universe,
				      reply->opt_state,
				      D6O_RAPID_COMMIT);
			/* status remains success */
			goto cleanup;
		}

		/*
		 * RFC3315 section 18.2.1:
		 *
		 * If the server finds that the prefix on one or more IP
		 * addresses in any IA in the message from the client is not
		 * appropriate for the link to which the client is connected,
		 * the server MUST return the IA to the client with a Status
		 * Code option with the value NotOnLink.
		 */
		if (reply->packet->dhcpv6_msg_type == DHCPV6_REQUEST) {
			/* Rewind the IA_NA to empty. */
			option_state_dereference(&reply->reply_ia, MDL);
			if (!option_state_allocate(&reply->reply_ia, MDL)) {
				log_error("reply_process_addr: No memory for "
					  "option state wipe.");
				status = ISC_R_NOMEMORY;
				goto cleanup;
			}

			/* Append a NotOnLink status code. */
			if (!set_status_code(STATUS_NotOnLink,
					     "Address not for use on this "
					     "link.", reply->reply_ia)) {
				log_error("reply_process_addr: Failure "
					  "setting status code.");
				status = ISC_R_FAILURE;
				goto cleanup;
			}

			/* Fin (no more IAADDRs). */
			status = ISC_R_CANCELED;
			goto cleanup;
		}

		/*
		 * RFC3315 sections 18.2.3 and 18.2.4 have identical language:
		 *
		 * If the server finds that any of the addresses are not
		 * appropriate for the link to which the client is attached,
		 * the server returns the address to the client with lifetimes
		 * of 0.
		 */
		if ((reply->packet->dhcpv6_msg_type != DHCPV6_RENEW) &&
		    (reply->packet->dhcpv6_msg_type != DHCPV6_REBIND)) {
			log_error("It is impossible to lease a client that is "
				  "not sending a solicit, request, renew, or "
				  "rebind.");
			status = ISC_R_FAILURE;
			goto cleanup;
		}

		reply->send_prefer = reply->send_valid = 0;
		goto send_addr;
	}

	/* Verify the address belongs to the client. */
	if (!address_is_owned(reply, &tmp_addr)) {
		/*
		 * For solicit and request, any addresses included are
		 * 'requested' addresses.  For rebind, we actually have
		 * no direction on what to do from 3315 section 18.2.4!
		 * So I think the best bet is to try and give it out, and if
		 * we can't, zero lifetimes.
		 */
		if ((reply->packet->dhcpv6_msg_type == DHCPV6_SOLICIT) ||
		    (reply->packet->dhcpv6_msg_type == DHCPV6_REQUEST) ||
		    (reply->packet->dhcpv6_msg_type == DHCPV6_REBIND)) {
			status = reply_process_try_addr(reply, &tmp_addr);

			/*
			 * If the address is in use, or isn't in any dynamic
			 * range, continue as normal.  If any other error was
			 * found, error out.
			 */
			if ((status != ISC_R_SUCCESS) && 
			    (status != ISC_R_ADDRINUSE) &&
			    (status != ISC_R_ADDRNOTAVAIL))
				goto cleanup;

			/*
			 * If we didn't honor this lease, for solicit and
			 * request we simply omit it from our answer.  For
			 * rebind, we send it with zeroed lifetimes.
			 */
			if (reply->lease == NULL) {
				if (reply->packet->dhcpv6_msg_type ==
							DHCPV6_REBIND) {
					reply->send_prefer = 0;
					reply->send_valid = 0;
					goto send_addr;
				}

				/* status remains success - ignore */
				goto cleanup;
			}
		/*
		 * RFC3315 section 18.2.3:
		 *
		 * If the server cannot find a client entry for the IA the
		 * server returns the IA containing no addresses with a Status
		 * Code option set to NoBinding in the Reply message.
		 *
		 * On mismatch we (ab)use this pretending we have not the IA
		 * as soon as we have not an address.
		 */
		} else if (reply->packet->dhcpv6_msg_type == DHCPV6_RENEW) {
			/* Rewind the IA_NA to empty. */
			option_state_dereference(&reply->reply_ia, MDL);
			if (!option_state_allocate(&reply->reply_ia, MDL)) {
				log_error("reply_process_addr: No memory for "
					  "option state wipe.");
				status = ISC_R_NOMEMORY;
				goto cleanup;
			}

			/* Append a NoBinding status code.  */
			if (!set_status_code(STATUS_NoBinding,
					     "Address not bound to this "
					     "interface.", reply->reply_ia)) {
				log_error("reply_process_addr: Unable to "
					  "attach status code.");
				status = ISC_R_FAILURE;
				goto cleanup;
			}

			/* Fin (no more IAADDRs). */
			status = ISC_R_CANCELED;
			goto cleanup;
		} else {
			log_error("It is impossible to lease a client that is "
				  "not sending a solicit, request, renew, or "
				  "rebind message.");
			status = ISC_R_FAILURE;
			goto cleanup;
		}
	}

	if (reply->static_lease) {
		if (reply->host == NULL)
			log_fatal("Impossible condition at %s:%d.", MDL);

		scope = &global_scope;
		group = reply->subnet->group;
	} else {
		if (reply->lease == NULL)
			log_fatal("Impossible condition at %s:%d.", MDL);

		scope = &reply->lease->scope;
		group = reply->lease->ipv6_pool->subnet->group;
	}

	/*
	 * If client_resources is nonzero, then the reply_process_is_addressed
	 * function has executed configuration state into the reply option
	 * cache.  We will use that valid cache to derive configuration for
	 * whether or not to engage in additional addresses, and similar.
	 */
	if (reply->client_resources != 0) {
		unsigned limit = 1;

		/*
		 * Does this client have "enough" addresses already?  Default
		 * to one.  Everybody gets one, and one should be enough for
		 * anybody.
		 */
		oc = lookup_option(&server_universe, reply->opt_state,
				   SV_LIMIT_ADDRS_PER_IA);
		if (oc != NULL) {
			if (!evaluate_option_cache(&data, reply->packet,
						   NULL, NULL,
						   reply->packet->options,
						   reply->opt_state,
						   scope, oc, MDL) ||
			    (data.len != 4)) {
				log_error("reply_process_addr: unable to "
					  "evaluate addrs-per-ia value.");
				status = ISC_R_FAILURE;
				goto cleanup;
			}

			limit = getULong(data.data);
			data_string_forget(&data, MDL);
		}

		/*
		 * If we wish to limit the client to a certain number of
		 * addresses, then omit the address from the reply.
		 */
		if (reply->client_resources >= limit)
			goto cleanup;
	}

	status = reply_process_is_addressed(reply, scope, group);
	if (status != ISC_R_SUCCESS)
		goto cleanup;

      send_addr:
	status = reply_process_send_addr(reply, &tmp_addr);

      cleanup:
	if (iaaddr.data != NULL)
		data_string_forget(&iaaddr, MDL);
	if (data.data != NULL)
		data_string_forget(&data, MDL);
	if (reply->lease != NULL)
		iasubopt_dereference(&reply->lease, MDL);

	return status;
}

/*
 * Verify the address belongs to the client.  If we've got a host
 * record with a fixed address, it has to be the assigned address
 * (fault out all else).  Otherwise it's a dynamic address, so lookup
 * that address and make sure it belongs to this DUID:IAID pair.
 */
static isc_boolean_t
address_is_owned(struct reply_state *reply, struct iaddr *addr) {
	int i;

	/*
	 * This faults out addresses that don't match fixed addresses.
	 */
	if (reply->static_lease) {
		if (reply->fixed.data == NULL)
			log_fatal("Impossible condition at %s:%d.", MDL);

		if (memcmp(addr->iabuf, reply->fixed.data, 16) == 0)
			return ISC_TRUE;

		return ISC_FALSE;
	}

	if ((reply->old_ia == NULL) || (reply->old_ia->num_iasubopt == 0))
		return ISC_FALSE;

	for (i = 0 ; i < reply->old_ia->num_iasubopt ; i++) {
		struct iasubopt *tmp;

		tmp = reply->old_ia->iasubopt[i];

		if (memcmp(addr->iabuf, &tmp->addr, 16) == 0) {
			iasubopt_reference(&reply->lease, tmp, MDL);
			return ISC_TRUE;
		}
	}

	return ISC_FALSE;
}

/* Process a client-supplied IA_TA.  This may append options to the tail of
 * the reply packet being built in the reply_state structure.
 */
static isc_result_t
reply_process_ia_ta(struct reply_state *reply, struct option_cache *ia) {
	isc_result_t status = ISC_R_SUCCESS;
	u_int32_t iaid;
	unsigned ia_cursor;
	struct option_state *packet_ia;
	struct option_cache *oc;
	struct data_string ia_data, data;
	struct data_string iaaddr;
	u_int32_t pref_life, valid_life;
	struct iaddr tmp_addr;

	/* Initialize values that will get cleaned up on return. */
	packet_ia = NULL;
	memset(&ia_data, 0, sizeof(ia_data));
	memset(&data, 0, sizeof(data));
	memset(&iaaddr, 0, sizeof(iaaddr));

	/* Make sure there is at least room for the header. */
	if ((reply->cursor + IA_TA_OFFSET + 4) > sizeof(reply->buf)) {
		log_error("reply_process_ia_ta: Reply too long for IA.");
		return ISC_R_NOSPACE;
	}


	/* Fetch the IA_TA contents. */
	if (!get_encapsulated_IA_state(&packet_ia, &ia_data, reply->packet,
				       ia, IA_TA_OFFSET)) {
		log_error("reply_process_ia_ta: error evaluating ia");
		status = ISC_R_FAILURE;
		goto cleanup;
	}

	/* Extract IA_TA header contents. */
	iaid = getULong(ia_data.data);

	/* Create an IA_TA structure. */
	if (ia_allocate(&reply->ia, iaid, (char *)reply->client_id.data,
			reply->client_id.len, MDL) != ISC_R_SUCCESS) {
		log_error("reply_process_ia_ta: no memory for ia.");
		status = ISC_R_NOMEMORY;
		goto cleanup;
	}
	reply->ia->ia_type = D6O_IA_TA;

	/* Cache pre-existing IA, if any. */
	ia_hash_lookup(&reply->old_ia, ia_ta_active,
		       (unsigned char *)reply->ia->iaid_duid.data,
		       reply->ia->iaid_duid.len, MDL);

	/*
	 * Create an option cache to carry the IA_TA option contents, and
	 * execute any user-supplied values into it.
	 */
	if (!option_state_allocate(&reply->reply_ia, MDL)) {
		status = ISC_R_NOMEMORY;
		goto cleanup;
	}

	/*
	 * Temporary leases are dynamic by definition.
	 */
	reply->static_lease = ISC_FALSE;

	/*
	 * Save the cursor position at the start of the IA, so we can
	 * set length later.  We write a temporary
	 * header out now just in case we decide to adjust the packet
	 * within sub-process functions.
	 */
	ia_cursor = reply->cursor;

	/* Initialize the IA_TA header.  First the code. */
	putUShort(reply->buf.data + reply->cursor, (unsigned)D6O_IA_TA);
	reply->cursor += 2;

	/* Then option length. */
	putUShort(reply->buf.data + reply->cursor, 0x04u);
	reply->cursor += 2;

	/* Then IA_TA header contents; IAID. */
	putULong(reply->buf.data + reply->cursor, iaid);
	reply->cursor += 4;

	/* 
	 * Deal with an IAADDR for lifetimes.
	 * For all or none, process IAADDRs as hints.
	 */
	reply->valid = reply->prefer = 0xffffffff;
	reply->client_valid = reply->client_prefer = 0;
	oc = lookup_option(&dhcpv6_universe, packet_ia, D6O_IAADDR);
	for (; oc != NULL; oc = oc->next) {
		memset(&iaaddr, 0, sizeof(iaaddr));
		if (!evaluate_option_cache(&iaaddr, reply->packet,
					   NULL, NULL,
					   reply->packet->options, NULL,
					   &global_scope, oc, MDL) ||
		    (iaaddr.len < IAADDR_OFFSET)) {
			log_error("reply_process_ia_ta: error "
				  "evaluating IAADDR.");
			status = ISC_R_FAILURE;
			goto cleanup;
		}
		/* The first 16 bytes are the IPv6 address. */
		pref_life = getULong(iaaddr.data + 16);
		valid_life = getULong(iaaddr.data + 20);

		if ((reply->client_valid == 0) ||
		    (reply->client_valid > valid_life))
			reply->client_valid = valid_life;

		if ((reply->client_prefer == 0) ||
		    (reply->client_prefer > pref_life))
			reply->client_prefer = pref_life;

		/* Nothing more if something has failed. */
		if (status == ISC_R_CANCELED)
			continue;

		tmp_addr.len = 16;
		memcpy(tmp_addr.iabuf, iaaddr.data, 16);
		if (!temporary_is_available(reply, &tmp_addr))
			goto bad_temp;
		status = reply_process_is_addressed(reply,
						    &reply->lease->scope,
						    reply->shared->group);
		if (status != ISC_R_SUCCESS)
			goto bad_temp;
		status = reply_process_send_addr(reply, &tmp_addr);
		if (status != ISC_R_SUCCESS)
			goto bad_temp;
		if (reply->lease != NULL)
			iasubopt_dereference(&reply->lease, MDL);
		continue;

	bad_temp:
		/* Rewind the IA_TA to empty. */
		option_state_dereference(&reply->reply_ia, MDL);
		if (!option_state_allocate(&reply->reply_ia, MDL)) {
			status = ISC_R_NOMEMORY;
			goto cleanup;
		}
		status = ISC_R_CANCELED;
		reply->client_resources = 0;
		reply->resources_included = ISC_FALSE;
		if (reply->lease != NULL)
			iasubopt_dereference(&reply->lease, MDL);
	}
	reply->ia_count++;

	/*
	 * Give the client temporary addresses.
	 */
	if (reply->client_resources != 0)
		goto store;
	status = find_client_temporaries(reply);
	if (status == ISC_R_NORESOURCES) {
		switch (reply->packet->dhcpv6_msg_type) {
		      case DHCPV6_SOLICIT:
			/*
			 * No address for any IA is handled
			 * by the caller.
			 */
			/* FALL THROUGH */

		      case DHCPV6_REQUEST:
			/* Section 18.2.1 (Request):
			 *
			 * If the server cannot assign any addresses to
			 * an IA in the message from the client, the
			 * server MUST include the IA in the Reply
			 * message with no addresses in the IA and a
			 * Status Code option in the IA containing
			 * status code NoAddrsAvail.
			 */
			option_state_dereference(&reply->reply_ia, MDL);
			if (!option_state_allocate(&reply->reply_ia,  MDL)) {
				log_error("reply_process_ia_ta: No "
					  "memory for option state wipe.");
				status = ISC_R_NOMEMORY;
				goto cleanup;
			}

			if (!set_status_code(STATUS_NoAddrsAvail,
					     "No addresses available "
					     "for this interface.",
					      reply->reply_ia)) {
				log_error("reply_process_ia_ta: Unable "
					  "to set NoAddrsAvail status code.");
				status = ISC_R_FAILURE;
				goto cleanup;
			}

			status = ISC_R_SUCCESS;
			break;

		      default:
			/*
			 * We don't want to include the IA if we
			 * provide zero addresses including zeroed
			 * lifetimes.
			 */
			if (reply->resources_included)
				status = ISC_R_SUCCESS;
			else
				goto cleanup;
			break;
		}
	} else if (status != ISC_R_SUCCESS)
		goto cleanup;

      store:
	reply->cursor += store_options6((char *)reply->buf.data + reply->cursor,
					sizeof(reply->buf) - reply->cursor,
					reply->reply_ia, reply->packet,
					required_opts_IA, NULL);

	/* Reset the length of this IA to match what was just written. */
	putUShort(reply->buf.data + ia_cursor + 2,
		  reply->cursor - (ia_cursor + 4));

	/*
	 * Consume the new changes into the database (if any have been
	 * attached to the ia_ta).
	 *
	 * Loop through the assigned dynamic addresses, referencing the
	 * leases onto this IA_TA rather than any old ones, and updating
	 * pool timers for each (if any).
	 */
	if ((status != ISC_R_CANCELED) &&
	    (reply->buf.reply.msg_type == DHCPV6_REPLY) &&
	    (reply->ia->num_iasubopt != 0)) {
		struct iasubopt *tmp;
		struct data_string *ia_id;
		int i;

		for (i = 0 ; i < reply->ia->num_iasubopt ; i++) {
			tmp = reply->ia->iasubopt[i];

			if (tmp->ia != NULL)
				ia_dereference(&tmp->ia, MDL);
			ia_reference(&tmp->ia, reply->ia, MDL);

			/* Commit 'hard' bindings. */
			tmp->hard_lifetime_end_time =
				tmp->soft_lifetime_end_time;
			tmp->soft_lifetime_end_time = 0;
			renew_lease6(tmp->ipv6_pool, tmp);
			schedule_lease_timeout(tmp->ipv6_pool);

#if defined (NSUPDATE)
			/*
			 * Perform ddns updates.
			 */
			oc = lookup_option(&server_universe, reply->opt_state,
					   SV_DDNS_UPDATES);
			if ((oc == NULL) ||
			    evaluate_boolean_option_cache(NULL, reply->packet,
							  NULL, NULL,
							reply->packet->options,
							  reply->opt_state,
							  &tmp->scope,
							  oc, MDL)) {
				ddns_updates(reply->packet, NULL, NULL,
					     tmp, NULL, reply->opt_state);
			}
#endif
		}

		/* Remove any old ia from the hash. */
		if (reply->old_ia != NULL) {
			ia_id = &reply->old_ia->iaid_duid;
			ia_hash_delete(ia_ta_active,
				       (unsigned char *)ia_id->data,
				       ia_id->len, MDL);
			ia_dereference(&reply->old_ia, MDL);
		}

		/* Put new ia into the hash. */
		reply->ia->cltt = cur_time;
		ia_id = &reply->ia->iaid_duid;
		ia_hash_add(ia_ta_active, (unsigned char *)ia_id->data,
			    ia_id->len, reply->ia, MDL);

		write_ia(reply->ia);
	}

      cleanup:
	if (packet_ia != NULL)
		option_state_dereference(&packet_ia, MDL);
	if (iaaddr.data != NULL)
		data_string_forget(&iaaddr, MDL);
	if (reply->reply_ia != NULL)
		option_state_dereference(&reply->reply_ia, MDL);
	if (ia_data.data != NULL)
		data_string_forget(&ia_data, MDL);
	if (data.data != NULL)
		data_string_forget(&data, MDL);
	if (reply->ia != NULL)
		ia_dereference(&reply->ia, MDL);
	if (reply->old_ia != NULL)
		ia_dereference(&reply->old_ia, MDL);
	if (reply->lease != NULL)
		iasubopt_dereference(&reply->lease, MDL);

	/*
	 * ISC_R_CANCELED is a status code used by the addr processing to
	 * indicate we're replying with other addresses.  This is still a
	 * success at higher layers.
	 */
	return((status == ISC_R_CANCELED) ? ISC_R_SUCCESS : status);
}

/*
 * Verify the temporary address is available.
 */
static isc_boolean_t
temporary_is_available(struct reply_state *reply, struct iaddr *addr) {
	struct in6_addr tmp_addr;
	struct subnet *subnet;
	struct ipv6_pool *pool;
	int i;

	memcpy(&tmp_addr, addr->iabuf, sizeof(tmp_addr));
	/*
	 * Clients may choose to send :: as an address, with the idea to give
	 * hints about preferred-lifetime or valid-lifetime.
	 * So this is not a request for this address.
	 */
	if (IN6_IS_ADDR_UNSPECIFIED(&tmp_addr))
		return ISC_FALSE;

	/*
	 * Verify that this address is on the client's network.
	 */
	for (subnet = reply->shared->subnets ; subnet != NULL ;
	     subnet = subnet->next_sibling) {
		if (addr_eq(subnet_number(*addr, subnet->netmask),
			    subnet->net))
			break;
	}

	/* Address not found on shared network. */
	if (subnet == NULL)
		return ISC_FALSE;

	/*
	 * Check if this address is owned (must be before next step).
	 */
	if (address_is_owned(reply, addr))
		return ISC_TRUE;

	/*
	 * Verify that this address is in a temporary pool and try to get it.
	 */
	if (reply->shared->ipv6_pools == NULL)
		return ISC_FALSE;
	for (i = 0 ; (pool = reply->shared->ipv6_pools[i]) != NULL ; i++) {
		if (pool->pool_type != D6O_IA_TA)
			continue;
		if (ipv6_in_pool(&tmp_addr, pool))
			break;
	}
	if (pool == NULL)
		return ISC_FALSE;
	if (lease6_exists(pool, &tmp_addr))
		return ISC_FALSE;
	if (iasubopt_allocate(&reply->lease, MDL) != ISC_R_SUCCESS)
		return ISC_FALSE;
	reply->lease->addr = tmp_addr;
	reply->lease->plen = 0;
	/* Default is soft binding for 2 minutes. */
	if (add_lease6(pool, reply->lease, cur_time + 120) != ISC_R_SUCCESS)
		return ISC_FALSE;

	return ISC_TRUE;
}

/*
 * Get a temporary address per prefix.
 */
static isc_result_t
find_client_temporaries(struct reply_state *reply) {
	struct shared_network *shared;
	int i;
	struct ipv6_pool *p;
	isc_result_t status;
	unsigned int attempts;
	struct iaddr send_addr;

	/*
	 * No pools, we're done.
	 */
	shared = reply->shared;
	if (shared->ipv6_pools == NULL) {
		log_debug("Unable to get client addresses: "
			  "no IPv6 pools on this shared network");
		return ISC_R_NORESOURCES;
	}

	status = ISC_R_NORESOURCES;
	for (i = 0;; i++) {
		p = shared->ipv6_pools[i];
		if (p == NULL) {
			break;
		}
		if (p->pool_type != D6O_IA_TA) {
			continue;
		}

		/*
		 * Get an address in this temporary pool.
		 */
		status = create_lease6(p, &reply->lease, &attempts,
				       &reply->client_id, cur_time + 120);
		if (status != ISC_R_SUCCESS) {
			log_debug("Unable to get a temporary address.");
			goto cleanup;
		}

		status = reply_process_is_addressed(reply,
						    &reply->lease->scope,
				      reply->lease->ipv6_pool->subnet->group);
		if (status != ISC_R_SUCCESS) {
			goto cleanup;
		}
		send_addr.len = 16;
		memcpy(send_addr.iabuf, &reply->lease->addr, 16);
		status = reply_process_send_addr(reply, &send_addr);
		if (status != ISC_R_SUCCESS) {
			goto cleanup;
		}
		if (reply->lease != NULL) {
			iasubopt_dereference(&reply->lease, MDL);
		}
	}

      cleanup:
	if (reply->lease != NULL) {
		iasubopt_dereference(&reply->lease, MDL);
	}
	return status;
}

/*
 * This function only returns failure on 'hard' failures.  If it succeeds,
 * it will leave a lease structure behind.
 */
static isc_result_t
reply_process_try_addr(struct reply_state *reply, struct iaddr *addr) {
	isc_result_t status = ISC_R_NORESOURCES;
	struct ipv6_pool *pool;
	int i;
	struct data_string data_addr;

	if ((reply == NULL) || (reply->shared == NULL) ||
	    (reply->shared->ipv6_pools == NULL) || (addr == NULL) ||
	    (reply->lease != NULL))
		return DHCP_R_INVALIDARG;

	memset(&data_addr, 0, sizeof(data_addr));
	data_addr.len = addr->len;
	data_addr.data = addr->iabuf;

	for (i = 0 ; (pool = reply->shared->ipv6_pools[i]) != NULL ; i++) {
		if (pool->pool_type != D6O_IA_NA)
			continue;
		status = try_client_v6_address(&reply->lease, pool,
					       &data_addr);
		if (status == ISC_R_SUCCESS)
			break;
	}

	/* Note that this is just pedantry.  There is no allocation to free. */
	data_string_forget(&data_addr, MDL);
	/* Return just the most recent status... */
	return status;
}

/* Look around for an address to give the client.  First, look through the
 * old IA for addresses we can extend.  Second, try to allocate a new address.
 * Finally, actually add that address into the current reply IA.
 */
static isc_result_t
find_client_address(struct reply_state *reply) {
	struct iaddr send_addr;
	isc_result_t status = ISC_R_NORESOURCES;
	struct iasubopt *lease, *best_lease = NULL;
	struct binding_scope **scope;
	struct group *group;
	int i;

	if (reply->static_lease) {
		if (reply->host == NULL)
			return DHCP_R_INVALIDARG;

		send_addr.len = 16;
		memcpy(send_addr.iabuf, reply->fixed.data, 16);

		status = ISC_R_SUCCESS;
		scope = &global_scope;
		group = reply->subnet->group;
		goto send_addr;
	}

	if (reply->old_ia != NULL)  {
		for (i = 0 ; i < reply->old_ia->num_iasubopt ; i++) {
			struct shared_network *candidate_shared;

			lease = reply->old_ia->iasubopt[i];
			candidate_shared = lease->ipv6_pool->shared_network;

			/*
			 * Look for the best lease on the client's shared
			 * network.
			 */
			if (candidate_shared == reply->shared) {
				best_lease = lease_compare(lease, best_lease);
			}
		}
	}

	/* Try to pick a new address if we didn't find one, or if we found an
	 * abandoned lease.
	 */
	if ((best_lease == NULL) || (best_lease->state == FTS_ABANDONED)) {
		status = pick_v6_address(&reply->lease, reply->shared,
					 &reply->client_id);
	} else if (best_lease != NULL) {
		iasubopt_reference(&reply->lease, best_lease, MDL);
		status = ISC_R_SUCCESS;
	}

	/* Pick the abandoned lease as a last resort. */
	if ((status == ISC_R_NORESOURCES) && (best_lease != NULL)) {
		/* I don't see how this is supposed to be done right now. */
		log_error("Reclaiming abandoned addresses is not yet "
			  "supported.  Treating this as an out of space "
			  "condition.");
		/* iasubopt_reference(&reply->lease, best_lease, MDL); */
	}

	/* Give up now if we didn't find a lease. */
	if (status != ISC_R_SUCCESS)
		return status;

	if (reply->lease == NULL)
		log_fatal("Impossible condition at %s:%d.", MDL);

	/* Draw binding scopes from the lease's binding scope, and config
	 * from the lease's containing subnet and higher.  Note that it may
	 * be desirable to place the group attachment directly in the pool.
	 */
	scope = &reply->lease->scope;
	group = reply->lease->ipv6_pool->subnet->group;

	send_addr.len = 16;
	memcpy(send_addr.iabuf, &reply->lease->addr, 16);

      send_addr:
	status = reply_process_is_addressed(reply, scope, group);
	if (status != ISC_R_SUCCESS)
		return status;

	status = reply_process_send_addr(reply, &send_addr);
	return status;
}

/* Once an address is found for a client, perform several common functions;
 * Calculate and store valid and preferred lease times, draw client options
 * into the option state.
 */
static isc_result_t
reply_process_is_addressed(struct reply_state *reply,
			   struct binding_scope **scope, struct group *group)
{
	isc_result_t status = ISC_R_SUCCESS;
	struct data_string data;
	struct option_cache *oc;

	/* Initialize values we will cleanup. */
	memset(&data, 0, sizeof(data));

	/*
	 * Bring configured options into the root packet level cache - start
	 * with the lease's closest enclosing group (passed in by the caller
	 * as 'group').
	 */
	execute_statements_in_scope(NULL, reply->packet, NULL, NULL,
				    reply->packet->options, reply->opt_state,
				    scope, group, root_group);

	/*
	 * If there is a host record, over-ride with values configured there,
	 * without re-evaluating configuration from the previously executed
	 * group or its common enclosers.
	 */
	if (reply->host != NULL)
		execute_statements_in_scope(NULL, reply->packet, NULL, NULL,
					    reply->packet->options,
					    reply->opt_state, scope,
					    reply->host->group, group);

	/* Determine valid lifetime. */
	if (reply->client_valid == 0)
		reply->send_valid = DEFAULT_DEFAULT_LEASE_TIME;
	else
		reply->send_valid = reply->client_valid;

	oc = lookup_option(&server_universe, reply->opt_state,
			   SV_DEFAULT_LEASE_TIME);
	if (oc != NULL) {
		if (!evaluate_option_cache(&data, reply->packet, NULL, NULL,
					   reply->packet->options,
					   reply->opt_state,
					   scope, oc, MDL) ||
		    (data.len != 4)) {
			log_error("reply_process_is_addressed: unable to "
				  "evaluate default lease time");
			status = ISC_R_FAILURE;
			goto cleanup;
		}

		reply->send_valid = getULong(data.data);
		data_string_forget(&data, MDL);
	}

	if (reply->client_prefer == 0)
		reply->send_prefer = reply->send_valid;
	else
		reply->send_prefer = reply->client_prefer;

	if (reply->send_prefer >= reply->send_valid)
		reply->send_prefer = (reply->send_valid / 2) +
				     (reply->send_valid / 8);

	oc = lookup_option(&server_universe, reply->opt_state,
			   SV_PREFER_LIFETIME);
	if (oc != NULL) {
		if (!evaluate_option_cache(&data, reply->packet, NULL, NULL,
					   reply->packet->options,
					   reply->opt_state,
					   scope, oc, MDL) ||
		    (data.len != 4)) {
			log_error("reply_process_is_addressed: unable to "
				  "evaluate preferred lease time");
			status = ISC_R_FAILURE;
			goto cleanup;
		}

		reply->send_prefer = getULong(data.data);
		data_string_forget(&data, MDL);
	}

	/* Note lowest values for later calculation of renew/rebind times. */
	if (reply->prefer > reply->send_prefer)
		reply->prefer = reply->send_prefer;

	if (reply->valid > reply->send_valid)
		reply->valid = reply->send_valid;

#if 0
	/*
	 * XXX: Old 4.0.0 alpha code would change the host {} record
	 * XXX: uid upon lease assignment.  This was intended to cover the
	 * XXX: case where a client first identifies itself using vendor
	 * XXX: options in a solicit, or request, but later neglects to include
	 * XXX: these options in a Renew or Rebind.  It is not clear that this
	 * XXX: is required, and has some startling ramifications (such as
	 * XXX: how to recover this dynamic host {} state across restarts).
	 */
	if (reply->host != NULL)
		change_host_uid(host, reply->client_id->data,
				reply->client_id->len);
#endif /* 0 */

	/* Perform dynamic lease related update work. */
	if (reply->lease != NULL) {
		/* Cached lifetimes */
		reply->lease->prefer = reply->send_prefer;
		reply->lease->valid = reply->send_valid;

		/* Advance (or rewind) the valid lifetime. */
		if (reply->buf.reply.msg_type == DHCPV6_REPLY) {
			reply->lease->soft_lifetime_end_time =
				cur_time + reply->send_valid;
			/* Wait before renew! */
		}

		status = ia_add_iasubopt(reply->ia, reply->lease, MDL);
		if (status != ISC_R_SUCCESS) {
			log_fatal("reply_process_is_addressed: Unable to "
				  "attach lease to new IA: %s",
				  isc_result_totext(status));
		}

		/*
		 * If this is a new lease, make sure it is attached somewhere.
		 */
		if (reply->lease->ia == NULL) {
			ia_reference(&reply->lease->ia, reply->ia, MDL);
		}
	}

	/* Bring a copy of the relevant options into the IA scope. */
	execute_statements_in_scope(NULL, reply->packet, NULL, NULL,
				    reply->packet->options, reply->reply_ia,
				    scope, group, root_group);

	/*
	 * And bring in host record configuration, if any, but not to overlap
	 * the previous group or its common enclosers.
	 */
	if (reply->host != NULL)
		execute_statements_in_scope(NULL, reply->packet, NULL, NULL,
					    reply->packet->options,
					    reply->reply_ia, scope,
					    reply->host->group, group);

      cleanup:
	if (data.data != NULL)
		data_string_forget(&data, MDL);

	if (status == ISC_R_SUCCESS)
		reply->client_resources++;

	return status;
}

/* Simply send an IAADDR within the IA scope as described. */
static isc_result_t
reply_process_send_addr(struct reply_state *reply, struct iaddr *addr) {
	isc_result_t status = ISC_R_SUCCESS;
	struct data_string data;

	memset(&data, 0, sizeof(data));

	/* Now append the lease. */
	data.len = IAADDR_OFFSET;
	if (!buffer_allocate(&data.buffer, data.len, MDL)) {
		log_error("reply_process_send_addr: out of memory"
			  "allocating new IAADDR buffer.");
		status = ISC_R_NOMEMORY;
		goto cleanup;
	}
	data.data = data.buffer->data;

	memcpy(data.buffer->data, addr->iabuf, 16);
	putULong(data.buffer->data + 16, reply->send_prefer);
	putULong(data.buffer->data + 20, reply->send_valid);

	if (!append_option_buffer(&dhcpv6_universe, reply->reply_ia,
				  data.buffer, data.buffer->data,
				  data.len, D6O_IAADDR, 0)) {
		log_error("reply_process_send_addr: unable "
			  "to save IAADDR option");
		status = ISC_R_FAILURE;
		goto cleanup;
	}

	reply->resources_included = ISC_TRUE;

      cleanup:
	if (data.data != NULL)
		data_string_forget(&data, MDL);

	return status;
}

/* Choose the better of two leases. */
static struct iasubopt *
lease_compare(struct iasubopt *alpha, struct iasubopt *beta) {
	if (alpha == NULL)
		return beta;
	if (beta == NULL)
		return alpha;

	switch(alpha->state) {
	      case FTS_ACTIVE:
		switch(beta->state) {
		      case FTS_ACTIVE:
			/* Choose the lease with the longest lifetime (most
			 * likely the most recently allocated).
			 */
			if (alpha->hard_lifetime_end_time < 
			    beta->hard_lifetime_end_time)
				return beta;
			else
				return alpha;

		      case FTS_EXPIRED:
		      case FTS_ABANDONED:
			return alpha;

		      default:
			log_fatal("Impossible condition at %s:%d.", MDL);
		}
		break;

	      case FTS_EXPIRED:
		switch (beta->state) {
		      case FTS_ACTIVE:
			return beta;

		      case FTS_EXPIRED:
			/* Choose the most recently expired lease. */
			if (alpha->hard_lifetime_end_time <
			    beta->hard_lifetime_end_time)
				return beta;
			else if ((alpha->hard_lifetime_end_time ==
				  beta->hard_lifetime_end_time) &&
				 (alpha->soft_lifetime_end_time <
				  beta->soft_lifetime_end_time))
				return beta;
			else
				return alpha;

		      case FTS_ABANDONED:
			return alpha;

		      default:
			log_fatal("Impossible condition at %s:%d.", MDL);
		}
		break;

	      case FTS_ABANDONED:
		switch (beta->state) {
		      case FTS_ACTIVE:
		      case FTS_EXPIRED:
			return alpha;

		      case FTS_ABANDONED:
			/* Choose the lease that was abandoned longest ago. */
			if (alpha->hard_lifetime_end_time <
			    beta->hard_lifetime_end_time)
				return alpha;

		      default:
			log_fatal("Impossible condition at %s:%d.", MDL);
		}
		break;

	      default:
		log_fatal("Impossible condition at %s:%d.", MDL);
	}

	log_fatal("Triple impossible condition at %s:%d.", MDL);
	return NULL;
}

/* Process a client-supplied IA_PD.  This may append options to the tail of
 * the reply packet being built in the reply_state structure.
 */
static isc_result_t
reply_process_ia_pd(struct reply_state *reply, struct option_cache *ia) {
	isc_result_t status = ISC_R_SUCCESS;
	u_int32_t iaid;
	unsigned ia_cursor;
	struct option_state *packet_ia;
	struct option_cache *oc;
	struct data_string ia_data, data;

	/* Initialize values that will get cleaned up on return. */
	packet_ia = NULL;
	memset(&ia_data, 0, sizeof(ia_data));
	memset(&data, 0, sizeof(data));
	/* 
	 * Note that find_client_prefix() may set reply->lease.
	 */

	/* Make sure there is at least room for the header. */
	if ((reply->cursor + IA_PD_OFFSET + 4) > sizeof(reply->buf)) {
		log_error("reply_process_ia_pd: Reply too long for IA.");
		return ISC_R_NOSPACE;
	}


	/* Fetch the IA_PD contents. */
	if (!get_encapsulated_IA_state(&packet_ia, &ia_data, reply->packet,
				       ia, IA_PD_OFFSET)) {
		log_error("reply_process_ia_pd: error evaluating ia");
		status = ISC_R_FAILURE;
		goto cleanup;
	}

	/* Extract IA_PD header contents. */
	iaid = getULong(ia_data.data);
	reply->renew = getULong(ia_data.data + 4);
	reply->rebind = getULong(ia_data.data + 8);

	/* Create an IA_PD structure. */
	if (ia_allocate(&reply->ia, iaid, (char *)reply->client_id.data, 
			reply->client_id.len, MDL) != ISC_R_SUCCESS) {
		log_error("reply_process_ia_pd: no memory for ia.");
		status = ISC_R_NOMEMORY;
		goto cleanup;
	}
	reply->ia->ia_type = D6O_IA_PD;

	/* Cache pre-existing IA_PD, if any. */
	ia_hash_lookup(&reply->old_ia, ia_pd_active,
		       (unsigned char *)reply->ia->iaid_duid.data,
		       reply->ia->iaid_duid.len, MDL);

	/*
	 * Create an option cache to carry the IA_PD option contents, and
	 * execute any user-supplied values into it.
	 */
	if (!option_state_allocate(&reply->reply_ia, MDL)) {
		status = ISC_R_NOMEMORY;
		goto cleanup;
	}

	/* Check & count the fixed prefix host records. */
	reply->static_prefixes = 0;
	if ((reply->host != NULL) && (reply->host->fixed_prefix != NULL)) {
		struct iaddrcidrnetlist *fp;

		for (fp = reply->host->fixed_prefix; fp != NULL;
		     fp = fp->next) {
			reply->static_prefixes += 1;
		}
	}

	/*
	 * Save the cursor position at the start of the IA_PD, so we can
	 * set length and adjust t1/t2 values later.  We write a temporary
	 * header out now just in case we decide to adjust the packet
	 * within sub-process functions.
	 */
	ia_cursor = reply->cursor;

	/* Initialize the IA_PD header.  First the code. */
	putUShort(reply->buf.data + reply->cursor, (unsigned)D6O_IA_PD);
	reply->cursor += 2;

	/* Then option length. */
	putUShort(reply->buf.data + reply->cursor, 0x0Cu);
	reply->cursor += 2;

	/* Then IA_PD header contents; IAID. */
	putULong(reply->buf.data + reply->cursor, iaid);
	reply->cursor += 4;

	/* We store the client's t1 for now, and may over-ride it later. */
	putULong(reply->buf.data + reply->cursor, reply->renew);
	reply->cursor += 4;

	/* We store the client's t2 for now, and may over-ride it later. */
	putULong(reply->buf.data + reply->cursor, reply->rebind);
	reply->cursor += 4;

	/* 
	 * For each prefix in this IA_PD, decide what to do about it.
	 */
	oc = lookup_option(&dhcpv6_universe, packet_ia, D6O_IAPREFIX);
	reply->valid = reply->prefer = 0xffffffff;
	reply->client_valid = reply->client_prefer = 0;
	reply->preflen = -1;
	for (; oc != NULL ; oc = oc->next) {
		status = reply_process_prefix(reply, oc);

		/*
		 * Canceled means we did not allocate prefixes to the
		 * client, but we're "done" with this IA - we set a status
		 * code.  So transmit this reply, e.g., move on to the next
		 * IA.
		 */
		if (status == ISC_R_CANCELED)
			break;

		if ((status != ISC_R_SUCCESS) && (status != ISC_R_ADDRINUSE))
			goto cleanup;
	}

	reply->pd_count++;

	/*
	 * If we fell through the above and never gave the client
	 * a prefix, give it one now.
	 */
	if ((status != ISC_R_CANCELED) && (reply->client_resources == 0)) {
		status = find_client_prefix(reply);

		if (status == ISC_R_NORESOURCES) {
			switch (reply->packet->dhcpv6_msg_type) {
			      case DHCPV6_SOLICIT:
				/*
				 * No prefix for any IA is handled
				 * by the caller.
				 */
				/* FALL THROUGH */

			      case DHCPV6_REQUEST:
				/* Same than for addresses. */
				option_state_dereference(&reply->reply_ia, MDL);
				if (!option_state_allocate(&reply->reply_ia,
							   MDL))
				{
					log_error("reply_process_ia_pd: No "
						  "memory for option state "
						  "wipe.");
					status = ISC_R_NOMEMORY;
					goto cleanup;
				}

				if (!set_status_code(STATUS_NoPrefixAvail,
						     "No prefixes available "
						     "for this interface.",
						      reply->reply_ia)) {
					log_error("reply_process_ia_pd: "
						  "Unable to set "
						  "NoPrefixAvail status "
						  "code.");
					status = ISC_R_FAILURE;
					goto cleanup;
				}

				status = ISC_R_SUCCESS;
				break;

			      default:
				if (reply->resources_included)
					status = ISC_R_SUCCESS;
				else
					goto cleanup;
				break;
			}
		}

		if (status != ISC_R_SUCCESS)
			goto cleanup;
	}

	reply->cursor += store_options6((char *)reply->buf.data + reply->cursor,
					sizeof(reply->buf) - reply->cursor,
					reply->reply_ia, reply->packet,
					required_opts_IA_PD, NULL);

	/* Reset the length of this IA_PD to match what was just written. */
	putUShort(reply->buf.data + ia_cursor + 2,
		  reply->cursor - (ia_cursor + 4));

	/*
	 * T1/T2 time selection is kind of weird.  We actually use DHCP
	 * (v4) scoped options as handy existing places where these might
	 * be configured by an administrator.  A value of zero tells the
	 * client it may choose its own renewal time.
	 */
	reply->renew = 0;
	oc = lookup_option(&dhcp_universe, reply->opt_state,
			   DHO_DHCP_RENEWAL_TIME);
	if (oc != NULL) {
		if (!evaluate_option_cache(&data, reply->packet, NULL, NULL,
					   reply->packet->options,
					   reply->opt_state, &global_scope,
					   oc, MDL) ||
		    (data.len != 4)) {
			log_error("Invalid renewal time.");
		} else {
			reply->renew = getULong(data.data);
		}

		if (data.data != NULL)
			data_string_forget(&data, MDL);
	}
	putULong(reply->buf.data + ia_cursor + 8, reply->renew);

	/* Now T2. */
	reply->rebind = 0;
	oc = lookup_option(&dhcp_universe, reply->opt_state,
			   DHO_DHCP_REBINDING_TIME);
	if (oc != NULL) {
		if (!evaluate_option_cache(&data, reply->packet, NULL, NULL,
					   reply->packet->options,
					   reply->opt_state, &global_scope,
					   oc, MDL) ||
		    (data.len != 4)) {
			log_error("Invalid rebinding time.");
		} else {
			reply->rebind = getULong(data.data);
		}

		if (data.data != NULL)
			data_string_forget(&data, MDL);
	}
	putULong(reply->buf.data + ia_cursor + 12, reply->rebind);

	/*
	 * If this is not a 'soft' binding, consume the new changes into
	 * the database (if any have been attached to the ia_pd).
	 *
	 * Loop through the assigned dynamic prefixes, referencing the
	 * prefixes onto this IA_PD rather than any old ones, and updating
	 * prefix pool timers for each (if any).
	 */
	if ((status != ISC_R_CANCELED) && (reply->static_prefixes == 0) &&
	    (reply->buf.reply.msg_type == DHCPV6_REPLY) &&
	    (reply->ia->num_iasubopt != 0)) {
		struct iasubopt *tmp;
		struct data_string *ia_id;
		int i;

		for (i = 0 ; i < reply->ia->num_iasubopt ; i++) {
			tmp = reply->ia->iasubopt[i];

			if (tmp->ia != NULL)
				ia_dereference(&tmp->ia, MDL);
			ia_reference(&tmp->ia, reply->ia, MDL);

			/* Commit 'hard' bindings. */
			tmp->hard_lifetime_end_time =
				tmp->soft_lifetime_end_time;
			tmp->soft_lifetime_end_time = 0;
			renew_lease6(tmp->ipv6_pool, tmp);
			schedule_lease_timeout(tmp->ipv6_pool);
		}

		/* Remove any old ia from the hash. */
		if (reply->old_ia != NULL) {
			ia_id = &reply->old_ia->iaid_duid;
			ia_hash_delete(ia_pd_active,
				       (unsigned char *)ia_id->data,
				       ia_id->len, MDL);
			ia_dereference(&reply->old_ia, MDL);
		}

		/* Put new ia into the hash. */
		reply->ia->cltt = cur_time;
		ia_id = &reply->ia->iaid_duid;
		ia_hash_add(ia_pd_active, (unsigned char *)ia_id->data,
			    ia_id->len, reply->ia, MDL);

		write_ia(reply->ia);
	}

      cleanup:
	if (packet_ia != NULL)
		option_state_dereference(&packet_ia, MDL);
	if (reply->reply_ia != NULL)
		option_state_dereference(&reply->reply_ia, MDL);
	if (ia_data.data != NULL)
		data_string_forget(&ia_data, MDL);
	if (data.data != NULL)
		data_string_forget(&data, MDL);
	if (reply->ia != NULL)
		ia_dereference(&reply->ia, MDL);
	if (reply->old_ia != NULL)
		ia_dereference(&reply->old_ia, MDL);
	if (reply->lease != NULL)
		iasubopt_dereference(&reply->lease, MDL);

	/*
	 * ISC_R_CANCELED is a status code used by the prefix processing to
	 * indicate we're replying with a status code.  This is still a
	 * success at higher layers.
	 */
	return((status == ISC_R_CANCELED) ? ISC_R_SUCCESS : status);
}

/*
 * Process an IAPREFIX within a given IA_PD, storing any IAPREFIX reply
 * contents into the reply's current ia_pd-scoped option cache.  Returns
 * ISC_R_CANCELED in the event we are replying with a status code and do
 * not wish to process more IAPREFIXes within this IA_PD.
 */
static isc_result_t
reply_process_prefix(struct reply_state *reply, struct option_cache *pref) {
	u_int32_t pref_life, valid_life;
	struct binding_scope **scope;
	struct iaddrcidrnet tmp_pref;
	struct option_cache *oc;
	struct data_string iapref, data;
	isc_result_t status = ISC_R_SUCCESS;

	/* Initializes values that will be cleaned up. */
	memset(&iapref, 0, sizeof(iapref));
	memset(&data, 0, sizeof(data));
	/* Note that reply->lease may be set by prefix_is_owned() */

	/*
	 * There is no point trying to process an incoming prefix if there
	 * is no room for an outgoing prefix.
	 */
	if ((reply->cursor + 29) > sizeof(reply->buf)) {
		log_error("reply_process_prefix: Out of room for prefix.");
		return ISC_R_NOSPACE;
	}

	/* Extract this IAPREFIX option. */
	if (!evaluate_option_cache(&iapref, reply->packet, NULL, NULL, 
				   reply->packet->options, NULL, &global_scope,
				   pref, MDL) ||
	    (iapref.len < IAPREFIX_OFFSET)) {
		log_error("reply_process_prefix: error evaluating IAPREFIX.");
		status = ISC_R_FAILURE;
		goto cleanup;
	}

	/*
	 * Layout: preferred and valid lifetimes followed by the prefix
	 * length and the IPv6 address.
	 */
	pref_life = getULong(iapref.data);
	valid_life = getULong(iapref.data + 4);

	if ((reply->client_valid == 0) ||
	    (reply->client_valid > valid_life))
		reply->client_valid = valid_life;

	if ((reply->client_prefer == 0) ||
	    (reply->client_prefer > pref_life))
		reply->client_prefer = pref_life;

	/* 
	 * Clients may choose to send ::/0 as a prefix, with the idea to give
	 * hints about preferred-lifetime or valid-lifetime.
	 */
	tmp_pref.lo_addr.len = 16;
	memset(tmp_pref.lo_addr.iabuf, 0, 16);
	if ((iapref.data[8] == 0) &&
	    (memcmp(iapref.data + 9, tmp_pref.lo_addr.iabuf, 16) == 0)) {
		/* Status remains success; we just ignore this one. */
		goto cleanup;
	}

	/*
	 * Clients may choose to send ::/X as a prefix to specify a
	 * preferred/requested prefix length. Note X is never zero here.
	 */
	tmp_pref.bits = (int) iapref.data[8];
	if (reply->preflen < 0) {
		/* Cache the first preferred prefix length. */
		reply->preflen = tmp_pref.bits;
	}
	if (memcmp(iapref.data + 9, tmp_pref.lo_addr.iabuf, 16) == 0) {
		goto cleanup;
	}

	memcpy(tmp_pref.lo_addr.iabuf, iapref.data + 9, 16);

	/* Verify the prefix belongs to the client. */
	if (!prefix_is_owned(reply, &tmp_pref)) {
		/* Same than for addresses. */
		if ((reply->packet->dhcpv6_msg_type == DHCPV6_SOLICIT) ||
		    (reply->packet->dhcpv6_msg_type == DHCPV6_REQUEST) ||
		    (reply->packet->dhcpv6_msg_type == DHCPV6_REBIND)) {
			status = reply_process_try_prefix(reply, &tmp_pref);

			/* Either error out or skip this prefix. */
			if ((status != ISC_R_SUCCESS) && 
			    (status != ISC_R_ADDRINUSE)) 
				goto cleanup;

			if (reply->lease == NULL) {
				if (reply->packet->dhcpv6_msg_type ==
							DHCPV6_REBIND) {
					reply->send_prefer = 0;
					reply->send_valid = 0;
					goto send_pref;
				}

				/* status remains success - ignore */
				goto cleanup;
			}
		/*
		 * RFC3633 section 18.2.3:
		 *
		 * If the delegating router cannot find a binding
		 * for the requesting router's IA_PD the delegating
		 * router returns the IA_PD containing no prefixes
		 * with a Status Code option set to NoBinding in the
		 * Reply message.
		 *
		 * On mismatch we (ab)use this pretending we have not the IA
		 * as soon as we have not a prefix.
		 */
		} else if (reply->packet->dhcpv6_msg_type == DHCPV6_RENEW) {
			/* Rewind the IA_PD to empty. */
			option_state_dereference(&reply->reply_ia, MDL);
			if (!option_state_allocate(&reply->reply_ia, MDL)) {
				log_error("reply_process_prefix: No memory "
					  "for option state wipe.");
				status = ISC_R_NOMEMORY;
				goto cleanup;
			}

			/* Append a NoBinding status code.  */
			if (!set_status_code(STATUS_NoBinding,
					     "Prefix not bound to this "
					     "interface.", reply->reply_ia)) {
				log_error("reply_process_prefix: Unable to "
					  "attach status code.");
				status = ISC_R_FAILURE;
				goto cleanup;
			}

			/* Fin (no more IAPREFIXes). */
			status = ISC_R_CANCELED;
			goto cleanup;
		} else {
			log_error("It is impossible to lease a client that is "
				  "not sending a solicit, request, renew, or "
				  "rebind message.");
			status = ISC_R_FAILURE;
			goto cleanup;
		}
	}

	if (reply->static_prefixes > 0) {
		if (reply->host == NULL)
			log_fatal("Impossible condition at %s:%d.", MDL);

		scope = &global_scope;
	} else {
		if (reply->lease == NULL)
			log_fatal("Impossible condition at %s:%d.", MDL);

		scope = &reply->lease->scope;
	}

	/*
	 * If client_resources is nonzero, then the reply_process_is_prefixed
	 * function has executed configuration state into the reply option
	 * cache.  We will use that valid cache to derive configuration for
	 * whether or not to engage in additional prefixes, and similar.
	 */
	if (reply->client_resources != 0) {
		unsigned limit = 1;

		/*
		 * Does this client have "enough" prefixes already?  Default
		 * to one.  Everybody gets one, and one should be enough for
		 * anybody.
		 */
		oc = lookup_option(&server_universe, reply->opt_state,
				   SV_LIMIT_PREFS_PER_IA);
		if (oc != NULL) {
			if (!evaluate_option_cache(&data, reply->packet,
						   NULL, NULL,
						   reply->packet->options,
						   reply->opt_state,
						   scope, oc, MDL) ||
			    (data.len != 4)) {
				log_error("reply_process_prefix: unable to "
					  "evaluate prefs-per-ia value.");
				status = ISC_R_FAILURE;
				goto cleanup;
			}

			limit = getULong(data.data);
			data_string_forget(&data, MDL);
		}

		/*
		 * If we wish to limit the client to a certain number of
		 * prefixes, then omit the prefix from the reply.
		 */
		if (reply->client_resources >= limit)
			goto cleanup;
	}

	status = reply_process_is_prefixed(reply, scope, reply->shared->group);
	if (status != ISC_R_SUCCESS)
		goto cleanup;

      send_pref:
	status = reply_process_send_prefix(reply, &tmp_pref);

      cleanup:
	if (iapref.data != NULL)
		data_string_forget(&iapref, MDL);
	if (data.data != NULL)
		data_string_forget(&data, MDL);
	if (reply->lease != NULL)
		iasubopt_dereference(&reply->lease, MDL);

	return status;
}

/*
 * Verify the prefix belongs to the client.  If we've got a host
 * record with fixed prefixes, it has to be an assigned prefix
 * (fault out all else).  Otherwise it's a dynamic prefix, so lookup
 * that prefix and make sure it belongs to this DUID:IAID pair.
 */
static isc_boolean_t
prefix_is_owned(struct reply_state *reply, struct iaddrcidrnet *pref) {
	struct iaddrcidrnetlist *l;
	int i;

	/*
	 * This faults out prefixes that don't match fixed prefixes.
	 */
	if (reply->static_prefixes > 0) {
		for (l = reply->host->fixed_prefix; l != NULL; l = l->next) {
			if ((pref->bits == l->cidrnet.bits) &&
			    (memcmp(pref->lo_addr.iabuf,
				    l->cidrnet.lo_addr.iabuf, 16) == 0))
				return ISC_TRUE;
		}
		return ISC_FALSE;
	}

	if ((reply->old_ia == NULL) ||
	    (reply->old_ia->num_iasubopt == 0))
		return ISC_FALSE;

	for (i = 0 ; i < reply->old_ia->num_iasubopt ; i++) {
		struct iasubopt *tmp;

		tmp = reply->old_ia->iasubopt[i];

		if ((pref->bits == (int) tmp->plen) &&
		    memcmp(pref->lo_addr.iabuf, &tmp->addr, 16) == 0) {
			iasubopt_reference(&reply->lease, tmp, MDL);
			return ISC_TRUE;
		}
	}

	return ISC_FALSE;
}

/*
 * This function only returns failure on 'hard' failures.  If it succeeds,
 * it will leave a prefix structure behind.
 */
static isc_result_t
reply_process_try_prefix(struct reply_state *reply,
			 struct iaddrcidrnet *pref) {
	isc_result_t status = ISC_R_NORESOURCES;
	struct ipv6_pool *pool;
	int i;
	struct data_string data_pref;

	if ((reply == NULL) || (reply->shared == NULL) ||
	    (reply->shared->ipv6_pools == NULL) || (pref == NULL) ||
	    (reply->lease != NULL))
		return DHCP_R_INVALIDARG;

	memset(&data_pref, 0, sizeof(data_pref));
	data_pref.len = 17;
	if (!buffer_allocate(&data_pref.buffer, data_pref.len, MDL)) {
		log_error("reply_process_try_prefix: out of memory.");
		return ISC_R_NOMEMORY;
	}
	data_pref.data = data_pref.buffer->data;
	data_pref.buffer->data[0] = (u_int8_t) pref->bits;
	memcpy(data_pref.buffer->data + 1, pref->lo_addr.iabuf, 16);

	for (i = 0 ; (pool = reply->shared->ipv6_pools[i]) != NULL ; i++) {
		if (pool->pool_type != D6O_IA_PD)
			continue;
		status = try_client_v6_prefix(&reply->lease, pool,
					      &data_pref);
		if (status == ISC_R_SUCCESS)
			break;
	}

	data_string_forget(&data_pref, MDL);
	/* Return just the most recent status... */
	return status;
}

/* Look around for a prefix to give the client.  First, look through the old
 * IA_PD for prefixes we can extend.  Second, try to allocate a new prefix.
 * Finally, actually add that prefix into the current reply IA_PD.
 */
static isc_result_t
find_client_prefix(struct reply_state *reply) {
	struct iaddrcidrnet send_pref;
	isc_result_t status = ISC_R_NORESOURCES;
	struct iasubopt *prefix, *best_prefix = NULL;
	struct binding_scope **scope;
	int i;

	if (reply->static_prefixes > 0) {
		struct iaddrcidrnetlist *l;

		if (reply->host == NULL)
			return DHCP_R_INVALIDARG;

		for (l = reply->host->fixed_prefix; l != NULL; l = l->next) {
			if (l->cidrnet.bits == reply->preflen)
				break;
		}
		if (l == NULL) {
			/*
			 * If no fixed prefix has the preferred length,
			 * get the first one.
			 */
			l = reply->host->fixed_prefix;
		}
		memcpy(&send_pref, &l->cidrnet, sizeof(send_pref));

		status = ISC_R_SUCCESS;
		scope = &global_scope;
		goto send_pref;
	}

	if (reply->old_ia != NULL)  {
		for (i = 0 ; i < reply->old_ia->num_iasubopt ; i++) {
			struct shared_network *candidate_shared;

			prefix = reply->old_ia->iasubopt[i];
			candidate_shared = prefix->ipv6_pool->shared_network;

			/*
			 * Consider this prefix if it is in a global pool or
			 * if it is scoped in a pool under the client's shared
			 * network.
			 */
			if (candidate_shared == NULL ||
			    candidate_shared == reply->shared) {
				best_prefix = prefix_compare(reply, prefix,
							     best_prefix);
			}
		}
	}

	/* Try to pick a new prefix if we didn't find one, or if we found an
	 * abandoned prefix.
	 */
	if ((best_prefix == NULL) || (best_prefix->state == FTS_ABANDONED)) {
		status = pick_v6_prefix(&reply->lease, reply->preflen,
					reply->shared, &reply->client_id);
	} else if (best_prefix != NULL) {
		iasubopt_reference(&reply->lease, best_prefix, MDL);
		status = ISC_R_SUCCESS;
	}

	/* Pick the abandoned prefix as a last resort. */
	if ((status == ISC_R_NORESOURCES) && (best_prefix != NULL)) {
		/* I don't see how this is supposed to be done right now. */
		log_error("Reclaiming abandoned prefixes is not yet "
			  "supported.  Treating this as an out of space "
			  "condition.");
		/* iasubopt_reference(&reply->lease, best_prefix, MDL); */
	}

	/* Give up now if we didn't find a prefix. */
	if (status != ISC_R_SUCCESS)
		return status;

	if (reply->lease == NULL)
		log_fatal("Impossible condition at %s:%d.", MDL);

	scope = &reply->lease->scope;

	send_pref.lo_addr.len = 16;
	memcpy(send_pref.lo_addr.iabuf, &reply->lease->addr, 16);
	send_pref.bits = (int) reply->lease->plen;

      send_pref:
	status = reply_process_is_prefixed(reply, scope, reply->shared->group);
	if (status != ISC_R_SUCCESS)
		return status;

	status = reply_process_send_prefix(reply, &send_pref);
	return status;
}

/* Once a prefix is found for a client, perform several common functions;
 * Calculate and store valid and preferred prefix times, draw client options
 * into the option state.
 */
static isc_result_t
reply_process_is_prefixed(struct reply_state *reply,
			  struct binding_scope **scope, struct group *group)
{
	isc_result_t status = ISC_R_SUCCESS;
	struct data_string data;
	struct option_cache *oc;

	/* Initialize values we will cleanup. */
	memset(&data, 0, sizeof(data));

	/*
	 * Bring configured options into the root packet level cache - start
	 * with the lease's closest enclosing group (passed in by the caller
	 * as 'group').
	 */
	execute_statements_in_scope(NULL, reply->packet, NULL, NULL,
				    reply->packet->options, reply->opt_state,
				    scope, group, root_group);

	/*
	 * If there is a host record, over-ride with values configured there,
	 * without re-evaluating configuration from the previously executed
	 * group or its common enclosers.
	 */
	if (reply->host != NULL)
		execute_statements_in_scope(NULL, reply->packet, NULL, NULL,
					    reply->packet->options,
					    reply->opt_state, scope,
					    reply->host->group, group);

	/* Determine valid lifetime. */
	if (reply->client_valid == 0)
		reply->send_valid = DEFAULT_DEFAULT_LEASE_TIME;
	else
		reply->send_valid = reply->client_valid;

	oc = lookup_option(&server_universe, reply->opt_state,
			   SV_DEFAULT_LEASE_TIME);
	if (oc != NULL) {
		if (!evaluate_option_cache(&data, reply->packet, NULL, NULL,
					   reply->packet->options,
					   reply->opt_state,
					   scope, oc, MDL) ||
		    (data.len != 4)) {
			log_error("reply_process_is_prefixed: unable to "
				  "evaluate default prefix time");
			status = ISC_R_FAILURE;
			goto cleanup;
		}

		reply->send_valid = getULong(data.data);
		data_string_forget(&data, MDL);
	}

	if (reply->client_prefer == 0)
		reply->send_prefer = reply->send_valid;
	else
		reply->send_prefer = reply->client_prefer;

	if (reply->send_prefer >= reply->send_valid)
		reply->send_prefer = (reply->send_valid / 2) +
				     (reply->send_valid / 8);

	oc = lookup_option(&server_universe, reply->opt_state,
			   SV_PREFER_LIFETIME);
	if (oc != NULL) {
		if (!evaluate_option_cache(&data, reply->packet, NULL, NULL,
					   reply->packet->options,
					   reply->opt_state,
					   scope, oc, MDL) ||
		    (data.len != 4)) {
			log_error("reply_process_is_prefixed: unable to "
				  "evaluate preferred prefix time");
			status = ISC_R_FAILURE;
			goto cleanup;
		}

		reply->send_prefer = getULong(data.data);
		data_string_forget(&data, MDL);
	}

	/* Note lowest values for later calculation of renew/rebind times. */
	if (reply->prefer > reply->send_prefer)
		reply->prefer = reply->send_prefer;

	if (reply->valid > reply->send_valid)
		reply->valid = reply->send_valid;

	/* Perform dynamic prefix related update work. */
	if (reply->lease != NULL) {
		/* Cached lifetimes */
		reply->lease->prefer = reply->send_prefer;
		reply->lease->valid = reply->send_valid;

		/* Advance (or rewind) the valid lifetime. */
		if (reply->buf.reply.msg_type == DHCPV6_REPLY) {
			reply->lease->soft_lifetime_end_time =
				cur_time + reply->send_valid;
			/* Wait before renew! */
		}

		status = ia_add_iasubopt(reply->ia, reply->lease, MDL);
		if (status != ISC_R_SUCCESS) {
			log_fatal("reply_process_is_prefixed: Unable to "
				  "attach prefix to new IA_PD: %s",
				  isc_result_totext(status));
		}

		/*
		 * If this is a new prefix, make sure it is attached somewhere.
		 */
		if (reply->lease->ia == NULL) {
			ia_reference(&reply->lease->ia, reply->ia, MDL);
		}
	}

	/* Bring a copy of the relevant options into the IA_PD scope. */
	execute_statements_in_scope(NULL, reply->packet, NULL, NULL,
				    reply->packet->options, reply->reply_ia,
				    scope, group, root_group);

	/*
	 * And bring in host record configuration, if any, but not to overlap
	 * the previous group or its common enclosers.
	 */
	if (reply->host != NULL)
		execute_statements_in_scope(NULL, reply->packet, NULL, NULL,
					    reply->packet->options,
					    reply->reply_ia, scope,
					    reply->host->group, group);

      cleanup:
	if (data.data != NULL)
		data_string_forget(&data, MDL);

	if (status == ISC_R_SUCCESS)
		reply->client_resources++;

	return status;
}

/* Simply send an IAPREFIX within the IA_PD scope as described. */
static isc_result_t
reply_process_send_prefix(struct reply_state *reply,
			  struct iaddrcidrnet *pref) {
	isc_result_t status = ISC_R_SUCCESS;
	struct data_string data;

	memset(&data, 0, sizeof(data));

	/* Now append the prefix. */
	data.len = IAPREFIX_OFFSET;
	if (!buffer_allocate(&data.buffer, data.len, MDL)) {
		log_error("reply_process_send_prefix: out of memory"
			  "allocating new IAPREFIX buffer.");
		status = ISC_R_NOMEMORY;
		goto cleanup;
	}
	data.data = data.buffer->data;

	putULong(data.buffer->data, reply->send_prefer);
	putULong(data.buffer->data + 4, reply->send_valid);
	data.buffer->data[8] = pref->bits;
	memcpy(data.buffer->data + 9, pref->lo_addr.iabuf, 16);

	if (!append_option_buffer(&dhcpv6_universe, reply->reply_ia,
				  data.buffer, data.buffer->data,
				  data.len, D6O_IAPREFIX, 0)) {
		log_error("reply_process_send_prefix: unable "
			  "to save IAPREFIX option");
		status = ISC_R_FAILURE;
		goto cleanup;
	}

	reply->resources_included = ISC_TRUE;

      cleanup:
	if (data.data != NULL)
		data_string_forget(&data, MDL);

	return status;
}

/* Choose the better of two prefixes. */
static struct iasubopt *
prefix_compare(struct reply_state *reply,
	       struct iasubopt *alpha, struct iasubopt *beta) {
	if (alpha == NULL)
		return beta;
	if (beta == NULL)
		return alpha;

	if (reply->preflen >= 0) {
		if ((alpha->plen == reply->preflen) &&
		    (beta->plen != reply->preflen))
			return alpha;
		if ((beta->plen == reply->preflen) &&
		    (alpha->plen != reply->preflen))
			return beta;
	}

	switch(alpha->state) {
	      case FTS_ACTIVE:
		switch(beta->state) {
		      case FTS_ACTIVE:
			/* Choose the prefix with the longest lifetime (most
			 * likely the most recently allocated).
			 */
			if (alpha->hard_lifetime_end_time < 
			    beta->hard_lifetime_end_time)
				return beta;
			else
				return alpha;

		      case FTS_EXPIRED:
		      case FTS_ABANDONED:
			return alpha;

		      default:
			log_fatal("Impossible condition at %s:%d.", MDL);
		}
		break;

	      case FTS_EXPIRED:
		switch (beta->state) {
		      case FTS_ACTIVE:
			return beta;

		      case FTS_EXPIRED:
			/* Choose the most recently expired prefix. */
			if (alpha->hard_lifetime_end_time <
			    beta->hard_lifetime_end_time)
				return beta;
			else if ((alpha->hard_lifetime_end_time ==
				  beta->hard_lifetime_end_time) &&
				 (alpha->soft_lifetime_end_time <
				  beta->soft_lifetime_end_time))
				return beta;
			else
				return alpha;

		      case FTS_ABANDONED:
			return alpha;

		      default:
			log_fatal("Impossible condition at %s:%d.", MDL);
		}
		break;

	      case FTS_ABANDONED:
		switch (beta->state) {
		      case FTS_ACTIVE:
		      case FTS_EXPIRED:
			return alpha;

		      case FTS_ABANDONED:
			/* Choose the prefix that was abandoned longest ago. */
			if (alpha->hard_lifetime_end_time <
			    beta->hard_lifetime_end_time)
				return alpha;

		      default:
			log_fatal("Impossible condition at %s:%d.", MDL);
		}
		break;

	      default:
		log_fatal("Impossible condition at %s:%d.", MDL);
	}

	log_fatal("Triple impossible condition at %s:%d.", MDL);
	return NULL;
}

/*
 * Solicit is how a client starts requesting addresses.
 *
 * If the client asks for rapid commit, and we support it, we will 
 * allocate the addresses and reply.
 *
 * Otherwise we will send an advertise message.
 */

static void
dhcpv6_solicit(struct data_string *reply_ret, struct packet *packet) {
	struct data_string client_id;

	/* 
	 * Validate our input.
	 */
	if (!valid_client_msg(packet, &client_id)) {
		return;
	}

	lease_to_client(reply_ret, packet, &client_id, NULL);

	/*
	 * Clean up.
	 */
	data_string_forget(&client_id, MDL);
}

/*
 * Request is how a client actually requests addresses.
 *
 * Very similar to Solicit handling, except the server DUID is required.
 */

/* TODO: reject unicast messages, unless we set unicast option */
static void
dhcpv6_request(struct data_string *reply_ret, struct packet *packet) {
	struct data_string client_id;
	struct data_string server_id;

	/*
	 * Validate our input.
	 */
	if (!valid_client_resp(packet, &client_id, &server_id)) {
		return;
	}

	/*
	 * Issue our lease.
	 */
	lease_to_client(reply_ret, packet, &client_id, &server_id);

	/*
	 * Cleanup.
	 */
	data_string_forget(&client_id, MDL);
	data_string_forget(&server_id, MDL);
}

/* Find a DHCPv6 packet's shared network from hints in the packet.
 */
static isc_result_t
shared_network_from_packet6(struct shared_network **shared,
			    struct packet *packet)
{
	const struct packet *chk_packet;
	const struct in6_addr *link_addr, *first_link_addr;
	struct iaddr tmp_addr;
	struct subnet *subnet;
	isc_result_t status;

	if ((shared == NULL) || (*shared != NULL) || (packet == NULL))
		return DHCP_R_INVALIDARG;

	/*
	 * First, find the link address where the packet from the client
	 * first appeared (if this packet was relayed).
	 */
	first_link_addr = NULL;
	chk_packet = packet->dhcpv6_container_packet;
	while (chk_packet != NULL) {
		link_addr = &chk_packet->dhcpv6_link_address;
		if (!IN6_IS_ADDR_UNSPECIFIED(link_addr) &&
		    !IN6_IS_ADDR_LINKLOCAL(link_addr)) {
			first_link_addr = link_addr;
			break;
		}
		chk_packet = chk_packet->dhcpv6_container_packet;
	}

	/*
	 * If there is a relayed link address, find the subnet associated
	 * with that, and use that to get the appropriate
	 * shared_network.
	 */
	if (first_link_addr != NULL) {
		tmp_addr.len = sizeof(*first_link_addr);
		memcpy(tmp_addr.iabuf,
		       first_link_addr, sizeof(*first_link_addr));
		subnet = NULL;
		if (!find_subnet(&subnet, tmp_addr, MDL)) {
			log_debug("No subnet found for link-address %s.",
				  piaddr(tmp_addr));
			return ISC_R_NOTFOUND;
		}
		status = shared_network_reference(shared,
						  subnet->shared_network, MDL);
		subnet_dereference(&subnet, MDL);

	/*
	 * If there is no link address, we will use the interface
	 * that this packet came in on to pick the shared_network.
	 */
	} else if (packet->interface != NULL) {
		status = shared_network_reference(shared,
					 packet->interface->shared_network,
					 MDL);
                if (packet->dhcpv6_container_packet != NULL) {
			log_info("[L2 Relay] No link address in relay packet "
				 "assuming L2 relay and using receiving "
				 "interface");
                }

	} else {
		/*
		 * We shouldn't be able to get here but if there is no link
		 * address and no interface we don't know where to get the
		 * pool from log an error and return an error.
		 */
		log_error("No interface and no link address " 
			  "can't determine pool");
		status = DHCP_R_INVALIDARG;
	}

	return status;
}

/*
 * When a client thinks it might be on a new link, it sends a 
 * Confirm message.
 *
 * From RFC3315 section 18.2.2:
 *
 *   When the server receives a Confirm message, the server determines
 *   whether the addresses in the Confirm message are appropriate for the
 *   link to which the client is attached.  If all of the addresses in the
 *   Confirm message pass this test, the server returns a status of
 *   Success.  If any of the addresses do not pass this test, the server
 *   returns a status of NotOnLink.  If the server is unable to perform
 *   this test (for example, the server does not have information about
 *   prefixes on the link to which the client is connected), or there were
 *   no addresses in any of the IAs sent by the client, the server MUST
 *   NOT send a reply to the client.
 */

static void
dhcpv6_confirm(struct data_string *reply_ret, struct packet *packet) {
	struct shared_network *shared;
	struct subnet *subnet;
	struct option_cache *ia, *ta, *oc;
	struct data_string cli_enc_opt_data, iaaddr, client_id, packet_oro;
	struct option_state *cli_enc_opt_state, *opt_state;
	struct iaddr cli_addr;
	int pass;
	isc_boolean_t inappropriate, has_addrs;
	char reply_data[65536];
	struct dhcpv6_packet *reply = (struct dhcpv6_packet *)reply_data;
	int reply_ofs = (int)(offsetof(struct dhcpv6_packet, options));

	/* 
	 * Basic client message validation.
	 */
	memset(&client_id, 0, sizeof(client_id));
	if (!valid_client_msg(packet, &client_id)) {
		return;
	}

	/*
	 * Do not process Confirms that do not have IA's we do not recognize.
	 */
	ia = lookup_option(&dhcpv6_universe, packet->options, D6O_IA_NA);
	ta = lookup_option(&dhcpv6_universe, packet->options, D6O_IA_TA);
	if ((ia == NULL) && (ta == NULL))
		return;

	/*
	 * IA_PD's are simply ignored.
	 */
	delete_option(&dhcpv6_universe, packet->options, D6O_IA_PD);

	/* 
	 * Bit of variable initialization.
	 */
	opt_state = cli_enc_opt_state = NULL;
	memset(&cli_enc_opt_data, 0, sizeof(cli_enc_opt_data));
	memset(&iaaddr, 0, sizeof(iaaddr));
	memset(&packet_oro, 0, sizeof(packet_oro));

	/* Determine what shared network the client is connected to.  We
	 * must not respond if we don't have any information about the
	 * network the client is on.
	 */
	shared = NULL;
	if ((shared_network_from_packet6(&shared, packet) != ISC_R_SUCCESS) ||
	    (shared == NULL))
		goto exit;

	/* If there are no recorded subnets, then we have no
	 * information about this subnet - ignore Confirms.
	 */
	subnet = shared->subnets;
	if (subnet == NULL)
		goto exit;

	/* Are the addresses in all the IA's appropriate for that link? */
	has_addrs = inappropriate = ISC_FALSE;
	pass = D6O_IA_NA;
	while(!inappropriate) {
		/* If we've reached the end of the IA_NA pass, move to the
		 * IA_TA pass.
		 */
		if ((pass == D6O_IA_NA) && (ia == NULL)) {
			pass = D6O_IA_TA;
			ia = ta;
		}

		/* If we've reached the end of all passes, we're done. */
		if (ia == NULL)
			break;

		if (((pass == D6O_IA_NA) &&
		     !get_encapsulated_IA_state(&cli_enc_opt_state,
						&cli_enc_opt_data,
						packet, ia, IA_NA_OFFSET)) ||
		    ((pass == D6O_IA_TA) &&
		     !get_encapsulated_IA_state(&cli_enc_opt_state,
						&cli_enc_opt_data,
						packet, ia, IA_TA_OFFSET))) {
			goto exit;
		}

		oc = lookup_option(&dhcpv6_universe, cli_enc_opt_state,
				   D6O_IAADDR);

		for ( ; oc != NULL ; oc = oc->next) {
			if (!evaluate_option_cache(&iaaddr, packet, NULL, NULL,
						   packet->options, NULL,
						   &global_scope, oc, MDL) ||
			    (iaaddr.len < IAADDR_OFFSET)) {
				log_error("dhcpv6_confirm: "
					  "error evaluating IAADDR.");
				goto exit;
			}

			/* Copy out the IPv6 address for processing. */
			cli_addr.len = 16;
			memcpy(cli_addr.iabuf, iaaddr.data, 16);

			data_string_forget(&iaaddr, MDL);

			/* Record that we've processed at least one address. */
			has_addrs = ISC_TRUE;

			/* Find out if any subnets cover this address. */
			for (subnet = shared->subnets ; subnet != NULL ;
			     subnet = subnet->next_sibling) {
				if (addr_eq(subnet_number(cli_addr,
							  subnet->netmask),
					    subnet->net))
					break;
			}

			/* If we reach the end of the subnet list, and no
			 * subnet matches the client address, then it must
			 * be inappropriate to the link (so far as our
			 * configuration says).  Once we've found one
			 * inappropriate address, there is no reason to
			 * continue searching.
			 */
			if (subnet == NULL) {
				inappropriate = ISC_TRUE;
				break;
			}
		}

		option_state_dereference(&cli_enc_opt_state, MDL);
		data_string_forget(&cli_enc_opt_data, MDL);

		/* Advance to the next IA_*. */
		ia = ia->next;
	}

	/* If the client supplied no addresses, do not reply. */
	if (!has_addrs)
		goto exit;

	/* 
	 * Set up reply.
	 */
	if (!start_reply(packet, &client_id, NULL, &opt_state, reply)) {
		goto exit;
	}

	/* 
	 * Set our status.
	 */
	if (inappropriate) {
		if (!set_status_code(STATUS_NotOnLink, 
				     "Some of the addresses are not on link.",
				     opt_state)) {
			goto exit;
		}
	} else {
		if (!set_status_code(STATUS_Success, 
				     "All addresses still on link.",
				     opt_state)) {
			goto exit;
		}
	}

	/* 
	 * Only one option: add it.
	 */
	reply_ofs += store_options6(reply_data+reply_ofs,
				    sizeof(reply_data)-reply_ofs, 
				    opt_state, packet,
				    required_opts, &packet_oro);

	/* 
	 * Return our reply to the caller.
	 */
	reply_ret->len = reply_ofs;
	reply_ret->buffer = NULL;
	if (!buffer_allocate(&reply_ret->buffer, reply_ofs, MDL)) {
		log_fatal("No memory to store reply.");
	}
	reply_ret->data = reply_ret->buffer->data;
	memcpy(reply_ret->buffer->data, reply, reply_ofs);

exit:
	/* Cleanup any stale data strings. */
	if (cli_enc_opt_data.buffer != NULL)
		data_string_forget(&cli_enc_opt_data, MDL);
	if (iaaddr.buffer != NULL)
		data_string_forget(&iaaddr, MDL);
	if (client_id.buffer != NULL)
		data_string_forget(&client_id, MDL);
	if (packet_oro.buffer != NULL)
		data_string_forget(&packet_oro, MDL);

	/* Release any stale option states. */
	if (cli_enc_opt_state != NULL)
		option_state_dereference(&cli_enc_opt_state, MDL);
	if (opt_state != NULL)
		option_state_dereference(&opt_state, MDL);
}

/*
 * Renew is when a client wants to extend its lease/prefix, at time T1.
 *
 * We handle this the same as if the client wants a new lease/prefix,
 * except for the error code of when addresses don't match.
 */

/* TODO: reject unicast messages, unless we set unicast option */
static void
dhcpv6_renew(struct data_string *reply, struct packet *packet) {
	struct data_string client_id;
	struct data_string server_id;

	/* 
	 * Validate the request.
	 */
	if (!valid_client_resp(packet, &client_id, &server_id)) {
		return;
	}

	/*
	 * Renew our lease.
	 */
	lease_to_client(reply, packet, &client_id, &server_id);

	/*
	 * Cleanup.
	 */
	data_string_forget(&server_id, MDL);
	data_string_forget(&client_id, MDL);
}

/*
 * Rebind is when a client wants to extend its lease, at time T2.
 *
 * We handle this the same as if the client wants a new lease, except
 * for the error code of when addresses don't match.
 */

static void
dhcpv6_rebind(struct data_string *reply, struct packet *packet) {
	struct data_string client_id;

	if (!valid_client_msg(packet, &client_id)) {
		return;
	}

	lease_to_client(reply, packet, &client_id, NULL);

	data_string_forget(&client_id, MDL);
}

static void
ia_na_match_decline(const struct data_string *client_id,
		    const struct data_string *iaaddr,
		    struct iasubopt *lease)
{
	char tmp_addr[INET6_ADDRSTRLEN];

	log_error("Client %s reports address %s is "
		  "already in use by another host!",
		  print_hex_1(client_id->len, client_id->data, 60),
		  inet_ntop(AF_INET6, iaaddr->data, 
		  	    tmp_addr, sizeof(tmp_addr)));
	if (lease != NULL) {
		decline_lease6(lease->ipv6_pool, lease);
		lease->ia->cltt = cur_time;
		write_ia(lease->ia);
	}
}

static void
ia_na_nomatch_decline(const struct data_string *client_id,
		      const struct data_string *iaaddr,
		      u_int32_t *ia_na_id,
		      struct packet *packet,
		      char *reply_data,
		      int *reply_ofs,
		      int reply_len)
{
	char tmp_addr[INET6_ADDRSTRLEN];
	struct option_state *host_opt_state;
	int len;

	log_info("Client %s declines address %s, which is not offered to it.",
		 print_hex_1(client_id->len, client_id->data, 60),
		 inet_ntop(AF_INET6, iaaddr->data, tmp_addr, sizeof(tmp_addr)));

	/*
	 * Create state for this IA_NA.
	 */
	host_opt_state = NULL;
	if (!option_state_allocate(&host_opt_state, MDL)) {
		log_error("ia_na_nomatch_decline: out of memory "
			  "allocating option_state.");
		goto exit;
	}

	if (!set_status_code(STATUS_NoBinding, "Decline for unknown address.",
			     host_opt_state)) {
		goto exit;
	}

	/*
	 * Insure we have enough space
	 */
	if (reply_len < (*reply_ofs + 16)) {
		log_error("ia_na_nomatch_decline: "
			  "out of space for reply packet.");
		goto exit;
	}

	/*
	 * Put our status code into the reply packet.
	 */
	len = store_options6(reply_data+(*reply_ofs)+16,
			     reply_len-(*reply_ofs)-16,
			     host_opt_state, packet,
			     required_opts_STATUS_CODE, NULL);

	/*
	 * Store the non-encapsulated option data for this 
	 * IA_NA into our reply packet. Defined in RFC 3315, 
	 * section 22.4.  
	 */
	/* option number */
	putUShort((unsigned char *)reply_data+(*reply_ofs), D6O_IA_NA);
	/* option length */
	putUShort((unsigned char *)reply_data+(*reply_ofs)+2, len + 12);
	/* IA_NA, copied from the client */
	memcpy(reply_data+(*reply_ofs)+4, ia_na_id, 4);
	/* t1 and t2, odd that we need them, but here it is */
	putULong((unsigned char *)reply_data+(*reply_ofs)+8, 0);
	putULong((unsigned char *)reply_data+(*reply_ofs)+12, 0);

	/*
	 * Get ready for next IA_NA.
	 */
	*reply_ofs += (len + 16);

exit:
	option_state_dereference(&host_opt_state, MDL);
}

static void
iterate_over_ia_na(struct data_string *reply_ret, 
		   struct packet *packet,
		   const struct data_string *client_id,
		   const struct data_string *server_id,
		   const char *packet_type,
		   void (*ia_na_match)(),
		   void (*ia_na_nomatch)())
{
	struct option_state *opt_state;
	struct host_decl *packet_host;
	struct option_cache *ia;
	struct option_cache *oc;
	/* cli_enc_... variables come from the IA_NA/IA_TA options */
	struct data_string cli_enc_opt_data;
	struct option_state *cli_enc_opt_state;
	struct host_decl *host;
	struct option_state *host_opt_state;
	struct data_string iaaddr;
	struct data_string fixed_addr;
	int iaaddr_is_found;
	char reply_data[65536];
	struct dhcpv6_packet *reply = (struct dhcpv6_packet *)reply_data;
	int reply_ofs = (int)(offsetof(struct dhcpv6_packet, options));
	char status_msg[32];
	struct iasubopt *lease;
	struct ia_xx *existing_ia_na;
	int i;
	struct data_string key;
	u_int32_t iaid;

	/*
	 * Initialize to empty values, in case we have to exit early.
	 */
	opt_state = NULL;
	memset(&cli_enc_opt_data, 0, sizeof(cli_enc_opt_data));
	cli_enc_opt_state = NULL;
	memset(&iaaddr, 0, sizeof(iaaddr));
	memset(&fixed_addr, 0, sizeof(fixed_addr));
	host_opt_state = NULL;
	lease = NULL;

	/* 
	 * Find the host record that matches from the packet, if any.
	 */
	packet_host = NULL;
	if (!find_hosts_by_uid(&packet_host, 
			       client_id->data, client_id->len, MDL)) {
		packet_host = NULL;
		/* 
		 * Note: In general, we don't expect a client to provide
		 *       enough information to match by option for these
		 *       types of messages, but if we don't have a UID
		 *       match we can check anyway.
		 */
		if (!find_hosts_by_option(&packet_host, 
					  packet, packet->options, MDL)) {
			packet_host = NULL;

			if (!find_hosts_by_duid_chaddr(&packet_host,
						       client_id))
				packet_host = NULL;
		}
	}

	/* 
	 * Set our reply information.
	 */
	reply->msg_type = DHCPV6_REPLY;
	memcpy(reply->transaction_id, packet->dhcpv6_transaction_id, 
	       sizeof(reply->transaction_id));

	/*
	 * Build our option state for reply.
	 */
	opt_state = NULL;
	if (!option_state_allocate(&opt_state, MDL)) {
		log_error("iterate_over_ia_na: no memory for option_state.");
		goto exit;
	}
	execute_statements_in_scope(NULL, packet, NULL, NULL, 
				    packet->options, opt_state, 
				    &global_scope, root_group, NULL);

	/* 
	 * RFC 3315, section 18.2.7 tells us which options to include.
	 */
	oc = lookup_option(&dhcpv6_universe, opt_state, D6O_SERVERID);
	if (oc == NULL) {
		if (!save_option_buffer(&dhcpv6_universe, opt_state, NULL, 
					(unsigned char *)server_duid.data, 
					server_duid.len, D6O_SERVERID, 0)) {
			log_error("iterate_over_ia_na: "
				  "error saving server identifier.");
			goto exit;
		}
	}

	if (!save_option_buffer(&dhcpv6_universe, opt_state, 
				client_id->buffer, 
				(unsigned char *)client_id->data,
				client_id->len, 
				D6O_CLIENTID, 0)) {
		log_error("iterate_over_ia_na: "
			  "error saving client identifier.");
		goto exit;
	}

	snprintf(status_msg, sizeof(status_msg), "%s received.", packet_type);
	if (!set_status_code(STATUS_Success, status_msg, opt_state)) {
		goto exit;
	}

	/* 
	 * Add our options that are not associated with any IA_NA or IA_TA. 
	 */
	reply_ofs += store_options6(reply_data+reply_ofs,
				    sizeof(reply_data)-reply_ofs, 
				    opt_state, packet,
				    required_opts, NULL);

	/*
	 * Loop through the IA_NA reported by the client, and deal with
	 * addresses reported as already in use.
	 */
	for (ia = lookup_option(&dhcpv6_universe, packet->options, D6O_IA_NA);
	     ia != NULL; ia = ia->next) {
	     	iaaddr_is_found = 0;

		if (!get_encapsulated_IA_state(&cli_enc_opt_state,
					       &cli_enc_opt_data,
					       packet, ia, IA_NA_OFFSET)) {
			goto exit;
		}

		iaid = getULong(cli_enc_opt_data.data);

		/* 
		 * XXX: It is possible that we can get multiple addresses
		 *      sent by the client. We don't send multiple 
		 *      addresses, so this indicates a client error. 
		 *      We should check for multiple IAADDR options, log
		 *      if found, and set as an error.
		 */
		oc = lookup_option(&dhcpv6_universe, cli_enc_opt_state, 
				   D6O_IAADDR);
		if (oc == NULL) {
			/* no address given for this IA, ignore */
			option_state_dereference(&cli_enc_opt_state, MDL);
			data_string_forget(&cli_enc_opt_data, MDL);
			continue;
		}

		memset(&iaaddr, 0, sizeof(iaaddr));
		if (!evaluate_option_cache(&iaaddr, packet, NULL, NULL, 
					   packet->options, NULL,
					   &global_scope, oc, MDL)) {
			log_error("iterate_over_ia_na: "
				  "error evaluating IAADDR.");
			goto exit;
		}

		/* 
		 * Now we need to figure out which host record matches
		 * this IA_NA and IAADDR (encapsulated option contents
		 * matching a host record by option).
		 *
		 * XXX: We don't currently track IA_NA separately, but
		 *      we will need to do this!
		 */
		host = NULL;
		if (!find_hosts_by_option(&host, packet, 
					  cli_enc_opt_state, MDL)) { 
			if (packet_host != NULL) {
				host = packet_host;
			} else {
				host = NULL;
			}
		}
		while (host != NULL) {
			if (host->fixed_addr != NULL) {
				if (!evaluate_option_cache(&fixed_addr, NULL, 
							   NULL, NULL, NULL, 
							   NULL, &global_scope,
							   host->fixed_addr, 
							   MDL)) {
					log_error("iterate_over_ia_na: error "
						  "evaluating host address.");
					goto exit;
				}
				if ((iaaddr.len >= 16) &&
				    !memcmp(fixed_addr.data, iaaddr.data, 16)) {
					data_string_forget(&fixed_addr, MDL);
					break;
				}
				data_string_forget(&fixed_addr, MDL);
			}
			host = host->n_ipaddr;
		}

		if ((host == NULL) && (iaaddr.len >= IAADDR_OFFSET)) {
			/*
			 * Find existing IA_NA.
			 */
			if (ia_make_key(&key, iaid, 
					(char *)client_id->data,
					client_id->len, 
					MDL) != ISC_R_SUCCESS) {
				log_fatal("iterate_over_ia_na: no memory for "
					  "key.");
			}

			existing_ia_na = NULL;
			if (ia_hash_lookup(&existing_ia_na, ia_na_active, 
					   (unsigned char *)key.data, 
					   key.len, MDL)) {
				/* 
				 * Make sure this address is in the IA_NA.
				 */
				for (i=0; i<existing_ia_na->num_iasubopt; i++) {
					struct iasubopt *tmp;
					struct in6_addr *in6_addr;

					tmp = existing_ia_na->iasubopt[i];
					in6_addr = &tmp->addr;
					if (memcmp(in6_addr, 
						   iaaddr.data, 16) == 0) {
						iasubopt_reference(&lease,
								   tmp, MDL);
						break;
					}
				}
			}

			data_string_forget(&key, MDL);
		}

		if ((host != NULL) || (lease != NULL)) {
			ia_na_match(client_id, &iaaddr, lease);
		} else {
			ia_na_nomatch(client_id, &iaaddr, 
				      (u_int32_t *)cli_enc_opt_data.data, 
				      packet, reply_data, &reply_ofs, 
				      sizeof(reply_data));
		}

		if (lease != NULL) {
			iasubopt_dereference(&lease, MDL);
		}

		data_string_forget(&iaaddr, MDL);
		option_state_dereference(&cli_enc_opt_state, MDL);
		data_string_forget(&cli_enc_opt_data, MDL);
	}

	/* 
	 * Return our reply to the caller.
	 */
	reply_ret->len = reply_ofs;
	reply_ret->buffer = NULL;
	if (!buffer_allocate(&reply_ret->buffer, reply_ofs, MDL)) {
		log_fatal("No memory to store reply.");
	}
	reply_ret->data = reply_ret->buffer->data;
	memcpy(reply_ret->buffer->data, reply, reply_ofs);

exit:
	if (lease != NULL) {
		iasubopt_dereference(&lease, MDL);
	}
	if (host_opt_state != NULL) {
		option_state_dereference(&host_opt_state, MDL);
	}
	if (fixed_addr.buffer != NULL) {
		data_string_forget(&fixed_addr, MDL);
	}
	if (iaaddr.buffer != NULL) {
		data_string_forget(&iaaddr, MDL);
	}
	if (cli_enc_opt_state != NULL) {
		option_state_dereference(&cli_enc_opt_state, MDL);
	}
	if (cli_enc_opt_data.buffer != NULL) {
		data_string_forget(&cli_enc_opt_data, MDL);
	}
	if (opt_state != NULL) {
		option_state_dereference(&opt_state, MDL);
	}
}

/*
 * Decline means a client has detected that something else is using an
 * address we gave it.
 *
 * Since we're only dealing with fixed leases for now, there's not
 * much we can do, other that log the occurrence.
 * 
 * When we start issuing addresses from pools, then we will have to
 * record our declined addresses and issue another. In general with
 * IPv6 there is no worry about DoS by clients exhausting space, but
 * we still need to be aware of this possibility.
 */

/* TODO: reject unicast messages, unless we set unicast option */
/* TODO: IA_TA */
static void
dhcpv6_decline(struct data_string *reply, struct packet *packet) {
	struct data_string client_id;
	struct data_string server_id;

	/* 
	 * Validate our input.
	 */
	if (!valid_client_resp(packet, &client_id, &server_id)) {
		return;
	}

	/*
	 * Undefined for IA_PD.
	 */
	delete_option(&dhcpv6_universe, packet->options, D6O_IA_PD);

	/*
	 * And operate on each IA_NA in this packet.
	 */
	iterate_over_ia_na(reply, packet, &client_id, &server_id, "Decline", 
			   ia_na_match_decline, ia_na_nomatch_decline);

	data_string_forget(&server_id, MDL);
	data_string_forget(&client_id, MDL);
}

static void
ia_na_match_release(const struct data_string *client_id,
		    const struct data_string *iaaddr,
		    struct iasubopt *lease)
{
	char tmp_addr[INET6_ADDRSTRLEN];

	log_info("Client %s releases address %s",
		 print_hex_1(client_id->len, client_id->data, 60),
		 inet_ntop(AF_INET6, iaaddr->data, tmp_addr, sizeof(tmp_addr)));
	if (lease != NULL) {
		release_lease6(lease->ipv6_pool, lease);
		lease->ia->cltt = cur_time;
		write_ia(lease->ia);
	}
}

static void
ia_na_nomatch_release(const struct data_string *client_id,
		      const struct data_string *iaaddr,
		      u_int32_t *ia_na_id,
		      struct packet *packet,
		      char *reply_data,
		      int *reply_ofs,
		      int reply_len)
{
	char tmp_addr[INET6_ADDRSTRLEN];
	struct option_state *host_opt_state;
	int len;

	log_info("Client %s releases address %s, which is not leased to it.",
		 print_hex_1(client_id->len, client_id->data, 60),
		 inet_ntop(AF_INET6, iaaddr->data, tmp_addr, sizeof(tmp_addr)));

	/*
	 * Create state for this IA_NA.
	 */
	host_opt_state = NULL;
	if (!option_state_allocate(&host_opt_state, MDL)) {
		log_error("ia_na_nomatch_release: out of memory "
			  "allocating option_state.");
		goto exit;
	}

	if (!set_status_code(STATUS_NoBinding, 
			     "Release for non-leased address.",
			     host_opt_state)) {
		goto exit;
	}

	/*
	 * Insure we have enough space
	 */
	if (reply_len < (*reply_ofs + 16)) {
		log_error("ia_na_nomatch_release: "
			  "out of space for reply packet.");
		goto exit;
	}

	/*
	 * Put our status code into the reply packet.
	 */
	len = store_options6(reply_data+(*reply_ofs)+16,
			     reply_len-(*reply_ofs)-16,
			     host_opt_state, packet,
			     required_opts_STATUS_CODE, NULL);

	/*
	 * Store the non-encapsulated option data for this 
	 * IA_NA into our reply packet. Defined in RFC 3315, 
	 * section 22.4.  
	 */
	/* option number */
	putUShort((unsigned char *)reply_data+(*reply_ofs), D6O_IA_NA);
	/* option length */
	putUShort((unsigned char *)reply_data+(*reply_ofs)+2, len + 12);
	/* IA_NA, copied from the client */
	memcpy(reply_data+(*reply_ofs)+4, ia_na_id, 4);
	/* t1 and t2, odd that we need them, but here it is */
	putULong((unsigned char *)reply_data+(*reply_ofs)+8, 0);
	putULong((unsigned char *)reply_data+(*reply_ofs)+12, 0);

	/*
	 * Get ready for next IA_NA.
	 */
	*reply_ofs += (len + 16);

exit:
	option_state_dereference(&host_opt_state, MDL);
}

static void
ia_pd_match_release(const struct data_string *client_id,
		    const struct data_string *iapref,
		    struct iasubopt *prefix)
{
	char tmp_addr[INET6_ADDRSTRLEN];

	log_info("Client %s releases prefix %s/%u",
		 print_hex_1(client_id->len, client_id->data, 60),
		 inet_ntop(AF_INET6, iapref->data + 9,
			   tmp_addr, sizeof(tmp_addr)),
		 (unsigned) getUChar(iapref->data + 8));
	if (prefix != NULL) {
		release_lease6(prefix->ipv6_pool, prefix);
		prefix->ia->cltt = cur_time;
		write_ia(prefix->ia);
	}
}

static void
ia_pd_nomatch_release(const struct data_string *client_id,
		      const struct data_string *iapref,
		      u_int32_t *ia_pd_id,
		      struct packet *packet,
		      char *reply_data,
		      int *reply_ofs,
		      int reply_len)
{
	char tmp_addr[INET6_ADDRSTRLEN];
	struct option_state *host_opt_state;
	int len;

	log_info("Client %s releases prefix %s/%u, which is not leased to it.",
		 print_hex_1(client_id->len, client_id->data, 60),
		 inet_ntop(AF_INET6, iapref->data + 9,
			   tmp_addr, sizeof(tmp_addr)),
		 (unsigned) getUChar(iapref->data + 8));

	/*
	 * Create state for this IA_PD.
	 */
	host_opt_state = NULL;
	if (!option_state_allocate(&host_opt_state, MDL)) {
		log_error("ia_pd_nomatch_release: out of memory "
			  "allocating option_state.");
		goto exit;
	}

	if (!set_status_code(STATUS_NoBinding, 
			     "Release for non-leased prefix.",
			     host_opt_state)) {
		goto exit;
	}

	/*
	 * Insure we have enough space
	 */
	if (reply_len < (*reply_ofs + 16)) {
		log_error("ia_pd_nomatch_release: "
			  "out of space for reply packet.");
		goto exit;
	}

	/*
	 * Put our status code into the reply packet.
	 */
	len = store_options6(reply_data+(*reply_ofs)+16,
			     reply_len-(*reply_ofs)-16,
			     host_opt_state, packet,
			     required_opts_STATUS_CODE, NULL);

	/*
	 * Store the non-encapsulated option data for this 
	 * IA_PD into our reply packet. Defined in RFC 3315, 
	 * section 22.4.  
	 */
	/* option number */
	putUShort((unsigned char *)reply_data+(*reply_ofs), D6O_IA_PD);
	/* option length */
	putUShort((unsigned char *)reply_data+(*reply_ofs)+2, len + 12);
	/* IA_PD, copied from the client */
	memcpy(reply_data+(*reply_ofs)+4, ia_pd_id, 4);
	/* t1 and t2, odd that we need them, but here it is */
	putULong((unsigned char *)reply_data+(*reply_ofs)+8, 0);
	putULong((unsigned char *)reply_data+(*reply_ofs)+12, 0);

	/*
	 * Get ready for next IA_PD.
	 */
	*reply_ofs += (len + 16);

exit:
	option_state_dereference(&host_opt_state, MDL);
}

static void
iterate_over_ia_pd(struct data_string *reply_ret, 
		   struct packet *packet,
		   const struct data_string *client_id,
		   const struct data_string *server_id,
		   const char *packet_type,
		   void (*ia_pd_match)(),
		   void (*ia_pd_nomatch)())
{
	struct data_string reply_new;
	int reply_len;
	struct option_state *opt_state;
	struct host_decl *packet_host;
	struct option_cache *ia;
	struct option_cache *oc;
	/* cli_enc_... variables come from the IA_PD options */
	struct data_string cli_enc_opt_data;
	struct option_state *cli_enc_opt_state;
	struct host_decl *host;
	struct option_state *host_opt_state;
	struct data_string iaprefix;
	int iaprefix_is_found;
	char reply_data[65536];
	int reply_ofs;
	struct iasubopt *prefix;
	struct ia_xx *existing_ia_pd;
	int i;
	struct data_string key;
	u_int32_t iaid;

	/*
	 * Initialize to empty values, in case we have to exit early.
	 */
	memset(&reply_new, 0, sizeof(reply_new));
	opt_state = NULL;
	memset(&cli_enc_opt_data, 0, sizeof(cli_enc_opt_data));
	cli_enc_opt_state = NULL;
	memset(&iaprefix, 0, sizeof(iaprefix));
	host_opt_state = NULL;
	prefix = NULL;

	/*
	 * Compute the available length for the reply.
	 */
	reply_len = sizeof(reply_data) - reply_ret->len;
	reply_ofs = 0;

	/* 
	 * Find the host record that matches from the packet, if any.
	 */
	packet_host = NULL;
	if (!find_hosts_by_uid(&packet_host, 
			       client_id->data, client_id->len, MDL)) {
		packet_host = NULL;
		/* 
		 * Note: In general, we don't expect a client to provide
		 *       enough information to match by option for these
		 *       types of messages, but if we don't have a UID
		 *       match we can check anyway.
		 */
		if (!find_hosts_by_option(&packet_host, 
					  packet, packet->options, MDL)) {
			packet_host = NULL;

			if (!find_hosts_by_duid_chaddr(&packet_host,
						       client_id))
				packet_host = NULL;
		}
	}

	/*
	 * Build our option state for reply.
	 */
	opt_state = NULL;
	if (!option_state_allocate(&opt_state, MDL)) {
		log_error("iterate_over_ia_pd: no memory for option_state.");
		goto exit;
	}
	execute_statements_in_scope(NULL, packet, NULL, NULL, 
				    packet->options, opt_state, 
				    &global_scope, root_group, NULL);

	/*
	 * Loop through the IA_PD reported by the client, and deal with
	 * prefixes reported as already in use.
	 */
	for (ia = lookup_option(&dhcpv6_universe, packet->options, D6O_IA_PD);
	     ia != NULL; ia = ia->next) {
	    iaprefix_is_found = 0;

	    if (!get_encapsulated_IA_state(&cli_enc_opt_state,
					   &cli_enc_opt_data,
					   packet, ia, IA_PD_OFFSET)) {
		goto exit;
	    }

	    iaid = getULong(cli_enc_opt_data.data);

	    oc = lookup_option(&dhcpv6_universe, cli_enc_opt_state, 
			       D6O_IAPREFIX);
	    if (oc == NULL) {
		/* no prefix given for this IA_PD, ignore */
		option_state_dereference(&cli_enc_opt_state, MDL);
		data_string_forget(&cli_enc_opt_data, MDL);
		continue;
	    }

	    for (; oc != NULL; oc = oc->next) {
		memset(&iaprefix, 0, sizeof(iaprefix));
		if (!evaluate_option_cache(&iaprefix, packet, NULL, NULL, 
					   packet->options, NULL,
					   &global_scope, oc, MDL)) {
			log_error("iterate_over_ia_pd: "
				  "error evaluating IAPREFIX.");
			goto exit;
		}

		/* 
		 * Now we need to figure out which host record matches
		 * this IA_PD and IAPREFIX (encapsulated option contents
		 * matching a host record by option).
		 *
		 * XXX: We don't currently track IA_PD separately, but
		 *      we will need to do this!
		 */
		host = NULL;
		if (!find_hosts_by_option(&host, packet, 
					  cli_enc_opt_state, MDL)) { 
			if (packet_host != NULL) {
				host = packet_host;
			} else {
				host = NULL;
			}
		}
		while (host != NULL) {
			if (host->fixed_prefix != NULL) {
				struct iaddrcidrnetlist *l;
				int plen = (int) getUChar(iaprefix.data + 8);

				for (l = host->fixed_prefix; l != NULL;
				     l = l->next) {
					if (plen != l->cidrnet.bits)
						continue;
					if (memcmp(iaprefix.data + 9,
						   l->cidrnet.lo_addr.iabuf,
						   16) == 0)
						break;
				}
				if ((l != NULL) && (iaprefix.len >= 17))
					break;
			}
			host = host->n_ipaddr;
		}

		if ((host == NULL) && (iaprefix.len >= IAPREFIX_OFFSET)) {
			/*
			 * Find existing IA_PD.
			 */
			if (ia_make_key(&key, iaid, 
					(char *)client_id->data,
					client_id->len, 
					MDL) != ISC_R_SUCCESS) {
				log_fatal("iterate_over_ia_pd: no memory for "
					  "key.");
			}

			existing_ia_pd = NULL;
			if (ia_hash_lookup(&existing_ia_pd, ia_pd_active, 
					   (unsigned char *)key.data, 
					   key.len, MDL)) {
				/* 
				 * Make sure this prefix is in the IA_PD.
				 */
				for (i = 0;
				     i < existing_ia_pd->num_iasubopt;
				     i++) {
					struct iasubopt *tmp;
					u_int8_t plen;

					plen = getUChar(iaprefix.data + 8);
					tmp = existing_ia_pd->iasubopt[i];
					if ((tmp->plen == plen) &&
					    (memcmp(&tmp->addr,
						    iaprefix.data + 9,
						    16) == 0)) {
						iasubopt_reference(&prefix,
								   tmp, MDL);
						break;
					}
				}
			}

			data_string_forget(&key, MDL);
		}

		if ((host != NULL) || (prefix != NULL)) {
			ia_pd_match(client_id, &iaprefix, prefix);
		} else {
			ia_pd_nomatch(client_id, &iaprefix, 
				      (u_int32_t *)cli_enc_opt_data.data, 
				      packet, reply_data, &reply_ofs, 
				      reply_len - reply_ofs);
		}

		if (prefix != NULL) {
			iasubopt_dereference(&prefix, MDL);
		}

		data_string_forget(&iaprefix, MDL);
	    }

	    option_state_dereference(&cli_enc_opt_state, MDL);
	    data_string_forget(&cli_enc_opt_data, MDL);
	}

	/* 
	 * Return our reply to the caller.
	 * The IA_NA routine has already filled at least the header.
	 */
	reply_new.len = reply_ret->len + reply_ofs;
	if (!buffer_allocate(&reply_new.buffer, reply_new.len, MDL)) {
		log_fatal("No memory to store reply.");
	}
	reply_new.data = reply_new.buffer->data;
	memcpy(reply_new.buffer->data,
	       reply_ret->buffer->data, reply_ret->len);
	memcpy(reply_new.buffer->data + reply_ret->len,
	       reply_data, reply_ofs);
	data_string_forget(reply_ret, MDL);
	data_string_copy(reply_ret, &reply_new, MDL);
	data_string_forget(&reply_new, MDL);

exit:
	if (prefix != NULL) {
		iasubopt_dereference(&prefix, MDL);
	}
	if (host_opt_state != NULL) {
		option_state_dereference(&host_opt_state, MDL);
	}
	if (iaprefix.buffer != NULL) {
		data_string_forget(&iaprefix, MDL);
	}
	if (cli_enc_opt_state != NULL) {
		option_state_dereference(&cli_enc_opt_state, MDL);
	}
	if (cli_enc_opt_data.buffer != NULL) {
		data_string_forget(&cli_enc_opt_data, MDL);
	}
	if (opt_state != NULL) {
		option_state_dereference(&opt_state, MDL);
	}
}

/*
 * Release means a client is done with the leases.
 */

/* TODO: reject unicast messages, unless we set unicast option */
static void
dhcpv6_release(struct data_string *reply, struct packet *packet) {
	struct data_string client_id;
	struct data_string server_id;

	/* 
	 * Validate our input.
	 */
	if (!valid_client_resp(packet, &client_id, &server_id)) {
		return;
	}

	/*
	 * And operate on each IA_NA in this packet.
	 */
	iterate_over_ia_na(reply, packet, &client_id, &server_id, "Release", 
			   ia_na_match_release, ia_na_nomatch_release);

	/*
	 * And operate on each IA_PD in this packet.
	 */
	iterate_over_ia_pd(reply, packet, &client_id, &server_id, "Release",
			   ia_pd_match_release, ia_pd_nomatch_release);

	data_string_forget(&server_id, MDL);
	data_string_forget(&client_id, MDL);
}

/*
 * Information-Request is used by clients who have obtained an address
 * from other means, but want configuration information from the server.
 */

static void
dhcpv6_information_request(struct data_string *reply, struct packet *packet) {
	struct data_string client_id;
	struct data_string server_id;

	/*
	 * Validate our input.
	 */
	if (!valid_client_info_req(packet, &server_id)) {
		return;
	}

	/*
	 * Get our client ID, if there is one.
	 */
	memset(&client_id, 0, sizeof(client_id));
	if (get_client_id(packet, &client_id) != ISC_R_SUCCESS) {
		data_string_forget(&client_id, MDL);
	}

	/*
	 * Use the lease_to_client() function. This will work fine, 
	 * because the valid_client_info_req() insures that we 
	 * don't have any IA that would cause us to allocate
	 * resources to the client.
	 */
	lease_to_client(reply, packet, &client_id,
			server_id.data != NULL ? &server_id : NULL);

	/*
	 * Cleanup.
	 */
	if (client_id.data != NULL) {
		data_string_forget(&client_id, MDL);
	}
	data_string_forget(&server_id, MDL);
}

/* 
 * The Relay-forw message is sent by relays. It typically contains a
 * single option, which encapsulates an entire packet.
 *
 * We need to build an encapsulated reply.
 */

/* XXX: this is very, very similar to do_packet6(), and should probably
	be combined in a clever way */
static void
dhcpv6_relay_forw(struct data_string *reply_ret, struct packet *packet) {
	struct option_cache *oc;
	struct data_string enc_opt_data;
	struct packet *enc_packet;
	unsigned char msg_type;
	const struct dhcpv6_packet *msg;
	const struct dhcpv6_relay_packet *relay;
	struct data_string enc_reply;
	char link_addr[sizeof("ffff:ffff:ffff:ffff:ffff:ffff:255.255.255.255")];
	char peer_addr[sizeof("ffff:ffff:ffff:ffff:ffff:ffff:255.255.255.255")];
	struct data_string a_opt, packet_ero;
	struct option_state *opt_state;
	static char reply_data[65536];
	struct dhcpv6_relay_packet *reply;
	int reply_ofs;

	/* 
	 * Initialize variables for early exit.
	 */
	opt_state = NULL;
	memset(&a_opt, 0, sizeof(a_opt));
	memset(&packet_ero, 0, sizeof(packet_ero));
	memset(&enc_reply, 0, sizeof(enc_reply));
	memset(&enc_opt_data, 0, sizeof(enc_opt_data));
	enc_packet = NULL;

	/*
	 * Get our encapsulated relay message.
	 */
	oc = lookup_option(&dhcpv6_universe, packet->options, D6O_RELAY_MSG);
	if (oc == NULL) {
		inet_ntop(AF_INET6, &packet->dhcpv6_link_address,
			  link_addr, sizeof(link_addr));
		inet_ntop(AF_INET6, &packet->dhcpv6_peer_address,
			  peer_addr, sizeof(peer_addr));
		log_info("Relay-forward from %s with link address=%s and "
			 "peer address=%s missing Relay Message option.",
			  piaddr(packet->client_addr), link_addr, peer_addr);
		goto exit;
	}

	if (!evaluate_option_cache(&enc_opt_data, NULL, NULL, NULL, 
				   NULL, NULL, &global_scope, oc, MDL)) {
		log_error("dhcpv6_forw_relay: error evaluating "
			  "relayed message.");
		goto exit;
	}

	if (!packet6_len_okay((char *)enc_opt_data.data, enc_opt_data.len)) {
		log_error("dhcpv6_forw_relay: encapsulated packet too short.");
		goto exit;
	}

	/*
	 * Build a packet structure from this encapsulated packet.
	 */
	enc_packet = NULL;
	if (!packet_allocate(&enc_packet, MDL)) {
		log_error("dhcpv6_forw_relay: "
			  "no memory for encapsulated packet.");
		goto exit;
	}

	if (!option_state_allocate(&enc_packet->options, MDL)) {
		log_error("dhcpv6_forw_relay: "
			  "no memory for encapsulated packet's options.");
		goto exit;
	}

	enc_packet->client_port = packet->client_port;
	enc_packet->client_addr = packet->client_addr;
	interface_reference(&enc_packet->interface, packet->interface, MDL);
	enc_packet->dhcpv6_container_packet = packet;

	msg_type = enc_opt_data.data[0];
	if ((msg_type == DHCPV6_RELAY_FORW) ||
	    (msg_type == DHCPV6_RELAY_REPL)) {
		relay = (struct dhcpv6_relay_packet *)enc_opt_data.data;
		enc_packet->dhcpv6_msg_type = relay->msg_type;

		/* relay-specific data */
		enc_packet->dhcpv6_hop_count = relay->hop_count;
		memcpy(&enc_packet->dhcpv6_link_address,
		       relay->link_address, sizeof(relay->link_address));
		memcpy(&enc_packet->dhcpv6_peer_address,
		       relay->peer_address, sizeof(relay->peer_address));

		if (!parse_option_buffer(enc_packet->options,
					 relay->options, 
					 enc_opt_data.len-sizeof(*relay),
					 &dhcpv6_universe)) {
			/* no logging here, as parse_option_buffer() logs all
			   cases where it fails */
			goto exit;
		}
	} else {
		msg = (struct dhcpv6_packet *)enc_opt_data.data;
		enc_packet->dhcpv6_msg_type = msg->msg_type;

		/* message-specific data */
		memcpy(enc_packet->dhcpv6_transaction_id,
		       msg->transaction_id,
		       sizeof(enc_packet->dhcpv6_transaction_id));

		if (!parse_option_buffer(enc_packet->options,
					 msg->options, 
					 enc_opt_data.len-sizeof(*msg),
					 &dhcpv6_universe)) {
			/* no logging here, as parse_option_buffer() logs all
			   cases where it fails */
			goto exit;
		}
	}

	/*
	 * This is recursive. It is possible to exceed maximum packet size.
	 * XXX: This will cause the packet send to fail.
	 */
	build_dhcpv6_reply(&enc_reply, enc_packet);

	/*
	 * If we got no encapsulated data, then it is discarded, and
	 * our reply-forw is also discarded.
	 */
	if (enc_reply.data == NULL) {
		goto exit;
	}

	/*
	 * Now we can use the reply_data buffer.
	 * Packet header stuff all comes from the forward message.
	 */
	reply = (struct dhcpv6_relay_packet *)reply_data;
	reply->msg_type = DHCPV6_RELAY_REPL;
	reply->hop_count = packet->dhcpv6_hop_count;
	memcpy(reply->link_address, &packet->dhcpv6_link_address,
	       sizeof(reply->link_address));
	memcpy(reply->peer_address, &packet->dhcpv6_peer_address,
	       sizeof(reply->peer_address));
	reply_ofs = (int)(offsetof(struct dhcpv6_relay_packet, options));

	/*
	 * Get the reply option state.
	 */
	opt_state = NULL;
	if (!option_state_allocate(&opt_state, MDL)) {
		log_error("dhcpv6_relay_forw: no memory for option state.");
		goto exit;
	}

	/*
	 * Append the interface-id if present.
	 */
	oc = lookup_option(&dhcpv6_universe, packet->options,
			   D6O_INTERFACE_ID);
	if (oc != NULL) {
		if (!evaluate_option_cache(&a_opt, packet,
					   NULL, NULL, 
					   packet->options, NULL,
					   &global_scope, oc, MDL)) {
			log_error("dhcpv6_relay_forw: error evaluating "
				  "Interface ID.");
			goto exit;
		}
		if (!save_option_buffer(&dhcpv6_universe, opt_state, NULL,
					(unsigned char *)a_opt.data,
					a_opt.len,
					D6O_INTERFACE_ID, 0)) {
			log_error("dhcpv6_relay_forw: error saving "
				  "Interface ID.");
			goto exit;
		}
		data_string_forget(&a_opt, MDL);
	}

	/* 
	 * Append our encapsulated stuff for caller.
	 */
	if (!save_option_buffer(&dhcpv6_universe, opt_state, NULL,
				(unsigned char *)enc_reply.data,
				enc_reply.len,
				D6O_RELAY_MSG, 0)) {
		log_error("dhcpv6_relay_forw: error saving Relay MSG.");
		goto exit;
	}

	/*
	 * Get the ERO if any.
	 */
	oc = lookup_option(&dhcpv6_universe, packet->options, D6O_ERO);
	if (oc != NULL) {
		unsigned req;
		int i;

		if (!evaluate_option_cache(&packet_ero, packet,
					   NULL, NULL,
					   packet->options, NULL,
					   &global_scope, oc, MDL) ||
			(packet_ero.len & 1)) {
			log_error("dhcpv6_relay_forw: error evaluating ERO.");
			goto exit;
		}

		/* Decode and apply the ERO. */
		for (i = 0; i < packet_ero.len; i += 2) {
			req = getUShort(packet_ero.data + i);
			/* Already in the reply? */
			oc = lookup_option(&dhcpv6_universe, opt_state, req);
			if (oc != NULL)
				continue;
			/* Get it from the packet if present. */
			oc = lookup_option(&dhcpv6_universe,
					   packet->options,
					   req);
			if (oc == NULL)
				continue;
			if (!evaluate_option_cache(&a_opt, packet,
						   NULL, NULL,
						   packet->options, NULL,
						   &global_scope, oc, MDL)) {
				log_error("dhcpv6_relay_forw: error "
					  "evaluating option %u.", req);
				goto exit;
			}
			if (!save_option_buffer(&dhcpv6_universe,
						opt_state,
						NULL,
						(unsigned char *)a_opt.data,
						a_opt.len,
						req,
						0)) {
				log_error("dhcpv6_relay_forw: error saving "
					  "option %u.", req);
				goto exit;
			}
			data_string_forget(&a_opt, MDL);
		}
	}

	reply_ofs += store_options6(reply_data + reply_ofs,
				    sizeof(reply_data) - reply_ofs,
				    opt_state, packet,
				    required_opts_agent, &packet_ero);

	/*
	 * Return our reply to the caller.
	 */
	reply_ret->len = reply_ofs;
	reply_ret->buffer = NULL;
	if (!buffer_allocate(&reply_ret->buffer, reply_ret->len, MDL)) {
		log_fatal("No memory to store reply.");
	}
	reply_ret->data = reply_ret->buffer->data;
	memcpy(reply_ret->buffer->data, reply_data, reply_ofs);

exit:
	if (opt_state != NULL)
		option_state_dereference(&opt_state, MDL);
	if (a_opt.data != NULL) {
		data_string_forget(&a_opt, MDL);
	}
	if (packet_ero.data != NULL) {
		data_string_forget(&packet_ero, MDL);
	}
	if (enc_reply.data != NULL) {
		data_string_forget(&enc_reply, MDL);
	}
	if (enc_opt_data.data != NULL) {
		data_string_forget(&enc_opt_data, MDL);
	}
	if (enc_packet != NULL) {
		packet_dereference(&enc_packet, MDL);
	}
}

static void
dhcpv6_discard(struct packet *packet) {
	/* INSIST(packet->msg_type > 0); */
	/* INSIST(packet->msg_type < dhcpv6_type_name_max); */

	log_debug("Discarding %s from %s; message type not handled by server", 
		  dhcpv6_type_names[packet->dhcpv6_msg_type],
		  piaddr(packet->client_addr));
}

static void 
build_dhcpv6_reply(struct data_string *reply, struct packet *packet) {
	memset(reply, 0, sizeof(*reply));
	switch (packet->dhcpv6_msg_type) {
		case DHCPV6_SOLICIT:
			dhcpv6_solicit(reply, packet);
			break;
		case DHCPV6_ADVERTISE:
			dhcpv6_discard(packet);
			break;
		case DHCPV6_REQUEST:
			dhcpv6_request(reply, packet);
			break;
		case DHCPV6_CONFIRM:
			dhcpv6_confirm(reply, packet);
			break;
		case DHCPV6_RENEW:
			dhcpv6_renew(reply, packet);
			break;
		case DHCPV6_REBIND:
			dhcpv6_rebind(reply, packet);
			break;
		case DHCPV6_REPLY:
			dhcpv6_discard(packet);
			break;
		case DHCPV6_RELEASE:
			dhcpv6_release(reply, packet);
			break;
		case DHCPV6_DECLINE:
			dhcpv6_decline(reply, packet);
			break;
		case DHCPV6_RECONFIGURE:
			dhcpv6_discard(packet);
			break;
		case DHCPV6_INFORMATION_REQUEST:
			dhcpv6_information_request(reply, packet);
			break;
		case DHCPV6_RELAY_FORW:
			dhcpv6_relay_forw(reply, packet);
			break;
		case DHCPV6_RELAY_REPL:
			dhcpv6_discard(packet);
			break;
		case DHCPV6_LEASEQUERY:
			dhcpv6_leasequery(reply, packet);
			break;
		case DHCPV6_LEASEQUERY_REPLY:
			dhcpv6_discard(packet);
			break;
		default:
			/* XXX: would be nice if we had "notice" level, 
				as syslog, for this */
			log_info("Discarding unknown DHCPv6 message type %d "
				 "from %s", packet->dhcpv6_msg_type, 
				 piaddr(packet->client_addr));
	}
}

static void
log_packet_in(const struct packet *packet) {
	struct data_string s;
	u_int32_t tid;
	char tmp_addr[INET6_ADDRSTRLEN];
	const void *addr;

	memset(&s, 0, sizeof(s));

	if (packet->dhcpv6_msg_type < dhcpv6_type_name_max) {
		data_string_sprintfa(&s, "%s message from %s port %d",
				     dhcpv6_type_names[packet->dhcpv6_msg_type],
				     piaddr(packet->client_addr),
				     ntohs(packet->client_port));
	} else {
		data_string_sprintfa(&s, 
				     "Unknown message type %d from %s port %d",
				     packet->dhcpv6_msg_type,
				     piaddr(packet->client_addr),
				     ntohs(packet->client_port));
	}
	if ((packet->dhcpv6_msg_type == DHCPV6_RELAY_FORW) || 
	    (packet->dhcpv6_msg_type == DHCPV6_RELAY_REPL)) {
	    	addr = &packet->dhcpv6_link_address;
	    	data_string_sprintfa(&s, ", link address %s", 
				     inet_ntop(AF_INET6, addr, 
					       tmp_addr, sizeof(tmp_addr)));
	    	addr = &packet->dhcpv6_peer_address;
	    	data_string_sprintfa(&s, ", peer address %s", 
				     inet_ntop(AF_INET6, addr, 
					       tmp_addr, sizeof(tmp_addr)));
	} else {
		tid = 0;
		memcpy(((char *)&tid)+1, packet->dhcpv6_transaction_id, 3);
		data_string_sprintfa(&s, ", transaction ID 0x%06X", tid);

/*
		oc = lookup_option(&dhcpv6_universe, packet->options, 
				   D6O_CLIENTID);
		if (oc != NULL) {
			memset(&tmp_ds, 0, sizeof(tmp_ds_));
			if (!evaluate_option_cache(&tmp_ds, packet, NULL, NULL, 
						   packet->options, NULL,
						   &global_scope, oc, MDL)) {
				log_error("Error evaluating Client Identifier");
			} else {
				data_strint_sprintf(&s, ", client ID %s",

				data_string_forget(&tmp_ds, MDL);
			}
		}
*/

	}
	log_info("%s", s.data);

	data_string_forget(&s, MDL);
}

void 
dhcpv6(struct packet *packet) {
	struct data_string reply;
	struct sockaddr_in6 to_addr;
	int send_ret;

	/* 
	 * Log a message that we received this packet.
	 */
	log_packet_in(packet); 

	/*
	 * Build our reply packet.
	 */
	build_dhcpv6_reply(&reply, packet);

	if (reply.data != NULL) {
		/* 
		 * Send our reply, if we have one.
		 */
		memset(&to_addr, 0, sizeof(to_addr));
		to_addr.sin6_family = AF_INET6;
		if ((packet->dhcpv6_msg_type == DHCPV6_RELAY_FORW) || 
		    (packet->dhcpv6_msg_type == DHCPV6_RELAY_REPL)) {
			to_addr.sin6_port = local_port;
		} else {
			to_addr.sin6_port = remote_port;
		}
/* For testing, we reply to the sending port, so we don't need a root client */
		to_addr.sin6_port = packet->client_port;
		memcpy(&to_addr.sin6_addr, packet->client_addr.iabuf, 
		       sizeof(to_addr.sin6_addr));

		log_info("Sending %s to %s port %d", 
			 dhcpv6_type_names[reply.data[0]],
			 piaddr(packet->client_addr),
			 ntohs(to_addr.sin6_port));

		send_ret = send_packet6(packet->interface, 
					reply.data, reply.len, &to_addr);
		if (send_ret != reply.len) {
			log_error("dhcpv6: send_packet6() sent %d of %d bytes",
				  send_ret, reply.len);
		}
		data_string_forget(&reply, MDL);
	}
}

static void
seek_shared_host(struct host_decl **hp, struct shared_network *shared) {
	struct host_decl *nofixed = NULL;
	struct host_decl *seek, *hold = NULL;

	/*
	 * Seek forward through fixed addresses for the right link.
	 *
	 * Note: how to do this for fixed prefixes???
	 */
	host_reference(&hold, *hp, MDL);
	host_dereference(hp, MDL);
	seek = hold;
	while (seek != NULL) {
		if (seek->fixed_addr == NULL)
			nofixed = seek;
		else if (fixed_matches_shared(seek, shared))
			break;

		seek = seek->n_ipaddr;
	}

	if ((seek == NULL) && (nofixed != NULL))
		seek = nofixed;

	if (seek != NULL)
		host_reference(hp, seek, MDL);
}

static isc_boolean_t
fixed_matches_shared(struct host_decl *host, struct shared_network *shared) {
	struct subnet *subnet;
	struct data_string addr;
	isc_boolean_t matched;
	struct iaddr fixed;

	if (host->fixed_addr == NULL)
		return ISC_FALSE;

	memset(&addr, 0, sizeof(addr));
	if (!evaluate_option_cache(&addr, NULL, NULL, NULL, NULL, NULL,
				   &global_scope, host->fixed_addr, MDL))
		return ISC_FALSE;

	if (addr.len < 16) {
		data_string_forget(&addr, MDL);
		return ISC_FALSE;
	}

	fixed.len = 16;
	memcpy(fixed.iabuf, addr.data, 16);

	matched = ISC_FALSE;
	for (subnet = shared->subnets ; subnet != NULL ;
	     subnet = subnet->next_sibling) {
		if (addr_eq(subnet_number(fixed, subnet->netmask),
			    subnet->net)) {
			matched = ISC_TRUE;
			break;
		}
	}

	data_string_forget(&addr, MDL);
	return matched;
}

/*
 * find_host_by_duid_chaddr() synthesizes a DHCPv4-like 'hardware'
 * parameter from a DHCPv6 supplied DUID (client-identifier option),
 * and may seek to use client or relay supplied hardware addresses.
 */
static int
find_hosts_by_duid_chaddr(struct host_decl **host,
			  const struct data_string *client_id) {
	static int once_htype;
	int htype, hlen;
	const unsigned char *chaddr;

	/*
	 * The DUID-LL and DUID-LLT must have a 2-byte DUID type and 2-byte
	 * htype.
	 */
	if (client_id->len < 4)
		return 0;

	/*
	 * The third and fourth octets of the DUID-LL and DUID-LLT
	 * is the hardware type, but in 16 bits.
	 */
	htype = getUShort(client_id->data + 2);
	hlen = 0;
	chaddr = NULL;

	/* The first two octets of the DUID identify the type. */
	switch(getUShort(client_id->data)) {
	      case DUID_LLT:
		if (client_id->len > 8) {
			hlen = client_id->len - 8;
			chaddr = client_id->data + 8;
		}
		break;

	      case DUID_LL:
		/*
		 * Note that client_id->len must be greater than or equal
		 * to four to get to this point in the function.
		 */
		hlen = client_id->len - 4;
		chaddr = client_id->data + 4;
		break;

	      default:
		break;
	}

	if (hlen == 0)
		return 0;

	/*
	 * XXX: DHCPv6 gives a 16-bit field for the htype.  DHCPv4 gives an
	 * 8-bit field.  To change the semantics of the generic 'hardware'
	 * structure, we would have to adjust many DHCPv4 sources (from
	 * interface to DHCPv4 lease code), and we would have to update the
	 * 'hardware' config directive (probably being reverse compatible and
	 * providing a new upgrade/replacement primitive).  This is a little
	 * too much to change for now.  Hopefully we will revisit this before
	 * hardware types exceeding 8 bits are assigned.
	 */
	if ((htype & 0xFF00) && !once_htype) {
		once_htype = 1;
		log_error("Attention: At least one client advertises a "
			  "hardware type of %d, which exceeds the software "
			  "limitation of 255.", htype);
	}

	return find_hosts_by_haddr(host, htype, chaddr, hlen, MDL);
}

#endif /* DHCPv6 */

