/* mdb.c

   Server-specific in-memory database support. */

/*
 * Copyright (C) 2004-2022 Internet Systems Consortium, Inc. ("ISC")
 * Copyright (c) 1996-2003 by Internet Software Consortium
 *
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
 *
 * THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES
 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR
 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
 * OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 *
 *   Internet Systems Consortium, Inc.
 *   PO Box 360
 *   Newmarket, NH 03857 USA
 *   <info@isc.org>
 *   https://www.isc.org/
 *
 */

#include "dhcpd.h"
#include "omapip/hash.h"

struct subnet *subnets;
struct shared_network *shared_networks;
host_hash_t *host_hw_addr_hash;
host_hash_t *host_uid_hash;
host_hash_t *host_name_hash;
lease_id_hash_t *lease_uid_hash;
lease_ip_hash_t *lease_ip_addr_hash;
lease_id_hash_t *lease_hw_addr_hash;

/*
 * We allow users to specify any option as a host identifier.
 *
 * Any host is uniquely identified by the combination of
 * option type & option data.
 *
 * We expect people will only use a few types of options as host
 * identifier. Because of this, we store a list with an entry for
 * each option type. Each of these has a hash table, which contains
 * hash of the option data.
 *
 * For v6 we also include a relay count - this specifies which
 * relay to check for the requested option.  As each different
 * value of relays creates a new instance admins should use the
 * same value across each option for all host-identifers.
 * A value of 0 indicates that we aren't doing relay options
 * and should simply look in the current option list.
 */
typedef struct host_id_info {
	struct option *option;
	host_hash_t *values_hash;
	int relays;
	struct host_id_info *next;
} host_id_info_t;

static host_id_info_t *host_id_info = NULL;

int numclasseswritten;

extern omapi_object_type_t *dhcp_type_host;

isc_result_t enter_class(cd, dynamicp, commit)
	struct class *cd;
	int dynamicp;
	int commit;
{
	if (!collections -> classes) {
		/* A subclass with no parent is invalid. */
		if (cd->name == NULL)
			return DHCP_R_INVALIDARG;

		class_reference (&collections -> classes, cd, MDL);
	} else if (cd->name != NULL) {	/* regular class */
		struct class *c = 0;

		if (find_class(&c, cd->name, MDL) != ISC_R_NOTFOUND) {
			class_dereference(&c, MDL);
			return ISC_R_EXISTS;
		}

		/* Find the tail. */
		for (c = collections -> classes;
		     c -> nic; c = c -> nic)
			/* nothing */ ;
		class_reference (&c -> nic, cd, MDL);
	}

	if (dynamicp && commit) {
		const char *name = cd->name;

		if (name == NULL) {
			name = cd->superclass->name;
		}

		write_named_billing_class ((const unsigned char *)name, 0, cd);
		if (!commit_leases ())
			return ISC_R_IOERROR;
	}

	return ISC_R_SUCCESS;
}


/* Variable to check if we're starting the server.  The server will init as
 * starting - but just to be safe start out as false to avoid triggering new
 * special-case code
 * XXX: There is actually a server_startup state...which is never entered...
 */
#define SS_NOSYNC	1
#define SS_QFOLLOW	2
static int server_starting = 0;

static int find_uid_statement (struct executable_statement *esp,
			       void *vp, int condp)
{
	struct executable_statement **evp = vp;

	if (esp -> op == supersede_option_statement &&
	    esp -> data.option &&
	    (esp -> data.option -> option -> universe ==
	     &dhcp_universe) &&
	    (esp -> data.option -> option -> code ==
	     DHO_DHCP_CLIENT_IDENTIFIER)) {
		if (condp) {
			log_error ("dhcp client identifier may not be %s",
				   "specified conditionally.");
		} else if (!(*evp)) {
			executable_statement_reference (evp, esp, MDL);
			return 1;
		} else {
			log_error ("only one dhcp client identifier may be %s",
				   "specified");
		}
	}
	return 0;
}


static host_id_info_t *
find_host_id_info(unsigned int option_code, int relays) {
	host_id_info_t *p;

	for (p = host_id_info; p != NULL; p = p->next) {
		if ((p->option->code == option_code) &&
		    (p->relays == relays)) {
			break;
		}
	}
	return p;
}

/* Debugging code */
#if 0
isc_result_t
print_host(const void *name, unsigned len, void *value) {
	struct host_decl *h;
	printf("--------------\n");
	printf("name:'%s'\n", print_hex_1(len, name, 60));
	printf("len:%d\n", len);
	h = (struct host_decl *)value;
	printf("host @%p is '%s'\n", h, h->name);
	return ISC_R_SUCCESS;
}

void
hash_print_hosts(struct hash_table *h) {
	hash_foreach(h, print_host);
	printf("--------------\n");
}
#endif /* 0 */

void
change_host_uid(struct host_decl *host, const char *uid, int len) {
	/* XXX: should consolidate this type of code throughout */
	if (host_uid_hash == NULL) {
		if (!host_new_hash(&host_uid_hash, HOST_HASH_SIZE, MDL)) {
			log_fatal("Can't allocate host/uid hash");
		}
	}

	/*
	 * Remove the old entry, if one exists.
	 */
	if (host->client_identifier.data != NULL) {
		host_hash_delete(host_uid_hash,
				 host->client_identifier.data,
				 host->client_identifier.len,
				 MDL);
		data_string_forget(&host->client_identifier, MDL);
	}

	/*
	 * Set our new value.
	 */
	memset(&host->client_identifier, 0, sizeof(host->client_identifier));
	host->client_identifier.len = len;
	if (!buffer_allocate(&host->client_identifier.buffer, len, MDL)) {
		log_fatal("Can't allocate uid buffer");
	}
	host->client_identifier.data = host->client_identifier.buffer->data;
	memcpy((char *)host->client_identifier.data, uid, len);

	/*
	 * And add to hash.
	 */
	host_hash_add(host_uid_hash, host->client_identifier.data,
		      host->client_identifier.len, host, MDL);
}

isc_result_t enter_host (hd, dynamicp, commit)
	struct host_decl *hd;
	int dynamicp;
	int commit;
{
	struct host_decl *hp = (struct host_decl *)0;
	struct host_decl *np = (struct host_decl *)0;
	struct executable_statement *esp;
	host_id_info_t *h_id_info;

	if (!host_name_hash) {
		if (!host_new_hash(&host_name_hash, HOST_HASH_SIZE, MDL))
			log_fatal ("Can't allocate host name hash");
		host_hash_add (host_name_hash,
			       (unsigned char *)hd -> name,
			       strlen (hd -> name), hd, MDL);
	} else {
		host_hash_lookup (&hp, host_name_hash,
				  (unsigned char *)hd -> name,
				  strlen (hd -> name), MDL);

		/* If it's deleted, we can supersede it. */
		if (hp && (hp -> flags & HOST_DECL_DELETED)) {
			host_hash_delete (host_name_hash,
					  (unsigned char *)hd -> name,
					  strlen (hd -> name), MDL);
			/* If the old entry wasn't dynamic, then we
			   always have to keep the deletion. */
			if (hp -> flags & HOST_DECL_STATIC) {
				hd -> flags |= HOST_DECL_STATIC;
			}
			host_dereference (&hp, MDL);
		}

		/* If we are updating an existing host declaration, we
		   can just delete it and add it again. */
		if (hp && hp == hd) {
			host_dereference (&hp, MDL);
			delete_host (hd, 0);
			if (!write_host (hd))
				return ISC_R_IOERROR;
			hd -> flags &= ~HOST_DECL_DELETED;
		}

		/* If there isn't already a host decl matching this
		   address, add it to the hash table. */
		if (!hp) {
			host_hash_add (host_name_hash,
				       (unsigned char *)hd -> name,
				       strlen (hd -> name), hd, MDL);
		} else {
			/* XXX actually, we have to delete the old one
			   XXX carefully and replace it.   Not done yet. */
			host_dereference (&hp, MDL);
			return ISC_R_EXISTS;
		}
	}

	if (hd -> n_ipaddr)
		host_dereference (&hd -> n_ipaddr, MDL);

	if (!hd -> type)
		hd -> type = dhcp_type_host;

	if (hd -> interface.hlen) {
		if (!host_hw_addr_hash) {
			if (!host_new_hash(&host_hw_addr_hash,
					   HOST_HASH_SIZE, MDL))
				log_fatal ("Can't allocate host/hw hash");
		} else {
			/* If there isn't already a host decl matching this
			   address, add it to the hash table. */
			host_hash_lookup (&hp, host_hw_addr_hash,
					  hd -> interface.hbuf,
					  hd -> interface.hlen, MDL);
		}
		if (!hp)
			host_hash_add (host_hw_addr_hash, hd -> interface.hbuf,
				       hd -> interface.hlen, hd, MDL);
		else {
			/* If there was already a host declaration for
			   this hardware address, add this one to the
			   end of the list. */
			for (np = hp; np -> n_ipaddr; np = np -> n_ipaddr)
				;
			host_reference (&np -> n_ipaddr, hd, MDL);
			host_dereference (&hp, MDL);
		}
	}

	/* See if there's a statement that sets the client identifier.
	   This is a kludge - the client identifier really shouldn't be
	   set with an executable statement. */
	esp = NULL;
	if (executable_statement_foreach (hd->group->statements,
					  find_uid_statement, &esp, 0)) {
		struct data_string cid;
		memset(&cid, 0, sizeof(cid));
		(void) evaluate_option_cache (&cid,
					      NULL, NULL, NULL, NULL, NULL,
					      &global_scope,
					      esp->data.option, MDL);

		if (hd->client_identifier.len > 0 && cid.len > 0) {
			char uid_buf[256];
			char cid_buf[256];
			print_hex_or_string(hd->client_identifier.len,
					    hd->client_identifier.data,
					    sizeof(uid_buf) - 1, uid_buf);

			print_hex_or_string(cid.len, cid.data,
					    sizeof(cid_buf) - 1, cid_buf);

			log_error ("Warning, host declaration '%s'"
				   " already has uid '%s',"
				   " ignoring dhcp-client-identifier '%s'",
				   hd->name, uid_buf, cid_buf);

			data_string_forget(&cid, MDL);
		} else {
			memcpy(&hd->client_identifier, &cid, sizeof(cid));
		}
	}

	/* If we got a client identifier, hash this entry by
	   client identifier. */
	if (hd -> client_identifier.len) {
		/* If there's no uid hash, make one; otherwise, see if
		   there's already an entry in the hash for this host. */
		if (!host_uid_hash) {
			if (!host_new_hash(&host_uid_hash,
					   HOST_HASH_SIZE, MDL))
				log_fatal ("Can't allocate host/uid hash");

			host_hash_add (host_uid_hash,
				       hd -> client_identifier.data,
				       hd -> client_identifier.len,
				       hd, MDL);
		} else {
			/* If there's already a host declaration for this
			   client identifier, add this one to the end of the
			   list.  Otherwise, add it to the hash table. */
			if (host_hash_lookup (&hp, host_uid_hash,
					      hd -> client_identifier.data,
					      hd -> client_identifier.len,
					      MDL)) {
				/* Don't link it in twice... */
				if (!np) {
					for (np = hp; np -> n_ipaddr;
					     np = np -> n_ipaddr) {
						if (hd == np)
						    break;
					}
					if (hd != np)
					    host_reference (&np -> n_ipaddr,
							    hd, MDL);
				}
				host_dereference (&hp, MDL);
			} else {
				host_hash_add (host_uid_hash,
					       hd -> client_identifier.data,
					       hd -> client_identifier.len,
					       hd, MDL);
			}
		}
	}


	/*
	 * If we use an option as our host identifier, record it here.
	 */
	if (hd->host_id_option != NULL) {
		/*
		 * Look for the host identifier information for this option,
		 * and create a new entry if there is none.
		 */
		h_id_info = find_host_id_info(hd->host_id_option->code,
					      hd->relays);
		if (h_id_info == NULL) {
			h_id_info = dmalloc(sizeof(*h_id_info), MDL);
			if (h_id_info == NULL) {
				log_fatal("No memory for host-identifier "
					  "option information.");
			}
			option_reference(&h_id_info->option,
					 hd->host_id_option, MDL);
			if (!host_new_hash(&h_id_info->values_hash,
					   HOST_HASH_SIZE, MDL)) {
				log_fatal("No memory for host-identifier "
					  "option hash.");
			}
			h_id_info->relays = hd->relays;
			h_id_info->next = host_id_info;
			host_id_info = h_id_info;
		}

		if (host_hash_lookup(&hp, h_id_info->values_hash,
				     hd->host_id.data, hd->host_id.len, MDL)) {
			/*
			 * If this option is already present, then add
			 * this host to the list in n_ipaddr, unless
			 * we have already done so previously.
			 *
			 * XXXSK: This seems scary to me, but I don't
			 *        fully understand how these are used.
			 *        Shouldn't there be multiple lists, or
			 *        maybe we should just forbid duplicates?
			 */
			if (np == NULL) {
				np = hp;
				while (np->n_ipaddr != NULL) {
					np = np->n_ipaddr;
				}
				if (hd != np) {
					host_reference(&np->n_ipaddr, hd, MDL);
				}
			}
			host_dereference(&hp, MDL);
		} else {
			host_hash_add(h_id_info->values_hash,
				      hd->host_id.data,
				      hd->host_id.len,
				      hd, MDL);
		}
	}

	if (dynamicp && commit) {
		if (!write_host (hd))
			return ISC_R_IOERROR;
		if (!commit_leases ())
			return ISC_R_IOERROR;
	}

	return ISC_R_SUCCESS;
}


isc_result_t delete_class (cp, commit)
	struct class *cp;
	int commit;
{
	cp->flags |= CLASS_DECL_DELETED;

	/* do the write first as we won't be leaving it in any data
	   structures, unlike the host objects */

	if (commit) {
		write_named_billing_class ((unsigned char *)cp->name, 0, cp);
		if (!commit_leases ())
			return ISC_R_IOERROR;
	}

	/*
	 * If this is a subclass remove it from the class's hash table
	 */
	if (cp->superclass) {
		class_hash_delete(cp->superclass->hash,
				  (const char *)cp->hash_string.data,
				  cp->hash_string.len,
				  MDL);
	}

	/* remove from collections */
	unlink_class(&cp);

	return ISC_R_SUCCESS;
}


isc_result_t delete_host (hd, commit)
	struct host_decl *hd;
	int commit;
{
	struct host_decl *hp = (struct host_decl *)0;
	struct host_decl *np = (struct host_decl *)0;
	struct host_decl *foo;
	int hw_head = 0, uid_head = 1;

	/* Don't need to do it twice. */
	if (hd -> flags & HOST_DECL_DELETED)
		return ISC_R_SUCCESS;

	/* But we do need to do it once!   :') */
	hd -> flags |= HOST_DECL_DELETED;

	if (hd -> interface.hlen) {
	    if (host_hw_addr_hash) {
		if (host_hash_lookup (&hp, host_hw_addr_hash,
				      hd -> interface.hbuf,
				      hd -> interface.hlen, MDL)) {
		    if (hp == hd) {
			host_hash_delete (host_hw_addr_hash,
					  hd -> interface.hbuf,
					  hd -> interface.hlen, MDL);
			hw_head = 1;
		    } else {
			np = (struct host_decl *)0;
			foo = (struct host_decl *)0;
			host_reference (&foo, hp, MDL);
			while (foo) {
			    if (foo == hd)
				    break;
			    if (np)
				    host_dereference (&np, MDL);
			    host_reference (&np, foo, MDL);
			    host_dereference (&foo, MDL);
			    if (np -> n_ipaddr)
				    host_reference (&foo, np -> n_ipaddr, MDL);
			}

			if (foo) {
			    host_dereference (&np -> n_ipaddr, MDL);
			    if (hd -> n_ipaddr)
				host_reference (&np -> n_ipaddr,
						hd -> n_ipaddr, MDL);
			    host_dereference (&foo, MDL);
			}
			if (np)
				host_dereference (&np, MDL);
		    }
		    host_dereference (&hp, MDL);
		}
	    }
	}

	/* If we got a client identifier, hash this entry by
	   client identifier. */
	if (hd -> client_identifier.len) {
	    if (host_uid_hash) {
		if (host_hash_lookup (&hp, host_uid_hash,
				      hd -> client_identifier.data,
				      hd -> client_identifier.len, MDL)) {
		    if (hp == hd) {
			host_hash_delete (host_uid_hash,
					  hd -> client_identifier.data,
					  hd -> client_identifier.len, MDL);
			uid_head = 1;
		    } else {
			np = (struct host_decl *)0;
			foo = (struct host_decl *)0;
			host_reference (&foo, hp, MDL);
			while (foo) {
			    if (foo == hd)
				    break;
			    if (np)
				host_dereference (&np, MDL);
			    host_reference (&np, foo, MDL);
			    host_dereference (&foo, MDL);
			    if (np -> n_ipaddr)
				    host_reference (&foo, np -> n_ipaddr, MDL);
			}

			if (foo) {
			    host_dereference (&np -> n_ipaddr, MDL);
			    if (hd -> n_ipaddr)
				host_reference (&np -> n_ipaddr,
						hd -> n_ipaddr, MDL);
			    host_dereference (&foo, MDL);
			}
			if (np)
				host_dereference (&np, MDL);
		    }
		    host_dereference (&hp, MDL);
		}
	    }
	}

	if (hd->host_id_option != NULL) {
		option_dereference(&hd->host_id_option, MDL);
		data_string_forget(&hd->host_id, MDL);
	}

	if (hd -> n_ipaddr) {
		if (uid_head && hd -> n_ipaddr -> client_identifier.len) {
			host_hash_add
				(host_uid_hash,
				 hd -> n_ipaddr -> client_identifier.data,
				 hd -> n_ipaddr -> client_identifier.len,
				 hd -> n_ipaddr, MDL);
		}
		if (hw_head && hd -> n_ipaddr -> interface.hlen) {
			host_hash_add (host_hw_addr_hash,
				       hd -> n_ipaddr -> interface.hbuf,
				       hd -> n_ipaddr -> interface.hlen,
				       hd -> n_ipaddr, MDL);
		}
		host_dereference (&hd -> n_ipaddr, MDL);
	}

	if (host_name_hash) {
		if (host_hash_lookup (&hp, host_name_hash,
				      (unsigned char *)hd -> name,
				      strlen (hd -> name), MDL)) {
			if (hp == hd && !(hp -> flags & HOST_DECL_STATIC)) {
				host_hash_delete (host_name_hash,
						  (unsigned char *)hd -> name,
						  strlen (hd -> name), MDL);
			}
			host_dereference (&hp, MDL);
		}
	}

	if (commit) {
		if (!write_host (hd))
			return ISC_R_IOERROR;
		if (!commit_leases ())
			return ISC_R_IOERROR;
	}
	return ISC_R_SUCCESS;
}

int find_hosts_by_haddr (struct host_decl **hp, int htype,
			 const unsigned char *haddr, unsigned hlen,
			 const char *file, int line)
{
	struct hardware h;
#if defined(LDAP_CONFIGURATION)
	int ret;

	if ((ret = find_haddr_in_ldap (hp, htype, hlen, haddr, file, line)))
		return ret;
#endif

	h.hlen = hlen + 1;
	h.hbuf [0] = htype;
	memcpy (&h.hbuf [1], haddr, hlen);

	return host_hash_lookup (hp, host_hw_addr_hash,
				 h.hbuf, h.hlen, file, line);
}

int find_hosts_by_uid (struct host_decl **hp,
		       const unsigned char *data, unsigned len,
		       const char *file, int line)
{
	return host_hash_lookup (hp, host_uid_hash, data, len, file, line);
}

int
find_hosts_by_option(struct host_decl **hp,
		     struct packet *packet,
		     struct option_state *opt_state,
		     const char *file, int line) {
	host_id_info_t *p;
	struct option_cache *oc;
	struct data_string data;
	int found;
	struct packet *relay_packet;
	struct option_state *relay_state;

#if defined(LDAP_CONFIGURATION)
	if ((found = find_client_in_ldap (hp, packet, opt_state, file, line)))
		return found;
#endif

	for (p = host_id_info; p != NULL; p = p->next) {
		relay_packet = packet;
		relay_state = opt_state;

		/* If this option block is for a relay (relays != 0)
		 * and we are processing the main options and not
		 * options from the IA (packet->options == opt_state)
		 * try to find the proper relay
		 */
		if ((p->relays != 0) && (packet->options == opt_state)) {
			int i = p->relays;
			while ((i != 0) &&
			       (relay_packet->dhcpv6_container_packet != NULL)) {
				relay_packet =
					relay_packet->dhcpv6_container_packet;
				i--;
			}
			/* We wanted a specific relay but were
			 * unable to find it */
			if ((p->relays <= MAX_V6RELAY_HOPS) && (i != 0))
				continue;

			relay_state = relay_packet->options;
		}

		oc = lookup_option(p->option->universe,
				   relay_state, p->option->code);
		if (oc != NULL) {
			memset(&data, 0, sizeof(data));

			if (!evaluate_option_cache(&data, relay_packet, NULL,
						   NULL, relay_state, NULL,
						   &global_scope, oc,
						   MDL)) {
				log_error("Error evaluating option cache");
				return 0;
			}

			found = host_hash_lookup(hp, p->values_hash,
						 data.data, data.len,
						 file, line);

			data_string_forget(&data, MDL);

			if (found) {
				return 1;
			}
		}
	}
	return 0;
}

/* More than one host_decl can be returned by find_hosts_by_haddr or
   find_hosts_by_uid, and each host_decl can have multiple addresses.
   Loop through the list of hosts, and then for each host, through the
   list of addresses, looking for an address that's in the same shared
   network as the one specified.    Store the matching address through
   the addr pointer, update the host pointer to point at the host_decl
   that matched, and return the subnet that matched. */

int find_host_for_network (struct subnet **sp, struct host_decl **host,
			   struct iaddr *addr, struct shared_network *share)
{
	int i;
	struct iaddr ip_address;
	struct host_decl *hp;
	struct data_string fixed_addr;

	memset (&fixed_addr, 0, sizeof fixed_addr);

	for (hp = *host; hp; hp = hp -> n_ipaddr) {
		if (!hp -> fixed_addr)
			continue;
		if (!evaluate_option_cache (&fixed_addr, (struct packet *)0,
					    (struct lease *)0,
					    (struct client_state *)0,
					    (struct option_state *)0,
					    (struct option_state *)0,
					    &global_scope,
					    hp -> fixed_addr, MDL))
			continue;
		for (i = 0; i < fixed_addr.len; i += 4) {
			ip_address.len = 4;
			memcpy (ip_address.iabuf,
				fixed_addr.data + i, 4);
			if (find_grouped_subnet (sp, share, ip_address, MDL)) {
				struct host_decl *tmp = (struct host_decl *)0;
				*addr = ip_address;
				/* This is probably not necessary, but
				   just in case *host is the only reference
				   to that host declaration, make a temporary
				   reference so that dereferencing it doesn't
				   dereference hp out from under us. */
				host_reference (&tmp, *host, MDL);
				host_dereference (host, MDL);
				host_reference (host, hp, MDL);
				host_dereference (&tmp, MDL);
				data_string_forget (&fixed_addr, MDL);
				return 1;
			}
		}
		data_string_forget (&fixed_addr, MDL);
	}
	return 0;
}

void new_address_range (cfile, low, high, subnet, pool, lpchain)
	struct parse *cfile;
	struct iaddr low, high;
	struct subnet *subnet;
	struct pool *pool;
	struct lease **lpchain;
{
#if defined(COMPACT_LEASES)
	struct lease *address_range;
	unsigned s;
#endif
	unsigned min, max, i, num_addrs;
	char lowbuf [16], highbuf [16], netbuf [16];
	struct shared_network *share = subnet -> shared_network;
	struct lease *lt = (struct lease *)0;
#if !defined(COMPACT_LEASES)
	isc_result_t status;
#endif

	/* All subnets should have attached shared network structures. */
	if (!share) {
		strcpy (netbuf, piaddr (subnet -> net));
		log_fatal ("No shared network for network %s (%s)",
		       netbuf, piaddr (subnet -> netmask));
	}

	/* Initialize the hash table if it hasn't been done yet. */
	if (!lease_uid_hash) {
		if (!lease_id_new_hash(&lease_uid_hash, LEASE_HASH_SIZE, MDL))
			log_fatal ("Can't allocate lease/uid hash");
	}
	if (!lease_ip_addr_hash) {
		if (!lease_ip_new_hash(&lease_ip_addr_hash, LEASE_HASH_SIZE,
				       MDL))
			log_fatal ("Can't allocate lease/ip hash");
	}
	if (!lease_hw_addr_hash) {
		if (!lease_id_new_hash(&lease_hw_addr_hash, LEASE_HASH_SIZE,
				       MDL))
			log_fatal ("Can't allocate lease/hw hash");
	}

	/* Make sure that high and low addresses are in this subnet. */
	if (!addr_eq(subnet->net, subnet_number(low, subnet->netmask))) {
		strcpy(lowbuf, piaddr(low));
		strcpy(netbuf, piaddr(subnet->net));
		log_fatal("bad range, address %s not in subnet %s netmask %s",
			  lowbuf, netbuf, piaddr(subnet->netmask));
	}

	if (!addr_eq(subnet->net, subnet_number(high, subnet->netmask))) {
		strcpy(highbuf, piaddr(high));
		strcpy(netbuf, piaddr(subnet->net));
		log_fatal("bad range, address %s not in subnet %s netmask %s",
			  highbuf, netbuf, piaddr(subnet->netmask));
	}

	/* Get the high and low host addresses... */
	max = host_addr (high, subnet -> netmask);
	min = host_addr (low, subnet -> netmask);

	/* Allow range to be specified high-to-low as well as low-to-high. */
	if (min > max) {
		max = min;
		min = host_addr (high, subnet -> netmask);
	}

	/* get the number of addresses we want, and add it to the pool info
	 * this value is only for use when setting up lease chains and will
	 * be overwritten when expire_all_pools is run
	 */
	num_addrs = max - min + 1;
#if defined (BINARY_LEASES)
	pool->lease_count += num_addrs;
#endif

	/* Get a lease structure for each address in the range. */
#if defined (COMPACT_LEASES)
	s = (num_addrs + 1) * sizeof (struct lease);
	/* Check unsigned overflow in new_leases().
	   With 304 byte lease structure (x64_86), this happens at
	   range 10.0.0.0 10.215.148.52; */
	if (((s % sizeof (struct lease)) != 0) ||
	    ((s / sizeof (struct lease)) != (num_addrs + 1))) {
		strcpy (lowbuf, piaddr (low));
		strcpy (highbuf, piaddr (high));
		parse_warn (cfile, "%s-%s is an overly large address range.",
			   lowbuf, highbuf);
		log_fatal ("Memory overflow.");
	}
	address_range = new_leases (num_addrs, MDL);
	if (!address_range) {
		strcpy (lowbuf, piaddr (low));
		strcpy (highbuf, piaddr (high));
		log_fatal ("No memory for address range %s-%s.",
			   lowbuf, highbuf);
	}
#endif

	/* Fill out the lease structures with some minimal information. */
	for (i = 0; i < num_addrs; i++) {
		struct lease *lp = (struct lease *)0;
#if defined (COMPACT_LEASES)
		omapi_object_initialize ((omapi_object_t *)&address_range [i],
					 dhcp_type_lease,
					 0, sizeof (struct lease), MDL);
		lease_reference (&lp, &address_range [i], MDL);
#else
		status = lease_allocate (&lp, MDL);
		if (status != ISC_R_SUCCESS)
			log_fatal ("No memory for lease %s: %s",
				   piaddr (ip_addr (subnet -> net,
						    subnet -> netmask,
						    i + min)),
				   isc_result_totext (status));
#endif
		lp->ip_addr = ip_addr(subnet->net, subnet->netmask, i + min);
		lp->starts = MIN_TIME;
		lp->ends = MIN_TIME;
		subnet_reference(&lp->subnet, subnet, MDL);
		pool_reference(&lp->pool, pool, MDL);
		lp->binding_state = FTS_FREE;
		lp->next_binding_state = FTS_FREE;
		lp->rewind_binding_state = FTS_FREE;
		lp->flags = 0;

		/* Remember the lease in the IP address hash. */
		if (find_lease_by_ip_addr (&lt, lp -> ip_addr, MDL)) {
			if (lt -> pool) {
				parse_warn (cfile,
					    "lease %s is declared twice!",
					    piaddr (lp -> ip_addr));
			} else
				pool_reference (&lt -> pool, pool, MDL);
			lease_dereference (&lt, MDL);
		} else
			lease_ip_hash_add(lease_ip_addr_hash,
					  lp->ip_addr.iabuf, lp->ip_addr.len,
					  lp, MDL);
		/* Put the lease on the chain for the caller. */
		if (lpchain) {
			if (*lpchain) {
				lease_reference (&lp -> next, *lpchain, MDL);
				lease_dereference (lpchain, MDL);
			}
			lease_reference (lpchain, lp, MDL);
		}
		lease_dereference (&lp, MDL);
	}
}

int find_subnet (struct subnet **sp,
		 struct iaddr addr, const char *file, int line)
{
	struct subnet *rv;

	for (rv = subnets; rv; rv = rv -> next_subnet) {
#if defined(DHCP4o6)
		if (addr.len != rv->netmask.len)
			continue;
#endif
		if (addr_eq (subnet_number (addr, rv -> netmask), rv -> net)) {
			if (subnet_reference (sp, rv,
					      file, line) != ISC_R_SUCCESS)
				return 0;
			return 1;
		}
	}
	return 0;
}

int find_grouped_subnet (struct subnet **sp,
			 struct shared_network *share, struct iaddr addr,
			 const char *file, int line)
{
	struct subnet *rv;

	for (rv = share -> subnets; rv; rv = rv -> next_sibling) {
#if defined(DHCP4o6)
		if (addr.len != rv->netmask.len)
			continue;
#endif
		if (addr_eq (subnet_number (addr, rv -> netmask), rv -> net)) {
			if (subnet_reference (sp, rv,
					      file, line) != ISC_R_SUCCESS)
				return 0;
			return 1;
		}
	}
	return 0;
}

/* XXX: could speed up if everyone had a prefix length */
int
subnet_inner_than(const struct subnet *subnet,
		  const struct subnet *scan,
		  int warnp) {
#if defined(DHCP4o6)
	if (subnet->net.len != scan->net.len)
		return 0;
#endif
	if (addr_eq(subnet_number(subnet->net, scan->netmask), scan->net) ||
	    addr_eq(subnet_number(scan->net, subnet->netmask), subnet->net)) {
		char n1buf[sizeof("ffff:ffff:ffff:ffff:ffff:ffff:255.255.255")];
		int i, j;
		for (i = 0; i < 128; i++)
			if (subnet->netmask.iabuf[3 - (i >> 3)]
			    & (1 << (i & 7)))
				break;
		for (j = 0; j < 128; j++)
			if (scan->netmask.iabuf[3 - (j >> 3)] &
			    (1 << (j & 7)))
				break;
		if (warnp) {
			strcpy(n1buf, piaddr(subnet->net));
			log_error("Warning: subnet %s/%d overlaps subnet %s/%d",
			      n1buf, 32 - i,
			      piaddr(scan->net), 32 - j);
		}
		if (i < j)
			return 1;
	}
	return 0;
}

/* Enter a new subnet into the subnet list. */
void enter_subnet (subnet)
	struct subnet *subnet;
{
	struct subnet *scan = (struct subnet *)0;
	struct subnet *next = (struct subnet *)0;
	struct subnet *prev = (struct subnet *)0;

	/* Check for duplicates... */
	if (subnets)
	    subnet_reference (&next, subnets, MDL);
	while (next) {
	    subnet_reference (&scan, next, MDL);
	    subnet_dereference (&next, MDL);

	    /* When we find a conflict, make sure that the
	       subnet with the narrowest subnet mask comes
	       first. */
	    if (subnet_inner_than (subnet, scan, 1)) {
		if (prev) {
		    if (prev -> next_subnet)
			subnet_dereference (&prev -> next_subnet, MDL);
		    subnet_reference (&prev -> next_subnet, subnet, MDL);
		    subnet_dereference (&prev, MDL);
		} else {
		    subnet_dereference (&subnets, MDL);
		    subnet_reference (&subnets, subnet, MDL);
		}
		subnet_reference (&subnet -> next_subnet, scan, MDL);
		subnet_dereference (&scan, MDL);
		return;
	    }
	    subnet_reference (&prev, scan, MDL);
	    subnet_dereference (&scan, MDL);
	}
	if (prev)
		subnet_dereference (&prev, MDL);

	/* XXX use the BSD radix tree code instead of a linked list. */
	if (subnets) {
		subnet_reference (&subnet -> next_subnet, subnets, MDL);
		subnet_dereference (&subnets, MDL);
	}
	subnet_reference (&subnets, subnet, MDL);
}

/* Enter a new shared network into the shared network list. */

void enter_shared_network (share)
	struct shared_network *share;
{
	if (shared_networks) {
		shared_network_reference (&share -> next,
					  shared_networks, MDL);
		shared_network_dereference (&shared_networks, MDL);
	}
	shared_network_reference (&shared_networks, share, MDL);
}

void new_shared_network_interface (cfile, share, name)
	struct parse *cfile;
	struct shared_network *share;
	const char *name;
{
	struct interface_info *ip;
	isc_result_t status;

	if (share -> interface) {
		parse_warn (cfile,
			    "A subnet or shared network can't be connected %s",
			    "to two interfaces.");
		return;
	}

	for (ip = interfaces; ip; ip = ip -> next)
		if (!strcmp (ip -> name, name))
			break;
	if (!ip) {
		status = interface_allocate (&ip, MDL);
		if (status != ISC_R_SUCCESS)
			log_fatal ("new_shared_network_interface %s: %s",
				   name, isc_result_totext (status));
		if (strlen (name) > sizeof ip -> name) {
			memcpy (ip -> name, name, (sizeof ip -> name) - 1);
			ip -> name [(sizeof ip -> name) - 1] = 0;
		} else
			strcpy (ip -> name, name);
		if (interfaces) {
			interface_reference (&ip -> next, interfaces, MDL);
			interface_dereference (&interfaces, MDL);
		}
		interface_reference (&interfaces, ip, MDL);
		ip -> flags = INTERFACE_REQUESTED;
		/* XXX this is a reference loop. */
		shared_network_reference (&ip -> shared_network, share, MDL);
		interface_reference (&share -> interface, ip, MDL);
	}
}

/* Enter a lease into the system.   This is called by the parser each
   time it reads in a new lease.   If the subnet for that lease has
   already been read in (usually the case), just update that lease;
   otherwise, allocate temporary storage for the lease and keep it around
   until we're done reading in the config file. */

void enter_lease (lease)
	struct lease *lease;
{
	struct lease *comp = (struct lease *)0;

	if (find_lease_by_ip_addr (&comp, lease -> ip_addr, MDL)) {
		if (!comp -> pool) {
			log_error ("undeclared lease found in database: %s",
				   piaddr (lease -> ip_addr));
		} else
			pool_reference (&lease -> pool, comp -> pool, MDL);

		if (comp -> subnet)
			subnet_reference (&lease -> subnet,
					  comp -> subnet, MDL);
		lease_ip_hash_delete(lease_ip_addr_hash,
				     lease->ip_addr.iabuf, lease->ip_addr.len,
				     MDL);
		lease_dereference (&comp, MDL);
	}

	/* The only way a lease can get here without a subnet is if it's in
	   the lease file, but not in the dhcpd.conf file.  In this case, we
	   *should* keep it around until it's expired, but never reallocate it
	   or renew it.  Currently, to maintain consistency, we are not doing
	   this.
	   XXX fix this so that the lease is kept around until it expires.
	   XXX this will be important in IPv6 with addresses that become
	   XXX non-renewable as a result of a renumbering event. */

	if (!lease -> subnet) {
		log_error ("lease %s: no subnet.", piaddr (lease -> ip_addr));
		return;
	}
	lease_ip_hash_add(lease_ip_addr_hash, lease->ip_addr.iabuf,
			  lease->ip_addr.len, lease, MDL);
}

/* Replace the data in an existing lease with the data in a new lease;
   adjust hash tables to suit, and insertion sort the lease into the
   list of leases by expiry time so that we can always find the oldest
   lease. */

int supersede_lease (comp, lease, commit, propogate, pimmediate, from_pool)
	struct lease *comp, *lease;
	int commit;
	int propogate;
	int pimmediate;
	int from_pool;
{
	LEASE_STRUCT_PTR lq;
	struct timeval tv;
#if defined (FAILOVER_PROTOCOL)
	int do_pool_check = 0;

	/* We must commit leases before sending updates regarding them
	   to failover peers.  It is, therefore, an error to set pimmediate
	   and not commit. */
	if (pimmediate && !commit)
		return 0;
#endif
	/* If there is no sample lease, just do the move. */
	if (!lease)
		goto just_move_it;

	/* Static leases are not currently kept in the database... */
	if (lease -> flags & STATIC_LEASE)
		return 1;

	/* If the existing lease hasn't expired and has a different
	   unique identifier or, if it doesn't have a unique
	   identifier, a different hardware address, then the two
	   leases are in conflict.  If the existing lease has a uid
	   and the new one doesn't, but they both have the same
	   hardware address, and dynamic bootp is allowed on this
	   lease, then we allow that, in case a dynamic BOOTP lease is
	   requested *after* a DHCP lease has been assigned. */

	if (lease -> binding_state != FTS_ABANDONED &&
	    lease -> next_binding_state != FTS_ABANDONED &&
	    comp -> binding_state == FTS_ACTIVE &&
	    (((comp -> uid && lease -> uid) &&
	      (comp -> uid_len != lease -> uid_len ||
	       memcmp (comp -> uid, lease -> uid, comp -> uid_len))) ||
	     (!comp -> uid &&
	      ((comp -> hardware_addr.hlen !=
		lease -> hardware_addr.hlen) ||
	       memcmp (comp -> hardware_addr.hbuf,
		       lease -> hardware_addr.hbuf,
		       comp -> hardware_addr.hlen))))) {
		log_error ("Lease conflict at %s",
		      piaddr (comp -> ip_addr));
	}

	/* If there's a Unique ID, dissociate it from the hash
	   table and free it if necessary. */
	if (comp->uid) {
		uid_hash_delete(comp);
		if (comp->uid != comp->uid_buf) {
			dfree(comp->uid, MDL);
			comp->uid_max = 0;
			comp->uid_len = 0;
		}
		comp -> uid = (unsigned char *)0;
	}

	/* If there's a hardware address, remove the lease from its
	 * old position in the hash bucket's ordered list.
	 */
	if (comp->hardware_addr.hlen)
		hw_hash_delete(comp);

	/* If the lease has been billed to a class, remove the billing. */
	if (comp -> billing_class != lease -> billing_class) {
		if (comp->billing_class)
			unbill_class(comp);
		if (lease -> billing_class)
			bill_class (comp, lease -> billing_class);
	}

	/* Copy the data files, but not the linkages. */
	comp -> starts = lease -> starts;
	if (lease -> uid) {
		if (lease -> uid_len <= sizeof (lease -> uid_buf)) {
			memcpy (comp -> uid_buf,
				lease -> uid, lease -> uid_len);
			comp -> uid = &comp -> uid_buf [0];
			comp -> uid_max = sizeof comp -> uid_buf;
			comp -> uid_len = lease -> uid_len;
		} else if (lease -> uid != &lease -> uid_buf [0]) {
			comp -> uid = lease -> uid;
			comp -> uid_max = lease -> uid_max;
			lease -> uid = (unsigned char *)0;
			lease -> uid_max = 0;
			comp -> uid_len = lease -> uid_len;
			lease -> uid_len = 0;
		} else {
			log_fatal ("corrupt lease uid."); /* XXX */
		}
	} else {
		comp -> uid = (unsigned char *)0;
		comp -> uid_len = comp -> uid_max = 0;
	}
	if (comp -> host)
		host_dereference (&comp -> host, MDL);
	host_reference (&comp -> host, lease -> host, MDL);
	comp -> hardware_addr = lease -> hardware_addr;
	if (comp -> scope)
		binding_scope_dereference (&comp -> scope, MDL);
	if (lease -> scope) {
		binding_scope_reference (&comp -> scope, lease -> scope, MDL);
		binding_scope_dereference (&lease -> scope, MDL);
	}

	if (comp -> agent_options)
		option_chain_head_dereference (&comp -> agent_options, MDL);
	if (lease -> agent_options) {
		/* Only retain the agent options if the lease is still
		   affirmatively associated with a client. */
		if (lease -> next_binding_state == FTS_ACTIVE ||
		    lease -> next_binding_state == FTS_EXPIRED)
			option_chain_head_reference (&comp -> agent_options,
						     lease -> agent_options,
						     MDL);
		option_chain_head_dereference (&lease -> agent_options, MDL);
	}

	/* Record the hostname information in the lease. */
	if (comp -> client_hostname)
		dfree (comp -> client_hostname, MDL);
	comp -> client_hostname = lease -> client_hostname;
	lease -> client_hostname = (char *)0;

	if (lease->on_star.on_expiry) {
		if (comp->on_star.on_expiry)
			executable_statement_dereference
				(&comp->on_star.on_expiry, MDL);
		executable_statement_reference (&comp->on_star.on_expiry,
						lease->on_star.on_expiry,
						MDL);
	}
	if (lease->on_star.on_commit) {
		if (comp->on_star.on_commit)
			executable_statement_dereference
				(&comp->on_star.on_commit, MDL);
		executable_statement_reference (&comp->on_star.on_commit,
						lease->on_star.on_commit,
						MDL);
	}
	if (lease->on_star.on_release) {
		if (comp->on_star.on_release)
			executable_statement_dereference
				(&comp->on_star.on_release, MDL);
		executable_statement_reference (&comp->on_star.on_release,
						lease->on_star.on_release,
						MDL);
	}

	/* Record the lease in the uid hash if necessary. */
	if (comp->uid)
		uid_hash_add(comp);

	/* Record it in the hardware address hash if necessary. */
	if (comp->hardware_addr.hlen)
		hw_hash_add(comp);

	comp->cltt = lease->cltt;
#if defined (FAILOVER_PROTOCOL)
	comp->tstp = lease->tstp;
	comp->tsfp = lease->tsfp;
	comp->atsfp = lease->atsfp;
#endif /* FAILOVER_PROTOCOL */
	comp->ends = lease->ends;
	comp->next_binding_state = lease->next_binding_state;

	/*
	 * If we have a control block pointer copy it in.
	 * We don't zero out an older ponter as it is still
	 * in use.  We shouldn't need to overwrite an
	 * old pointer with a new one as the old transaction
	 * should have been cancelled before getting here.
	 */
	if (lease->ddns_cb != NULL)
		comp->ddns_cb = lease->ddns_cb;

      just_move_it:
#if defined (FAILOVER_PROTOCOL)
	/*
	 * Atsfp should be cleared upon any state change that implies
	 * propagation whether supersede_lease was given a copy lease
	 * structure or not (often from the pool_timer()).
	 */
	if (propogate)
		comp->atsfp = 0;
#endif /* FAILOVER_PROTOCOL */

	if (!comp -> pool) {
		log_error ("Supersede_lease: lease %s with no pool.",
			   piaddr (comp -> ip_addr));
		return 0;
	}

	/* Figure out which queue it's on. */
	switch (comp -> binding_state) {
	      case FTS_FREE:
		if (comp->flags & RESERVED_LEASE)
			lq = &comp->pool->reserved;
		else {
			lq = &comp->pool->free;
			comp->pool->free_leases--;
		}

#if defined(FAILOVER_PROTOCOL)
		do_pool_check = 1;
#endif
		break;

	      case FTS_ACTIVE:
		lq = &comp -> pool -> active;
		break;

	      case FTS_EXPIRED:
	      case FTS_RELEASED:
	      case FTS_RESET:
		lq = &comp -> pool -> expired;
		break;

	      case FTS_ABANDONED:
		lq = &comp -> pool -> abandoned;
		break;

	      case FTS_BACKUP:
		if (comp->flags & RESERVED_LEASE)
			lq = &comp->pool->reserved;
		else {
			lq = &comp->pool->backup;
			comp->pool->backup_leases--;
		}

#if defined(FAILOVER_PROTOCOL)
		do_pool_check = 1;
#endif
		break;

	      default:
		log_error ("Lease with bogus binding state: %d",
			   comp -> binding_state);
#if defined (BINDING_STATE_DEBUG)
		abort ();
#endif
		return 0;
	}

	/* Remove the lease from its current place in its current
	   timer sequence. */
	LEASE_REMOVEP(lq, comp);

	/* Now that we've done the flag-affected queue removal
	 * we can update the new lease's flags, if there's an
	 * existing lease */
	if (lease) {
		comp->flags = ((lease->flags & ~PERSISTENT_FLAGS) |
				(comp->flags & ~EPHEMERAL_FLAGS));
	}

	/* Make the state transition. */
	if (commit || !pimmediate)
		make_binding_state_transition (comp);

	/* Put the lease back on the appropriate queue.    If the lease
	   is corrupt (as detected by lease_enqueue), don't go any farther. */
	if (!lease_enqueue (comp))
		return 0;

	/* If this is the next lease that will timeout on the pool,
	   zap the old timeout and set the timeout on this pool to the
	   time that the lease's next event will happen.

	   We do not actually set the timeout unless commit is true -
	   we don't want to thrash the timer queue when reading the
	   lease database.  Instead, the database code calls the
	   expiry event on each pool after reading in the lease file,
	   and the expiry code sets the timer if there's anything left
	   to expire after it's run any outstanding expiry events on
	   the pool. */
	if ((commit || !pimmediate) &&
	    comp -> sort_time != MIN_TIME &&
	    comp -> sort_time > cur_time &&
	    (comp -> sort_time < comp -> pool -> next_event_time ||
	     comp -> pool -> next_event_time == MIN_TIME)) {
		comp -> pool -> next_event_time = comp -> sort_time;
		tv . tv_sec = comp -> pool -> next_event_time;
		tv . tv_usec = 0;
		add_timeout (&tv,
			     pool_timer, comp -> pool,
			     (tvref_t)pool_reference,
			     (tvunref_t)pool_dereference);
	}

	if (commit) {
#if defined(FAILOVER_PROTOCOL)
		/*
		 * If commit and propogate are set, then we can save a
		 * possible fsync later in BNDUPD socket transmission by
		 * stepping the rewind state forward to the new state, in
		 * case it has changed.  This is only worth doing if the
		 * failover connection is currently connected, as in this
		 * case it is likely we will be transmitting to the peer very
		 * shortly.
		 */
		if (propogate && (comp->pool->failover_peer != NULL) &&
		    ((comp->pool->failover_peer->service_state ==
							    cooperating) ||
		     (comp->pool->failover_peer->service_state ==
							    not_responding)))
			comp->rewind_binding_state = comp->binding_state;
#endif

		if (!write_lease (comp))
			return 0;
		if ((server_starting & SS_NOSYNC) == 0) {
			if (!commit_leases ())
				return 0;
		}
	}

#if defined (FAILOVER_PROTOCOL)
	if (propogate) {
		comp -> desired_binding_state = comp -> binding_state;
		if (!dhcp_failover_queue_update (comp, pimmediate))
			return 0;
	}
	if (do_pool_check && comp->pool->failover_peer)
		dhcp_failover_pool_check(comp->pool);
#endif

	/* If the current binding state has already expired and we haven't
	 * been called from pool_timer, do an expiry event right now.
	 */
	/* XXX At some point we should optimize this so that we don't
	   XXX write the lease twice, but this is a safe way to fix the
	   XXX problem for 3.0 (I hope!). */
	if ((from_pool == 0) &&
	    (commit || !pimmediate) &&
	    (comp->sort_time < cur_time) &&
	    (comp->next_binding_state != comp->binding_state))
		pool_timer(comp->pool);

	return 1;
}

void make_binding_state_transition (struct lease *lease)
{

#if defined (FAILOVER_PROTOCOL)
	dhcp_failover_state_t *peer;

	if (lease -> pool && lease -> pool -> failover_peer)
		peer = lease -> pool -> failover_peer;
	else
		peer = (dhcp_failover_state_t *)0;
#endif

	/* If the lease was active and is now no longer active, but isn't
	   released, then it just expired, so do the expiry event. */
	if (lease -> next_binding_state != lease -> binding_state &&
	    ((
#if defined (FAILOVER_PROTOCOL)
		    peer &&
		    (lease->binding_state == FTS_EXPIRED ||
		     lease->binding_state == FTS_ACTIVE) &&
		    (lease->next_binding_state == FTS_FREE ||
		     lease->next_binding_state == FTS_BACKUP)) ||
	     (!peer &&
#endif
	      lease -> binding_state == FTS_ACTIVE &&
	      lease -> next_binding_state != FTS_RELEASED))) {
#if defined (NSUPDATE)
		(void) ddns_removals(lease, NULL, NULL, ISC_TRUE);
#endif
		if (lease->on_star.on_expiry) {
			execute_statements(NULL, NULL, lease,
					   NULL, NULL, NULL,
					   &lease->scope,
					   lease->on_star.on_expiry,
					   NULL);
			if (lease->on_star.on_expiry)
				executable_statement_dereference
					(&lease->on_star.on_expiry, MDL);
		}

		/* No sense releasing a lease after it's expired. */
		if (lease->on_star.on_release)
			executable_statement_dereference
				(&lease->on_star.on_release, MDL);
		/* Get rid of client-specific bindings that are only
		   correct when the lease is active. */
		if (lease->billing_class)
			unbill_class(lease);
		if (lease -> agent_options)
			option_chain_head_dereference (&lease -> agent_options,
						       MDL);
		if (lease -> client_hostname) {
			dfree (lease -> client_hostname, MDL);
			lease -> client_hostname = (char *)0;
		}
		if (lease -> host)
			host_dereference (&lease -> host, MDL);

		/* Send the expiry time to the peer. */
		lease -> tstp = lease -> ends;
	}

	/* If the lease was active and is now released, do the release
	   event. */
	if (lease -> next_binding_state != lease -> binding_state &&
	    ((
#if defined (FAILOVER_PROTOCOL)
		    peer &&
		    lease -> binding_state == FTS_RELEASED &&
		    (lease -> next_binding_state == FTS_FREE ||
		     lease -> next_binding_state == FTS_BACKUP)) ||
	     (!peer &&
#endif
	      lease -> binding_state == FTS_ACTIVE &&
	      lease -> next_binding_state == FTS_RELEASED))) {
#if defined (NSUPDATE)
		/*
		 * Note: ddns_removals() is also iterated when the lease
		 * enters state 'released' in 'release_lease()'.  The below
		 * is caught when a peer receives a BNDUPD from a failover
		 * peer; it may not have received the client's release (it
		 * may have been offline).
		 *
		 * We could remove the call from release_lease() because
		 * it will also catch here on the originating server after the
		 * peer acknowledges the state change.  However, there could
		 * be many hours inbetween, and in this case we /know/ the
		 * client is no longer using the lease when we receive the
		 * release message.  This is not true of expiry, where the
		 * peer may have extended the lease.
		 */
		(void) ddns_removals(lease, NULL, NULL, ISC_TRUE);
#endif
		if (lease->on_star.on_release) {
			execute_statements(NULL, NULL, lease,
					   NULL, NULL, NULL,
					   &lease->scope,
					   lease->on_star.on_release,
					   NULL);
			executable_statement_dereference
				(&lease->on_star.on_release, MDL);
		}

		/* A released lease can't expire. */
		if (lease->on_star.on_expiry)
			executable_statement_dereference
				(&lease->on_star.on_expiry, MDL);

		/* Get rid of client-specific bindings that are only
		   correct when the lease is active. */
		if (lease->billing_class)
			unbill_class(lease);
		if (lease -> agent_options)
			option_chain_head_dereference (&lease -> agent_options,
						       MDL);
		if (lease -> client_hostname) {
			dfree (lease -> client_hostname, MDL);
			lease -> client_hostname = (char *)0;
		}
		if (lease -> host)
			host_dereference (&lease -> host, MDL);

		/* Send the release time (should be == cur_time) to the
		   peer. */
		lease -> tstp = lease -> ends;
	}

#if defined (DEBUG_LEASE_STATE_TRANSITIONS)
	log_debug ("lease %s moves from %s to %s",
		   piaddr (lease -> ip_addr),
		   binding_state_print (lease -> binding_state),
		   binding_state_print (lease -> next_binding_state));
#endif

	lease -> binding_state = lease -> next_binding_state;
	switch (lease -> binding_state) {
	      case FTS_ACTIVE:
#if defined (FAILOVER_PROTOCOL)
		if (lease -> pool && lease -> pool -> failover_peer)
			lease -> next_binding_state = FTS_EXPIRED;
		else
#endif
			lease -> next_binding_state = FTS_FREE;
		break;

	      case FTS_EXPIRED:
	      case FTS_RELEASED:
	      case FTS_ABANDONED:
	      case FTS_RESET:
		lease->next_binding_state = FTS_FREE;
#if defined(FAILOVER_PROTOCOL)
		/* If we are not in partner_down, leases don't go from
		   EXPIRED to FREE on a timeout - only on an update.
		   If we're in partner_down, they expire at mclt past
		   the time we entered partner_down. */
		if ((lease->pool != NULL) &&
		    (lease->pool->failover_peer != NULL) &&
		    (lease->pool->failover_peer->me.state == partner_down))
			lease->tsfp =
			    (lease->pool->failover_peer->me.stos +
			     lease->pool->failover_peer->mclt);
#endif /* FAILOVER_PROTOCOL */
		break;

	      case FTS_FREE:
	      case FTS_BACKUP:
		lease -> next_binding_state = lease -> binding_state;
		break;
	}
#if defined (DEBUG_LEASE_STATE_TRANSITIONS)
	log_debug ("lease %s: next binding state %s",
		   piaddr (lease -> ip_addr),
		   binding_state_print (lease -> next_binding_state));
#endif
}

/* Copy the contents of one lease into another, correctly maintaining
   reference counts. */
int lease_copy (struct lease **lp,
		struct lease *lease, const char *file, int line)
{
	struct lease *lt = (struct lease *)0;
	isc_result_t status;

	status = lease_allocate (&lt, MDL);
	if (status != ISC_R_SUCCESS)
		return 0;

	lt -> ip_addr = lease -> ip_addr;
	lt -> starts = lease -> starts;
	lt -> ends = lease -> ends;
	lt -> uid_len = lease -> uid_len;
	lt -> uid_max = lease -> uid_max;
	if (lease -> uid == lease -> uid_buf) {
		lt -> uid = lt -> uid_buf;
		memcpy (lt -> uid_buf, lease -> uid_buf, sizeof lt -> uid_buf);
	} else if (!lease -> uid_max) {
		lt -> uid = (unsigned char *)0;
	} else {
		lt -> uid = dmalloc (lt -> uid_max, MDL);
		if (!lt -> uid) {
			lease_dereference (&lt, MDL);
			return 0;
		}
		memcpy (lt -> uid, lease -> uid, lease -> uid_max);
	}
	if (lease -> client_hostname) {
		lt -> client_hostname =
			dmalloc (strlen (lease -> client_hostname) + 1, MDL);
		if (!lt -> client_hostname) {
			lease_dereference (&lt, MDL);
			return 0;
		}
		strcpy (lt -> client_hostname, lease -> client_hostname);
	}
	if (lease -> scope)
		binding_scope_reference (&lt -> scope, lease -> scope, MDL);
	if (lease -> agent_options)
		option_chain_head_reference (&lt -> agent_options,
					     lease -> agent_options, MDL);
	host_reference (&lt -> host, lease -> host, file, line);
	subnet_reference (&lt -> subnet, lease -> subnet, file, line);
	pool_reference (&lt -> pool, lease -> pool, file, line);
	class_reference (&lt -> billing_class,
			 lease -> billing_class, file, line);
	lt -> hardware_addr = lease -> hardware_addr;
	if (lease->on_star.on_expiry)
		executable_statement_reference (&lt->on_star.on_expiry,
						lease->on_star.on_expiry,
						file, line);
	if (lease->on_star.on_commit)
		executable_statement_reference (&lt->on_star.on_commit,
						lease->on_star.on_commit,
						file, line);
	if (lease->on_star.on_release)
		executable_statement_reference (&lt->on_star.on_release,
						lease->on_star.on_release,
						file, line);
	lt->flags = lease->flags;
	lt->tstp = lease->tstp;
	lt->tsfp = lease->tsfp;
	lt->atsfp = lease->atsfp;
	lt->cltt = lease -> cltt;
	lt->binding_state = lease->binding_state;
	lt->next_binding_state = lease->next_binding_state;
	lt->rewind_binding_state = lease->rewind_binding_state;
	status = lease_reference(lp, lt, file, line);
	lease_dereference(&lt, MDL);
	return status == ISC_R_SUCCESS;
}

/* Release the specified lease and re-hash it as appropriate. */
void release_lease (lease, packet)
	struct lease *lease;
	struct packet *packet;
{
	/* If there are statements to execute when the lease is
	   released, execute them. */
#if defined (NSUPDATE)
	(void) ddns_removals(lease, NULL, NULL, ISC_FALSE);
#endif
	if (lease->on_star.on_release) {
		execute_statements (NULL, packet, lease,
				    NULL, packet->options,
				    NULL, &lease->scope,
				    lease->on_star.on_release, NULL);
		if (lease->on_star.on_release)
			executable_statement_dereference
				(&lease->on_star.on_release, MDL);
	}

	/* We do either the on_release or the on_expiry events, but
	   not both (it's possible that they could be the same,
	   in any case). */
	if (lease->on_star.on_expiry)
		executable_statement_dereference
			(&lease->on_star.on_expiry, MDL);

	if (lease -> binding_state != FTS_FREE &&
	    lease -> binding_state != FTS_BACKUP &&
	    lease -> binding_state != FTS_RELEASED &&
	    lease -> binding_state != FTS_EXPIRED &&
	    lease -> binding_state != FTS_RESET) {
		if (lease->on_star.on_commit)
			executable_statement_dereference
				(&lease->on_star.on_commit, MDL);

		/* Blow away any bindings. */
		if (lease -> scope)
			binding_scope_dereference (&lease -> scope, MDL);

		/* Set sort times to the present. */
		lease -> ends = cur_time;
		/* Lower layers of muckery set tstp to ->ends.  But we send
		 * protocol messages before this.  So it is best to set
		 * tstp now anyway.
		 */
		lease->tstp = cur_time;
#if defined (FAILOVER_PROTOCOL)
		if (lease -> pool && lease -> pool -> failover_peer) {
			dhcp_failover_state_t *peer = NULL;

			if (lease->pool != NULL)
				peer = lease->pool->failover_peer;

			if ((peer->service_state == not_cooperating) &&
			    (((peer->i_am == primary) &&
			      (lease->rewind_binding_state == FTS_FREE)) ||
			     ((peer->i_am == secondary) &&
			      (lease->rewind_binding_state == FTS_BACKUP)))) {
				lease->next_binding_state =
						  lease->rewind_binding_state;
			} else
				lease -> next_binding_state = FTS_RELEASED;
		} else {
			lease -> next_binding_state = FTS_FREE;
		}
#else
		lease -> next_binding_state = FTS_FREE;
#endif
		supersede_lease(lease, NULL, 1, 1, 1, 0);
	}
}

/* Abandon the specified lease (set its timeout to infinity and its
   particulars to zero, and re-hash it as appropriate. */

void abandon_lease (lease, message)
	struct lease *lease;
	const char *message;
{
	struct lease *lt = NULL;
#if defined (NSUPDATE)
	(void) ddns_removals(lease, NULL, NULL, ISC_FALSE);
#endif

	if (!lease_copy(&lt, lease, MDL)) {
		return;
	}

	if (lt->scope) {
		binding_scope_dereference(&lt->scope, MDL);
	}

	/* Calculate the abandone expiry time.  If it wraps,
 	 * use the maximum expiry time. */
	lt->ends = cur_time + abandon_lease_time;
	if (lt->ends < cur_time || lt->ends > MAX_TIME) {
		lt->ends = MAX_TIME;
	}

	lt->next_binding_state = FTS_ABANDONED;

	log_error ("Abandoning IP address %s: %s", piaddr(lease->ip_addr),
                    message);
	lt->hardware_addr.hlen = 0;
	if (lt->uid && lt->uid != lt->uid_buf) {
		dfree(lt->uid, MDL);
	}

	lt->uid = NULL;
	lt->uid_len = 0;
	lt->uid_max = 0;
	supersede_lease(lease, lt, 1, 1, 1, 0);
	lease_dereference(&lt, MDL);
}

#if 0
/*
 * This doesn't appear to be in use for anything anymore.
 * I'm ifdeffing it now and if there are no complaints in
 * the future it will be removed.
 * SAR
 */

/* Abandon the specified lease (set its timeout to infinity and its
   particulars to zero, and re-hash it as appropriate. */

void dissociate_lease (lease)
	struct lease *lease;
{
	struct lease *lt = (struct lease *)0;
#if defined (NSUPDATE)
	(void) ddns_removals(lease, NULL, NULL, ISC_FALSE);
#endif

	if (!lease_copy (&lt, lease, MDL))
		return;

#if defined (FAILOVER_PROTOCOL)
	if (lease -> pool && lease -> pool -> failover_peer) {
		lt -> next_binding_state = FTS_RESET;
	} else {
		lt -> next_binding_state = FTS_FREE;
	}
#else
	lt -> next_binding_state = FTS_FREE;
#endif
	lt -> ends = cur_time; /* XXX */
	lt -> hardware_addr.hlen = 0;
	if (lt -> uid && lt -> uid != lt -> uid_buf)
		dfree (lt -> uid, MDL);
	lt -> uid = (unsigned char *)0;
	lt -> uid_len = 0;
	lt -> uid_max = 0;
	supersede_lease (lease, lt, 1, 1, 1, 0);
	lease_dereference (&lt, MDL);
}
#endif

/* Timer called when a lease in a particular pool expires. */
void pool_timer (vpool)
	void *vpool;
{
	struct pool *pool;
	struct lease *next = NULL;
	struct lease *lease = NULL;
	struct lease *ltemp = NULL;
#define FREE_LEASES 0
#define ACTIVE_LEASES 1
#define EXPIRED_LEASES 2
#define ABANDONED_LEASES 3
#define BACKUP_LEASES 4
#define RESERVED_LEASES 5
	LEASE_STRUCT_PTR lptr[RESERVED_LEASES+1];
	TIME next_expiry = MAX_TIME;
	int i;
	struct timeval tv;

	pool = (struct pool *)vpool;

	lptr[FREE_LEASES] = &pool->free;
	lptr[ACTIVE_LEASES] = &pool->active;
	lptr[EXPIRED_LEASES] = &pool->expired;
	lptr[ABANDONED_LEASES] = &pool->abandoned;
	lptr[BACKUP_LEASES] = &pool->backup;
	lptr[RESERVED_LEASES] = &pool->reserved;

	for (i = FREE_LEASES; i <= RESERVED_LEASES; i++) {
		/* If there's nothing on the queue, skip it. */
		if (!(LEASE_NOT_EMPTYP(lptr[i])))
			continue;

#if defined (FAILOVER_PROTOCOL)
		if (pool->failover_peer &&
		    pool->failover_peer->me.state != partner_down) {
			/*
			 * Normally the secondary doesn't initiate expiration
			 * events (unless in partner-down), but rather relies
			 * on the primary to expire the lease.  However, when
			 * disconnected from its peer, the server is allowed to
			 * rewind a lease to the previous state that the peer
			 * would have recorded it.  This means there may be
			 * opportunities for active->free or active->backup
			 * expirations while out of contact.
			 *
			 * Q: Should we limit this expiration to
			 *    comms-interrupt rather than not-normal?
			 */
			if ((i == ACTIVE_LEASES) &&
			    (pool->failover_peer->i_am == secondary) &&
			    (pool->failover_peer->me.state == normal))
				continue;

			/* Leases in an expired state don't move to
			   free because of a timeout unless we're in
			   partner_down. */
			if (i == EXPIRED_LEASES)
				continue;
		}
#endif
		lease_reference(&lease, LEASE_GET_FIRSTP(lptr[i]), MDL);

		while (lease) {
			/* Remember the next lease in the list. */
			if (next)
				lease_dereference(&next, MDL);
			ltemp = LEASE_GET_NEXTP(lptr[i], lease);
			if (ltemp)
				lease_reference(&next, ltemp, MDL);

			/* If we've run out of things to expire on this list,
			   stop. */
			if (lease->sort_time > cur_time) {
				if (lease->sort_time < next_expiry)
					next_expiry = lease->sort_time;
				break;
			}

			/* If there is a pending state change, and
			   this lease has gotten to the time when the
			   state change should happen, just call
			   supersede_lease on it to make the change
			   happen. */
			if (lease->next_binding_state != lease->binding_state)
			{
#if defined(FAILOVER_PROTOCOL)
				dhcp_failover_state_t *peer = NULL;

				if (lease->pool != NULL)
					peer = lease->pool->failover_peer;

				/* Can we rewind the lease to a free state? */
				if (peer != NULL &&
				    peer->service_state == not_cooperating &&
				    lease->next_binding_state == FTS_EXPIRED &&
				    ((peer->i_am == primary &&
				      lease->rewind_binding_state == FTS_FREE)
					||
				     (peer->i_am == secondary &&
				      lease->rewind_binding_state ==
								FTS_BACKUP)))
					lease->next_binding_state =
						   lease->rewind_binding_state;
#endif
				supersede_lease(lease, NULL, 1, 1, 1, 1);
			}

			lease_dereference(&lease, MDL);
			if (next)
				lease_reference(&lease, next, MDL);
		}
		if (next)
			lease_dereference(&next, MDL);
		if (lease)
			lease_dereference(&lease, MDL);
	}

	/* If we found something to expire and its expiration time
	 * is either less than the current expiration time or the
	 * current expiration time is already expired update the
	 * timer.
	 */
	if ((next_expiry != MAX_TIME) &&
	    ((pool->next_event_time > next_expiry) ||
	     (pool->next_event_time <= cur_time))) {
		pool->next_event_time = next_expiry;
		tv.tv_sec = pool->next_event_time;
		tv.tv_usec = 0;
		add_timeout (&tv, pool_timer, pool,
			     (tvref_t)pool_reference,
			     (tvunref_t)pool_dereference);
	} else
		pool->next_event_time = MIN_TIME;

}

/* Locate the lease associated with a given IP address... */

int find_lease_by_ip_addr (struct lease **lp, struct iaddr addr,
			   const char *file, int line)
{
	return lease_ip_hash_lookup(lp, lease_ip_addr_hash, addr.iabuf,
				    addr.len, file, line);
}

int find_lease_by_uid (struct lease **lp, const unsigned char *uid,
		       unsigned len, const char *file, int line)
{
	if (len == 0)
		return 0;
	return lease_id_hash_lookup (lp, lease_uid_hash, uid, len, file, line);
}

int find_lease_by_hw_addr (struct lease **lp,
			   const unsigned char *hwaddr, unsigned hwlen,
			   const char *file, int line)
{
	if (hwlen == 0)
		return (0);

	/*
	 * If it's an infiniband address don't bother
	 * as we don't have a useful address to hash.
	 */
	if ((hwlen == 1) && (hwaddr[0] == HTYPE_INFINIBAND))
		return (0);

	return (lease_id_hash_lookup(lp, lease_hw_addr_hash, hwaddr, hwlen,
				     file, line));
}

/* If the lease is preferred over the candidate, return truth.  The
 * 'cand' and 'lease' names are retained to read more clearly against
 * the 'uid_hash_add' and 'hw_hash_add' functions (this is common logic
 * to those two functions).
 *
 * 1) ACTIVE leases are preferred.  The active lease with
 *    the longest lifetime is preferred over shortest.
 * 2) "transitional states" are next, this time with the
 *    most recent CLTT.
 * 3) free/backup/etc states are next, again with CLTT.  In truth we
 *    should never see reset leases for this.
 * 4) Abandoned leases are always dead last.
 */
static isc_boolean_t
client_lease_preferred(struct lease *cand, struct lease *lease)
{
	if (cand->binding_state == FTS_ACTIVE) {
		if (lease->binding_state == FTS_ACTIVE &&
		    lease->ends >= cand->ends)
			return ISC_TRUE;
	} else if (cand->binding_state == FTS_EXPIRED ||
		   cand->binding_state == FTS_RELEASED) {
		if (lease->binding_state == FTS_ACTIVE)
			return ISC_TRUE;

		if ((lease->binding_state == FTS_EXPIRED ||
		     lease->binding_state == FTS_RELEASED) &&
		    lease->cltt >= cand->cltt)
			return ISC_TRUE;
	} else if (cand->binding_state != FTS_ABANDONED) {
		if (lease->binding_state == FTS_ACTIVE ||
		    lease->binding_state == FTS_EXPIRED ||
		    lease->binding_state == FTS_RELEASED)
			return ISC_TRUE;

		if (lease->binding_state != FTS_ABANDONED &&
		    lease->cltt >= cand->cltt)
			return ISC_TRUE;
	} else /* (cand->binding_state == FTS_ABANDONED) */ {
		if (lease->binding_state != FTS_ABANDONED ||
		    lease->cltt >= cand->cltt)
			return ISC_TRUE;
	}

	return ISC_FALSE;
}

/* Add the specified lease to the uid hash. */
void
uid_hash_add(struct lease *lease)
{
	struct lease *head = NULL;
	struct lease *cand = NULL;
	struct lease *prev = NULL;
	struct lease *next = NULL;

	/* If it's not in the hash, just add it. */
	if (!find_lease_by_uid (&head, lease -> uid, lease -> uid_len, MDL))
		lease_id_hash_add(lease_uid_hash, lease->uid, lease->uid_len,
				  lease, MDL);
	else {
		/* Otherwise, insert it into the list in order of its
		 * preference for "resuming allocation to the client."
		 *
		 * Because we don't have control of the hash bucket index
		 * directly, we have to remove and re-insert the client
		 * id into the hash if we're inserting onto the head.
		 */
		lease_reference(&cand, head, MDL);
		while (cand != NULL) {
			if (client_lease_preferred(cand, lease))
				break;

			if (prev != NULL)
				lease_dereference(&prev, MDL);
			lease_reference(&prev, cand, MDL);

			if (cand->n_uid != NULL)
				lease_reference(&next, cand->n_uid, MDL);

			lease_dereference(&cand, MDL);

			if (next != NULL) {
				lease_reference(&cand, next, MDL);
				lease_dereference(&next, MDL);
			}
		}

		/* If we want to insert 'before cand', and prev is NULL,
		 * then it was the head of the list.  Assume that position.
		 */
		if (prev == NULL) {
			lease_reference(&lease->n_uid, head, MDL);
			lease_id_hash_delete(lease_uid_hash, lease->uid,
					     lease->uid_len, MDL);
			lease_id_hash_add(lease_uid_hash, lease->uid,
					  lease->uid_len, lease, MDL);
		} else /* (prev != NULL) */ {
			if(prev->n_uid != NULL) {
				lease_reference(&lease->n_uid, prev->n_uid,
						MDL);
				lease_dereference(&prev->n_uid, MDL);
			}
			lease_reference(&prev->n_uid, lease, MDL);

			lease_dereference(&prev, MDL);
		}

		if (cand != NULL)
			lease_dereference(&cand, MDL);
		lease_dereference(&head, MDL);
	}
}

/* Delete the specified lease from the uid hash. */

void uid_hash_delete (lease)
	struct lease *lease;
{
	struct lease *head = (struct lease *)0;
	struct lease *scan;

	/* If it's not in the hash, we have no work to do. */
	if (!find_lease_by_uid (&head, lease -> uid, lease -> uid_len, MDL)) {
		if (lease -> n_uid)
			lease_dereference (&lease -> n_uid, MDL);
		return;
	}

	/* If the lease we're freeing is at the head of the list,
	   remove the hash table entry and add a new one with the
	   next lease on the list (if there is one). */
	if (head == lease) {
		lease_id_hash_delete(lease_uid_hash, lease->uid,
				     lease->uid_len, MDL);
		if (lease -> n_uid) {
			lease_id_hash_add(lease_uid_hash, lease->n_uid->uid,
					  lease->n_uid->uid_len, lease->n_uid,
					  MDL);
			lease_dereference (&lease -> n_uid, MDL);
		}
	} else {
		/* Otherwise, look for the lease in the list of leases
		   attached to the hash table entry, and remove it if
		   we find it. */
		for (scan = head; scan -> n_uid; scan = scan -> n_uid) {
			if (scan -> n_uid == lease) {
				lease_dereference (&scan -> n_uid, MDL);
				if (lease -> n_uid) {
					lease_reference (&scan -> n_uid,
							 lease -> n_uid, MDL);
					lease_dereference (&lease -> n_uid,
							   MDL);
				}
				break;
			}
		}
	}
	lease_dereference (&head, MDL);
}

/* Add the specified lease to the hardware address hash. */
/* We don't add leases with infiniband addresses to the
 * hash as there isn't any address to hash on. */

void
hw_hash_add(struct lease *lease)
{
	struct lease *head = NULL;
	struct lease *cand = NULL;
	struct lease *prev = NULL;
	struct lease *next = NULL;

	/*
	 * If it's an infiniband address don't bother
	 * as we don't have a useful address to hash.
	 */
	if ((lease->hardware_addr.hlen == 1) &&
	    (lease->hardware_addr.hbuf[0] == HTYPE_INFINIBAND))
		return;

	/* If it's not in the hash, just add it. */
	if (!find_lease_by_hw_addr (&head, lease -> hardware_addr.hbuf,
				    lease -> hardware_addr.hlen, MDL))
		lease_id_hash_add(lease_hw_addr_hash,
				  lease->hardware_addr.hbuf,
				  lease->hardware_addr.hlen, lease, MDL);
	else {
		/* Otherwise, insert it into the list in order of its
		 * preference for "resuming allocation to the client."
		 *
		 * Because we don't have control of the hash bucket index
		 * directly, we have to remove and re-insert the client
		 * id into the hash if we're inserting onto the head.
		 */
		lease_reference(&cand, head, MDL);
		while (cand != NULL) {
			if (client_lease_preferred(cand, lease))
				break;

			if (prev != NULL)
				lease_dereference(&prev, MDL);
			lease_reference(&prev, cand, MDL);

			if (cand->n_hw != NULL)
				lease_reference(&next, cand->n_hw, MDL);

			lease_dereference(&cand, MDL);

			if (next != NULL) {
				lease_reference(&cand, next, MDL);
				lease_dereference(&next, MDL);
			}
		}

		/* If we want to insert 'before cand', and prev is NULL,
		 * then it was the head of the list.  Assume that position.
		 */
		if (prev == NULL) {
			lease_reference(&lease->n_hw, head, MDL);
			lease_id_hash_delete(lease_hw_addr_hash,
					     lease->hardware_addr.hbuf,
					     lease->hardware_addr.hlen, MDL);
			lease_id_hash_add(lease_hw_addr_hash,
					  lease->hardware_addr.hbuf,
					  lease->hardware_addr.hlen,
					  lease, MDL);
		} else /* (prev != NULL) */ {
			if(prev->n_hw != NULL) {
				lease_reference(&lease->n_hw, prev->n_hw,
						MDL);
				lease_dereference(&prev->n_hw, MDL);
			}
			lease_reference(&prev->n_hw, lease, MDL);

			lease_dereference(&prev, MDL);
		}

		if (cand != NULL)
			lease_dereference(&cand, MDL);
		lease_dereference(&head, MDL);
	}
}

/* Delete the specified lease from the hardware address hash. */

void hw_hash_delete (lease)
	struct lease *lease;
{
	struct lease *head = (struct lease *)0;
	struct lease *next = (struct lease *)0;

	/*
	 * If it's an infiniband address don't bother
	 * as we don't have a useful address to hash.
	 */
	if ((lease->hardware_addr.hlen == 1) &&
	    (lease->hardware_addr.hbuf[0] == HTYPE_INFINIBAND))
		return;

	/* If it's not in the hash, we have no work to do. */
	if (!find_lease_by_hw_addr (&head, lease -> hardware_addr.hbuf,
				    lease -> hardware_addr.hlen, MDL)) {
		if (lease -> n_hw)
			lease_dereference (&lease -> n_hw, MDL);
		return;
	}

	/* If the lease we're freeing is at the head of the list,
	   remove the hash table entry and add a new one with the
	   next lease on the list (if there is one). */
	if (head == lease) {
		lease_id_hash_delete(lease_hw_addr_hash,
				     lease->hardware_addr.hbuf,
				     lease->hardware_addr.hlen, MDL);
		if (lease->n_hw) {
			lease_id_hash_add(lease_hw_addr_hash,
					  lease->n_hw->hardware_addr.hbuf,
					  lease->n_hw->hardware_addr.hlen,
					  lease->n_hw, MDL);
			lease_dereference(&lease->n_hw, MDL);
		}
	} else {
		/* Otherwise, look for the lease in the list of leases
		   attached to the hash table entry, and remove it if
		   we find it. */
		while (head -> n_hw) {
			if (head -> n_hw == lease) {
				lease_dereference (&head -> n_hw, MDL);
				if (lease -> n_hw) {
					lease_reference (&head -> n_hw,
							 lease -> n_hw, MDL);
					lease_dereference (&lease -> n_hw,
							   MDL);
				}
				break;
			}
			lease_reference (&next, head -> n_hw, MDL);
			lease_dereference (&head, MDL);
			lease_reference (&head, next, MDL);
			lease_dereference (&next, MDL);
		}
	}
	if (head)
		lease_dereference (&head, MDL);
}

/* Write v4 leases to permanent storage. */
int write_leases4(void) {
	struct lease *l;
	struct shared_network *s;
	struct pool *p;
	LEASE_STRUCT_PTR lptr[RESERVED_LEASES+1];
	int num_written = 0, i;

	/* Write all the leases. */
	for (s = shared_networks; s; s = s->next) {
	    for (p = s->pools; p; p = p->next) {
		lptr[FREE_LEASES] = &p->free;
		lptr[ACTIVE_LEASES] = &p->active;
		lptr[EXPIRED_LEASES] = &p->expired;
		lptr[ABANDONED_LEASES] = &p->abandoned;
		lptr[BACKUP_LEASES] = &p->backup;
		lptr[RESERVED_LEASES] = &p->reserved;

		for (i = FREE_LEASES; i <= RESERVED_LEASES; i++) {
		    for (l = LEASE_GET_FIRSTP(lptr[i]);
			 l != NULL;
			 l = LEASE_GET_NEXTP(lptr[i], l)) {
#if !defined (DEBUG_DUMP_ALL_LEASES)
			if (l->hardware_addr.hlen != 0 || l->uid_len != 0 ||
			    l->tsfp != 0 || l->binding_state != FTS_FREE)
#endif
			{
			    if (write_lease(l) == 0)
				    return (0);
			    num_written++;
			}
		    }
		}
	    }
	}

	log_info ("Wrote %d leases to leases file.", num_written);
	return (1);
}

/* Write all interesting leases to permanent storage. */

int write_leases ()
{
	struct host_decl *hp;
	struct group_object *gp;
	struct hash_bucket *hb;
	struct class *cp;
	struct collection *colp;
	int i;
	int num_written;

	/* write all the dynamically-created class declarations. */
	if (collections->classes) {
		numclasseswritten = 0;
		for (colp = collections ; colp ; colp = colp->next) {
			for (cp = colp->classes ; cp ; cp = cp->nic) {
				write_named_billing_class(
						(unsigned char *)cp->name,
							  0, cp);
			}
		}

		/* XXXJAB this number doesn't include subclasses... */
		log_info ("Wrote %d class decls to leases file.",
			  numclasseswritten);
	}


	/* Write all the dynamically-created group declarations. */
	if (group_name_hash) {
	    num_written = 0;
	    for (i = 0; i < group_name_hash -> hash_count; i++) {
		for (hb = group_name_hash -> buckets [i];
		     hb; hb = hb -> next) {
			gp = (struct group_object *)hb -> value;
			if ((gp -> flags & GROUP_OBJECT_DYNAMIC) ||
			    ((gp -> flags & GROUP_OBJECT_STATIC) &&
			     (gp -> flags & GROUP_OBJECT_DELETED))) {
				if (!write_group (gp))
					return 0;
				++num_written;
			}
		}
	    }
	    log_info ("Wrote %d group decls to leases file.", num_written);
	}

	/* Write all the deleted host declarations. */
	if (host_name_hash) {
	    num_written = 0;
	    for (i = 0; i < host_name_hash -> hash_count; i++) {
		for (hb = host_name_hash -> buckets [i];
		     hb; hb = hb -> next) {
			hp = (struct host_decl *)hb -> value;
			if (((hp -> flags & HOST_DECL_STATIC) &&
			     (hp -> flags & HOST_DECL_DELETED))) {
				if (!write_host (hp))
					return 0;
				++num_written;
			}
		}
	    }
	    log_info ("Wrote %d deleted host decls to leases file.",
		      num_written);
	}

	/* Write all the new, dynamic host declarations. */
	if (host_name_hash) {
	    num_written = 0;
	    for (i = 0; i < host_name_hash -> hash_count; i++) {
		for (hb = host_name_hash -> buckets [i];
		     hb; hb = hb -> next) {
			hp = (struct host_decl *)hb -> value;
			if ((hp -> flags & HOST_DECL_DYNAMIC)) {
				if (!write_host (hp))
					++num_written;
			}
		}
	    }
	    log_info ("Wrote %d new dynamic host decls to leases file.",
		      num_written);
	}

#if defined (FAILOVER_PROTOCOL)
	/* Write all the failover states. */
	if (!dhcp_failover_write_all_states ())
		return 0;
#endif

	switch (local_family) {
	      case AF_INET:
		if (write_leases4() == 0)
			return (0);
		break;
#ifdef DHCPv6
	      case AF_INET6:
		if (write_leases6() == 0)
			return (0);
		break;
#endif /* DHCPv6 */
	}

	if (commit_leases() == 0)
		return (0);
	return (1);
}

#if !defined (BINARY_LEASES)
/* Unlink all the leases in the queue. */
void lease_remove_all(struct lease **lq) {
	struct lease *lp, *ln = NULL;

	/* nothing to do */
	if (*lq == NULL)
		return;

	/* We simply derefernce the first item in the list.  When
	 * it's reference counter goes to zero it will be cleaned
	 * and the reference counter
	 *
	 * Get a pointer to the first item in the list and then
	 * drop the reference from the queue pointer
	 */
	lease_reference(&lp, *lq, MDL);
	lease_dereference(lq, MDL);

	do {
		/* if we have a next save a pointer to it and unlink it */
		if (lp->next) {
			lease_reference(&ln, lp->next, MDL);
			lease_dereference(&lp->next, MDL);
		}

		/* get rid of what we currently have */
		lease_dereference(&lp, MDL);

		/* move the next to the current and loop */
		lp = ln;
		ln = NULL;
	} while (lp != NULL);
}

/*
 * This routine walks through a given lease queue (lq) looking
 * for comp.  If it doesn't find the lease it is a fatal error
 * as it should be on the given queue.  Once we find the lease
 * we can remove it from this list.
 */
void lease_remove(struct lease **lq, struct lease *comp)
{
	struct lease *prev, *lp;

	prev = NULL;
	for (lp = *lq; lp != NULL; lp = lp->next) {
		if (lp == comp)
			break;
		prev = lp;
	}

	if (!lp) {
		log_fatal("Lease with binding state %s not on its queue.",
			  (comp->binding_state < 1 ||
			   comp->binding_state > FTS_LAST)
			  ? "unknown"
			  : binding_state_names[comp->binding_state - 1]);
	}

	if (prev) {
		lease_dereference(&prev->next, MDL);
		if (comp->next) {
			lease_reference(&prev->next, comp->next, MDL);
			lease_dereference (&comp->next, MDL);
		}
	} else {
		lease_dereference(lq, MDL);
		if (comp->next) {
			lease_reference(lq, comp->next, MDL);
			lease_dereference(&comp->next, MDL);
		}
	}
}

/* This routine inserts comp into lq in a sorted fashion.
 * The sort key is comp->sort_time, smaller values are
 * placed earlier in the list.
 */
void lease_insert(struct lease **lq, struct lease *comp)
{
	struct lease *prev, *lp;
	static struct lease **last_lq = NULL;
	static struct lease *last_insert_point = NULL;

	/* This only works during server startup: during runtime, the last
	 * lease may be dequeued in between calls.  If the queue is the same
	 * as was used previously, and the lease structure isn't (this is not
	 * a re-queue), use that as a starting point for the insertion-sort.
	 */
	if ((server_starting & SS_QFOLLOW) && (lq == last_lq) &&
	    (comp != last_insert_point) &&
	    (last_insert_point->sort_time <= comp->sort_time)) {
		prev = last_insert_point;
		lp = prev->next;
	} else {
		prev = NULL;
		lp = *lq;
	}

	/* Insertion sort the lease onto the appropriate queue. */
	for (; lp != NULL ; lp = lp->next) {
		if (lp->sort_time >= comp->sort_time)
			break;
		prev = lp;
	}

	if (prev) {
		if (prev->next) {
			lease_reference(&comp->next, prev->next, MDL);
			lease_dereference(&prev->next, MDL);
		}
		lease_reference(&prev->next, comp, MDL);
	} else {
		if (*lq) {
			lease_reference (&comp->next, *lq, MDL);
			lease_dereference(lq, MDL);
		}
		lease_reference(lq, comp, MDL);
	}
	last_insert_point = comp;
	last_lq = lq;

	return;
}
#endif

/* In addition to placing this lease upon a lease queue depending on its
 * state, it also keeps track of the number of FREE and BACKUP leases in
 * existence, and sets the sort_time on the lease.
 *
 * Sort_time is used in pool_timer() to determine when the lease will
 * bubble to the top of the list and be supersede_lease()'d into its next
 * state (possibly, if all goes well).  Example, ACTIVE leases move to
 * EXPIRED state when the 'ends' value is reached, so that is its sort
 * time.  Most queues are sorted by 'ends', since it is generally best
 * practice to re-use the oldest lease, to reduce address collision
 * chances.
 */
int lease_enqueue (struct lease *comp)
{
	LEASE_STRUCT_PTR lq;

	/* No queue to put it on? */
	if (!comp -> pool)
		return 0;

	/* Figure out which queue it's going to. */
	switch (comp -> binding_state) {
	      case FTS_FREE:
		if (comp->flags & RESERVED_LEASE) {
			lq = &comp->pool->reserved;
		} else {
			lq = &comp->pool->free;
			comp->pool->free_leases++;
		}
		comp -> sort_time = comp -> ends;
		break;

	      case FTS_ACTIVE:
		lq = &comp -> pool -> active;
		comp -> sort_time = comp -> ends;
		break;

	      case FTS_EXPIRED:
	      case FTS_RELEASED:
	      case FTS_RESET:
		lq = &comp -> pool -> expired;
#if defined(FAILOVER_PROTOCOL)
		/* In partner_down, tsfp is the time at which the lease
		 * may be reallocated (stos+mclt).  We can do that with
		 * lease_mine_to_reallocate() anywhere between tsfp and
		 * ends.  But we prefer to wait until ends before doing it
		 * automatically (choose the greater of the two).  Note
		 * that 'ends' is usually a historic timestamp in the
		 * case of expired leases, is really only in the future
		 * on released leases, and if we know a lease to be released
		 * the peer might still know it to be active...in which case
		 * it's possible the peer has renewed this lease, so avoid
		 * doing that.
		 */
		if (comp->pool->failover_peer &&
		    comp->pool->failover_peer->me.state == partner_down)
			comp->sort_time = (comp->tsfp > comp->ends) ?
					  comp->tsfp : comp->ends;
		else
#endif
			comp->sort_time = comp->ends;

		break;

	      case FTS_ABANDONED:
		lq = &comp -> pool -> abandoned;
		comp -> sort_time = comp -> ends;
		break;

	      case FTS_BACKUP:
		if (comp->flags & RESERVED_LEASE) {
			lq = &comp->pool->reserved;
		} else {
			lq = &comp->pool->backup;
			comp->pool->backup_leases++;
		}
		comp -> sort_time = comp -> ends;
		break;

	      default:
		log_error ("Lease with bogus binding state: %d",
			   comp -> binding_state);
#if defined (BINDING_STATE_DEBUG)
		abort ();
#endif
		return 0;
	}

	LEASE_INSERTP(lq, comp);

	return 1;
}

/* For a given lease, sort it onto the right list in its pool and put it
   in each appropriate hash, understanding that it's already by definition
   in lease_ip_addr_hash. */

isc_result_t
lease_instantiate(const void *key, unsigned len, void *object)
{
	struct lease *lease = object;
	struct class *class;
	/* XXX If the lease doesn't have a pool at this point, it's an
	   XXX orphan, which we *should* keep around until it expires,
	   XXX but which right now we just forget. */
	if (!lease -> pool) {
		lease_ip_hash_delete(lease_ip_addr_hash, lease->ip_addr.iabuf,
				     lease->ip_addr.len, MDL);
		return ISC_R_SUCCESS;
	}

#if defined (FAILOVER_PROTOCOL)
	/* If the lease is in FTS_BACKUP but there is no peer, then the
 	 * pool must have been formerly configured for failover and
 	 * is now configured as standalone. This means we need to
 	 * move the lease to FTS_FREE to make it available. */
	if ((lease->binding_state == FTS_BACKUP) &&
	    (lease->pool->failover_peer == NULL)) {
#else
	/* We aren't compiled for failover, so just move to FTS_FREE */
	if (lease->binding_state == FTS_BACKUP) {
#endif
		lease->binding_state = FTS_FREE;
		lease->next_binding_state = FTS_FREE;
		lease->rewind_binding_state = FTS_FREE;
	}

	/* Put the lease on the right queue.  Failure to queue is probably
	 * due to a bogus binding state.  In such a case, we claim success,
	 * so that later leases in a hash_foreach are processed, but we
	 * return early as we really don't want hw address hash entries or
	 * other cruft to surround such a bogus entry.
	 */
	if (!lease_enqueue(lease))
		return ISC_R_SUCCESS;

	/* Record the lease in the uid hash if possible. */
	if (lease -> uid) {
		uid_hash_add (lease);
	}

	/* Record it in the hardware address hash if possible. */
	if (lease -> hardware_addr.hlen) {
		hw_hash_add (lease);
	}

	/* If the lease has a billing class, set up the billing. */
	if (lease -> billing_class) {
		class = (struct class *)0;
		class_reference (&class, lease -> billing_class, MDL);
		class_dereference (&lease -> billing_class, MDL);
		/* If the lease is available for allocation, the billing
		   is invalid, so we don't keep it. */
		if (lease -> binding_state == FTS_ACTIVE ||
		    lease -> binding_state == FTS_EXPIRED ||
		    lease -> binding_state == FTS_RELEASED ||
		    lease -> binding_state == FTS_RESET)
			bill_class (lease, class);
		class_dereference (&class, MDL);
	}
	return ISC_R_SUCCESS;
}

/* Run expiry events on every pool.   This is called on startup so that
   any expiry events that occurred after the server stopped and before it
   was restarted can be run.   At the same time, if failover support is
   compiled in, we compute the balance of leases for the pool. */

void expire_all_pools ()
{
	struct shared_network *s;
	struct pool *p;
	int i;
	struct lease *l;
	LEASE_STRUCT_PTR lptr[RESERVED_LEASES+1];

	/* Indicate that we are in the startup phase */
	server_starting = SS_NOSYNC | SS_QFOLLOW;

#if defined (BINARY_LEASES)
	/* set up the growth factors for the binary leases.
	 * We use 100% for free, 50% for active and backup
	 * 20% for expired, abandoned and reserved
	 * but no less than 100, 50, and 20.
	 */
	for (s = shared_networks; s; s = s -> next) {
	    for (p = s -> pools; p != NULL; p = p -> next) {
		size_t num_f = 100, num_a = 50, num_e = 20;
		if (p->lease_count > 100) {
		    num_f = p->lease_count;
		    num_a = num_f / 2;
		    num_e = num_f / 5;
		}
		lc_init_growth(&p->free, num_f);
		lc_init_growth(&p->active, num_a);
		lc_init_growth(&p->expired, num_a);
		lc_init_growth(&p->abandoned, num_e);
		lc_init_growth(&p->backup, num_e);
		lc_init_growth(&p->reserved, num_e);
	    }
	}
#endif

	/* First, go over the hash list and actually put all the leases
	   on the appropriate lists. */
	lease_ip_hash_foreach(lease_ip_addr_hash, lease_instantiate);

	/* Loop through each pool in each shared network and call the
	 * expiry routine on the pool.  It is no longer safe to follow
	 * the queue insertion point, as expiration of a lease can move
	 * it between queues (and this may be the lease that function
	 * points at).
	 */
	server_starting &= ~SS_QFOLLOW;
	for (s = shared_networks; s; s = s -> next) {
	    for (p = s -> pools; p; p = p -> next) {
		pool_timer (p);

		p -> lease_count = 0;
		p -> free_leases = 0;
		p -> backup_leases = 0;

		lptr [FREE_LEASES] = &p -> free;
		lptr [ACTIVE_LEASES] = &p -> active;
		lptr [EXPIRED_LEASES] = &p -> expired;
		lptr [ABANDONED_LEASES] = &p -> abandoned;
		lptr [BACKUP_LEASES] = &p -> backup;
		lptr [RESERVED_LEASES] = &p->reserved;

		for (i = FREE_LEASES; i <= RESERVED_LEASES; i++) {
		    for (l = LEASE_GET_FIRSTP(lptr[i]);
			 l != NULL;
			 l = LEASE_GET_NEXTP(lptr[i], l)) {
			p -> lease_count++;
			if (l -> ends <= cur_time) {
				if (l->binding_state == FTS_FREE) {
					if (i == FREE_LEASES)
						p->free_leases++;
					else if (i != RESERVED_LEASES)
						log_fatal("Impossible case "
							  "at %s:%d.", MDL);
				} else if (l->binding_state == FTS_BACKUP) {
					if (i == BACKUP_LEASES)
						p->backup_leases++;
					else if (i != RESERVED_LEASES)
						log_fatal("Impossible case "
							  "at %s:%d.", MDL);
				}
			}
#if defined (FAILOVER_PROTOCOL)
			if (p -> failover_peer &&
			    l -> tstp > l -> atsfp &&
			    !(l -> flags & ON_UPDATE_QUEUE)) {
				l -> desired_binding_state = l -> binding_state;
				dhcp_failover_queue_update (l, 1);
			}
#endif
		    }
		}
	    }
	}

	/* turn off startup phase */
	server_starting = 0;
}

void dump_subnets ()
{
	struct lease *l;
	struct shared_network *s;
	struct subnet *n;
	struct pool *p;
	LEASE_STRUCT_PTR lptr[RESERVED_LEASES+1];
	int i;

	log_info ("Subnets:");
	for (n = subnets; n; n = n -> next_subnet) {
		log_debug ("  Subnet %s", piaddr (n -> net));
		log_debug ("     netmask %s",
		       piaddr (n -> netmask));
	}
	log_info ("Shared networks:");
	for (s = shared_networks; s; s = s -> next) {
	    log_info ("  %s", s -> name);
	    for (p = s -> pools; p; p = p -> next) {
		lptr [FREE_LEASES] = &p -> free;
		lptr [ACTIVE_LEASES] = &p -> active;
		lptr [EXPIRED_LEASES] = &p -> expired;
		lptr [ABANDONED_LEASES] = &p -> abandoned;
		lptr [BACKUP_LEASES] = &p -> backup;
		lptr [RESERVED_LEASES] = &p->reserved;

		for (i = FREE_LEASES; i <= RESERVED_LEASES; i++) {
		    for (l = LEASE_GET_FIRSTP(lptr[i]);
			 l != NULL;
			 l = LEASE_GET_NEXTP(lptr[i], l)) {
			    print_lease (l);
		    }
		}
	    }
	}
}

HASH_FUNCTIONS(lease_ip, const unsigned char *, struct lease, lease_ip_hash_t,
	       lease_reference, lease_dereference, do_ip4_hash)
HASH_FUNCTIONS(lease_id, const unsigned char *, struct lease, lease_id_hash_t,
	       lease_reference, lease_dereference, do_id_hash)
HASH_FUNCTIONS (host, const unsigned char *, struct host_decl, host_hash_t,
		host_reference, host_dereference, do_string_hash)
HASH_FUNCTIONS (class, const char *, struct class, class_hash_t,
		class_reference, class_dereference, do_string_hash)

#if defined (DEBUG_MEMORY_LEAKAGE) && \
		defined (DEBUG_MEMORY_LEAKAGE_ON_EXIT)
extern struct hash_table *dns_zone_hash;
extern struct interface_info **interface_vector;
extern int interface_count;
dhcp_control_object_t *dhcp_control_object;
extern struct hash_table *auth_key_hash;
struct hash_table *universe_hash;
struct universe **universes;
int universe_count, universe_max;
#if 0
extern int end;
#endif

#if defined (COMPACT_LEASES)
extern struct lease *lease_hunks;
#endif

void free_everything(void)
{
	struct subnet *sc = (struct subnet *)0, *sn = (struct subnet *)0;
	struct shared_network *nc = (struct shared_network *)0,
		*nn = (struct shared_network *)0;
	struct pool *pc = (struct pool *)0, *pn = (struct pool *)0;
	struct lease *lc = NULL, *ln = NULL,  *ltemp = NULL;
	struct interface_info *ic = (struct interface_info *)0,
		*in = (struct interface_info *)0;
	struct class *cc = (struct class *)0, *cn = (struct class *)0;
	struct collection *lp;
	int i;

	/* Get rid of all the hash tables. */
	if (host_hw_addr_hash)
		host_free_hash_table (&host_hw_addr_hash, MDL);
	host_hw_addr_hash = 0;
	if (host_uid_hash)
		host_free_hash_table (&host_uid_hash, MDL);
	host_uid_hash = 0;
	if (lease_uid_hash)
		lease_id_free_hash_table (&lease_uid_hash, MDL);
	lease_uid_hash = 0;
	if (lease_ip_addr_hash)
		lease_ip_free_hash_table (&lease_ip_addr_hash, MDL);
	lease_ip_addr_hash = 0;
	if (lease_hw_addr_hash)
		lease_id_free_hash_table (&lease_hw_addr_hash, MDL);
	lease_hw_addr_hash = 0;
	if (host_name_hash)
		host_free_hash_table (&host_name_hash, MDL);
	host_name_hash = 0;
	if (dns_zone_hash)
		dns_zone_free_hash_table (&dns_zone_hash, MDL);
	dns_zone_hash = 0;

	while (host_id_info != NULL) {
		host_id_info_t *tmp;
		option_dereference(&host_id_info->option, MDL);
		host_free_hash_table(&host_id_info->values_hash, MDL);
		tmp = host_id_info->next;
		dfree(host_id_info, MDL);
		host_id_info = tmp;
	}
#if 0
	if (auth_key_hash)
		auth_key_free_hash_table (&auth_key_hash, MDL);
#endif
	auth_key_hash = 0;

	omapi_object_dereference ((omapi_object_t **)&dhcp_control_object,
				  MDL);

	for (lp = collections; lp; lp = lp -> next) {
	    if (lp -> classes) {
		class_reference (&cn, lp -> classes, MDL);
		do {
		    if (cn) {
			class_reference (&cc, cn, MDL);
			class_dereference (&cn, MDL);
		    }
		    if (cc -> nic) {
			class_reference (&cn, cc -> nic, MDL);
			class_dereference (&cc -> nic, MDL);
		    }
		    group_dereference (&cc -> group, MDL);
		    if (cc -> hash) {
			    class_free_hash_table (&cc -> hash, MDL);
			    cc -> hash = (struct hash_table *)0;
		    }
		    class_dereference (&cc, MDL);
		} while (cn);
		class_dereference (&lp -> classes, MDL);
	    }
	}

	if (interface_vector) {
	    for (i = 0; i < interface_count; i++) {
		if (interface_vector [i])
		    interface_dereference (&interface_vector [i], MDL);
	    }
	    dfree (interface_vector, MDL);
	    interface_vector = 0;
	}

	if (interfaces) {
	    interface_reference (&in, interfaces, MDL);
	    do {
		if (in) {
		    interface_reference (&ic, in, MDL);
		    interface_dereference (&in, MDL);
		}
		if (ic -> next) {
		    interface_reference (&in, ic -> next, MDL);
		    interface_dereference (&ic -> next, MDL);
		}
		omapi_unregister_io_object ((omapi_object_t *)ic);
		if (ic -> shared_network) {
		    if (ic -> shared_network -> interface)
			interface_dereference
				(&ic -> shared_network -> interface, MDL);
		    shared_network_dereference (&ic -> shared_network, MDL);
		}
		interface_dereference (&ic, MDL);
	    } while (in);
	    interface_dereference (&interfaces, MDL);
	}

	/* Subnets are complicated because of the extra links. */
	if (subnets) {
	    subnet_reference (&sn, subnets, MDL);
	    do {
		if (sn) {
		    subnet_reference (&sc, sn, MDL);
		    subnet_dereference (&sn, MDL);
		}
		if (sc -> next_subnet) {
		    subnet_reference (&sn, sc -> next_subnet, MDL);
		    subnet_dereference (&sc -> next_subnet, MDL);
		}
		if (sc -> next_sibling)
		    subnet_dereference (&sc -> next_sibling, MDL);
		if (sc -> shared_network)
		    shared_network_dereference (&sc -> shared_network, MDL);
		group_dereference (&sc -> group, MDL);
		if (sc -> interface)
		    interface_dereference (&sc -> interface, MDL);
		subnet_dereference (&sc, MDL);
	    } while (sn);
	    subnet_dereference (&subnets, MDL);
	}

	/* So are shared networks. */
	/* XXX: this doesn't work presently, but i'm ok just filtering
	 * it out of the noise (you get a bigger spike on the real leaks).
	 * It would be good to fix this, but it is not a "real bug," so not
	 * today.  This hack is incomplete, it doesn't trim out sub-values.
	 */
	if (shared_networks) {
		shared_network_dereference (&shared_networks, MDL);
	/* This is the old method (tries to free memory twice, broken) */
	} else if (0) {
	    shared_network_reference (&nn, shared_networks, MDL);
	    do {
		if (nn) {
		    shared_network_reference (&nc, nn, MDL);
		    shared_network_dereference (&nn, MDL);
		}
		if (nc -> next) {
		    shared_network_reference (&nn, nc -> next, MDL);
		    shared_network_dereference (&nc -> next, MDL);
		}

		/* As are pools. */
		if (nc -> pools) {
		    pool_reference (&pn, nc -> pools, MDL);
		    do {
			LEASE_STRUCT_PTR lptr[RESERVED_LEASES+1];

			if (pn) {
			    pool_reference (&pc, pn, MDL);
			    pool_dereference (&pn, MDL);
			}
			if (pc -> next) {
			    pool_reference (&pn, pc -> next, MDL);
			    pool_dereference (&pc -> next, MDL);
			}

			lptr [FREE_LEASES] = &pc -> free;
			lptr [ACTIVE_LEASES] = &pc -> active;
			lptr [EXPIRED_LEASES] = &pc -> expired;
			lptr [ABANDONED_LEASES] = &pc -> abandoned;
			lptr [BACKUP_LEASES] = &pc -> backup;
			lptr [RESERVED_LEASES] = &pc->reserved;

			/* As (sigh) are leases. */
			for (i = FREE_LEASES ; i <= RESERVED_LEASES ; i++) {
			    if (LEASE_NOT_EMPTYP(lptr[i])) {
			        lease_reference(&ln, LEASE_GET_FIRSTP(lptr[i]), MDL);
				do {
				    /* save a pointer to the current lease */
				    lease_reference (&lc, ln, MDL);
				    lease_dereference (&ln, MDL);

				    /* get the next lease if there is one */
				    ltemp = LEASE_GET_NEXTP(lptr[i], lc);
				    if (ltemp != NULL) {
					lease_reference(&ln, ltemp, MDL);
				    }

				    /* remove the current lease from the queue */
				    LEASE_REMOVEP(lptr[i], lc);

				    if (lc -> billing_class)
				       class_dereference (&lc -> billing_class,
							  MDL);
				    if (lc -> state)
					free_lease_state (lc -> state, MDL);
				    lc -> state = (struct lease_state *)0;
				    if (lc -> n_hw)
					lease_dereference (&lc -> n_hw, MDL);
				    if (lc -> n_uid)
					lease_dereference (&lc -> n_uid, MDL);
				    lease_dereference (&lc, MDL);
				} while (ln);
			    }
			}
			if (pc -> group)
			    group_dereference (&pc -> group, MDL);
			if (pc -> shared_network)
			    shared_network_dereference (&pc -> shared_network,
							MDL);
			pool_dereference (&pc, MDL);
		    } while (pn);
		    pool_dereference (&nc -> pools, MDL);
		}
		/* Because of a circular reference, we need to nuke this
		   manually. */
		group_dereference (&nc -> group, MDL);
		shared_network_dereference (&nc, MDL);
	    } while (nn);
	    shared_network_dereference (&shared_networks, MDL);
	}

	cancel_all_timeouts ();
	relinquish_timeouts ();
#if defined(DELAYED_ACK)
	relinquish_ackqueue();
#endif
	trace_free_all ();
	group_dereference (&root_group, MDL);
	executable_statement_dereference (&default_classification_rules, MDL);

	shutdown_state = shutdown_drop_omapi_connections;
	omapi_io_state_foreach (dhcp_io_shutdown, 0);
	shutdown_state = shutdown_listeners;
	omapi_io_state_foreach (dhcp_io_shutdown, 0);
	shutdown_state = shutdown_dhcp;
	omapi_io_state_foreach (dhcp_io_shutdown, 0);

	omapi_object_dereference ((omapi_object_t **)&icmp_state, MDL);

	universe_free_hash_table (&universe_hash, MDL);
	for (i = 0; i < universe_count; i++) {
#if 0
		union {
			const char *c;
			char *s;
		} foo;
#endif
		if (universes [i]) {
			if (universes[i]->name_hash)
			    option_name_free_hash_table(
						&universes[i]->name_hash,
						MDL);
			if (universes[i]->code_hash)
			    option_code_free_hash_table(
						&universes[i]->code_hash,
						MDL);
#if 0
			if (universes [i] -> name > (char *)&end) {
				foo.c = universes [i] -> name;
				dfree (foo.s, MDL);
			}
			if (universes [i] > (struct universe *)&end)
				dfree (universes [i], MDL);
#endif
		}
	}
	dfree (universes, MDL);

	relinquish_free_lease_states ();
	relinquish_free_pairs ();
	relinquish_free_expressions ();
	relinquish_free_binding_values ();
	relinquish_free_option_caches ();
	relinquish_free_packets ();
#if defined(COMPACT_LEASES)
	relinquish_lease_hunks ();
#endif
	relinquish_hash_bucket_hunks ();
	omapi_type_relinquish ();
}
#endif /* DEBUG_MEMORY_LEAKAGE_ON_EXIT */
