Radix cross Linux

The main Radix cross Linux repository contains the build scripts of packages, which have the most complete and common functionality for desktop machines

452 Commits   2 Branches   1 Tag
Index: client/dhclient.c
===================================================================
--- client/dhclient.c	(nonexistent)
+++ client/dhclient.c	(revision 5)
@@ -0,0 +1,5742 @@
+/* dhclient.c
+
+   DHCP Client. */
+
+/*
+ * Copyright (c) 2004-2020 by Internet Systems Consortium, Inc. ("ISC")
+ * Copyright (c) 1995-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.
+ *   950 Charter Street
+ *   Redwood City, CA 94063
+ *   <info@isc.org>
+ *   https://www.isc.org/
+ *
+ * This code is based on the original client state machine that was
+ * written by Elliot Poger.  The code has been extensively hacked on
+ * by Ted Lemon since then, so any mistakes you find are probably his
+ * fault and not Elliot's.
+ */
+
+#include "dhcpd.h"
+#include <isc/util.h>
+#include <isc/file.h>
+#include <dns/result.h>
+#include <syslog.h>
+#include <signal.h>
+#include <errno.h>
+#include <sys/time.h>
+#include <sys/wait.h>
+#include <limits.h>
+
+TIME default_lease_time = 43200; /* 12 hours... */
+TIME max_lease_time = 86400; /* 24 hours... */
+
+const char *path_dhclient_conf = _PATH_DHCLIENT_CONF;
+const char *path_dhclient_db = NULL;
+const char *path_dhclient_pid = NULL;
+static char path_dhclient_script_array[] = _PATH_DHCLIENT_SCRIPT;
+char *path_dhclient_script = path_dhclient_script_array;
+const char *path_dhclient_duid = NULL;
+
+static void add_to_tail(struct client_lease** lease_list, struct client_lease* lease);
+
+/* False (default) => we write and use a pid file */
+isc_boolean_t no_pid_file = ISC_FALSE;
+
+int dhcp_max_agent_option_packet_length = 0;
+
+int interfaces_requested = 0;
+
+struct iaddr iaddr_broadcast = { 4, { 255, 255, 255, 255 } };
+struct iaddr iaddr_any = { 4, { 0, 0, 0, 0 } };
+struct in_addr inaddr_any;
+struct sockaddr_in sockaddr_broadcast;
+struct in_addr giaddr;
+struct data_string default_duid;
+int duid_type = 0;
+int duid_v4 = 0;
+int std_dhcid = 0;
+
+int decline_wait_time = 10; /* Default to 10 secs per, RFC 2131, 3.1.5 */
+
+/* ASSERT_STATE() does nothing now; it used to be
+   assert (state_is == state_shouldbe). */
+#define ASSERT_STATE(state_is, state_shouldbe) {}
+
+#ifndef UNIT_TEST
+static const char copyright[] = "Copyright 2004-2020 Internet Systems Consortium.";
+static const char arr [] = "All rights reserved.";
+static const char message [] = "Internet Systems Consortium DHCP Client";
+static const char url [] = "For info, please visit https://www.isc.org/software/dhcp/";
+#endif /* UNIT_TEST */
+
+#if defined(DHCPv6) && defined(DHCP4o6)
+int dhcp4o6_state = -1; /* -1 = stopped, 0 = polling, 1 = started */
+#endif
+int no_daemon = 0;
+int dfd[2] = { -1, -1 };
+struct string_list *client_env = NULL;
+int client_env_count = 0;
+int onetry = 0;
+int quiet = 1;
+int nowait = 0;
+int stateless = 0;
+int wanted_ia_na = -1;		/* the absolute value is the real one. */
+int wanted_ia_ta = 0;
+int wanted_ia_pd = 0;
+int require_all_ias = 0;	/* If the user requires all of the IAs to
+				   be available before accepting a lease
+				   0 = no, 1 = requries */
+#if defined(DHCPv6)
+int dad_wait_time = 0;
+int prefix_len_hint = 0;
+#endif
+
+int address_prefix_len = DHCLIENT_DEFAULT_PREFIX_LEN;
+char *mockup_relay = NULL;
+
+char *progname = NULL;
+
+void run_stateless(int exit_mode, u_int16_t port);
+
+static isc_result_t write_duid(struct data_string *duid);
+static void add_reject(struct packet *packet);
+
+static int check_domain_name(const char *ptr, size_t len, int dots);
+static int check_domain_name_list(const char *ptr, size_t len, int dots);
+static int check_option_values(struct universe *universe, unsigned int opt,
+			       const char *ptr, size_t len);
+
+#if defined(NSUPDATE)
+static void dhclient_ddns_cb_free(dhcp_ddns_cb_t *ddns_cb,
+                                   char* file, int line);
+#endif /* defined NSUPDATE */
+
+
+/*!
+ *
+ * \brief Print the generic usage message
+ *
+ * If the user has provided an incorrect command line print out
+ * the description of the command line.  The arguments provide
+ * a way for the caller to request more specific information about
+ * the error be printed as well.  Mostly this will be that some
+ * comamnd doesn't include its argument.
+ *
+ * \param sfmt - The basic string and format for the specific error
+ * \param sarg - Generally the offending argument from the comamnd line.
+ *
+ * \return Nothing
+ */
+
+#if defined(DHCPv6) && defined(DHCP4o6)
+static void dhcp4o6_poll(void *dummy);
+static void dhcp4o6_resume(void);
+static void recv_dhcpv4_response(struct data_string *raw);
+static int send_dhcpv4_query(struct client_state *client, int broadcast);
+
+static void dhcp4o6_stop(void);
+static void forw_dhcpv4_response(struct packet *packet);
+static void forw_dhcpv4_query(struct data_string *raw);
+#endif
+
+#ifndef UNIT_TEST
+/* These are only used when we call usage() from the main routine
+ * which isn't compiled when building for unit tests
+ */
+static const char use_noarg[] = "No argument for command: %s";
+#ifdef DHCPv6
+static const char use_v6command[] = "Command not used for DHCPv4: %s";
+#endif
+
+#ifdef DHCPv6
+#ifdef DHCP4o6
+#define DHCLIENT_USAGE0 \
+"[-4|-6] [-SNTPRI1dvrxi] [-nw] -4o6 <port>] [-p <port>] [-D LL|LLT]\n" \
+"                [--dad-wait-time <seconds>] [--prefix-len-hint <length>]\n" \
+"                [--decline-wait-time <seconds>]\n" \
+"                [--address-prefix-len <length>]\n"
+#else /* DHCP4o6 */
+#define DHCLIENT_USAGE0 \
+"[-4|-6] [-SNTPRI1dvrxi] [-nw] [-p <port>] [-D LL|LLT]\n" \
+"                [--dad-wait-time <seconds>] [--prefix-len-hint <length>]\n" \
+"                [--decline-wait-time <seconds>]\n" \
+"                [--address-prefix-len <length>]\n"
+#endif
+#else /* DHCPv6 */
+#define DHCLIENT_USAGE0 \
+"[-I1dvrxi] [-nw] [-p <port>] [-D LL|LLT] \n" \
+"                [--decline-wait-time <seconds>]\n"
+#endif
+
+#define DHCLIENT_USAGEC \
+"                [-s server-addr] [-cf config-file]\n" \
+"                [-df duid-file] [-lf lease-file]\n" \
+"                [-pf pid-file] [--no-pid] [-e VAR=val]\n" \
+"                [-sf script-file] [interface]*"
+
+#define DHCLIENT_USAGEH "{--version|--help|-h}"
+
+static void
+usage(const char *sfmt, const char *sarg)
+{
+	log_info("%s %s", message, PACKAGE_VERSION);
+	log_info(copyright);
+	log_info(arr);
+	log_info(url);
+
+	/* If desired print out the specific error message */
+#ifdef PRINT_SPECIFIC_CL_ERRORS
+	if (sfmt != NULL)
+		log_error(sfmt, sarg);
+#endif
+
+	log_fatal("Usage: %s %s%s\n       %s %s",
+		  isc_file_basename(progname),
+		  DHCLIENT_USAGE0,
+		  DHCLIENT_USAGEC,
+		  isc_file_basename(progname),
+		  DHCLIENT_USAGEH);
+}
+
+extern void initialize_client_option_spaces();
+
+int
+main(int argc, char **argv) {
+	int fd;
+	int i;
+	struct interface_info *ip;
+	struct client_state *client;
+	unsigned seed;
+	char *server = NULL;
+	isc_result_t status;
+	int exit_mode = 0;
+	int release_mode = 0;
+	struct timeval tv;
+	omapi_object_t *listener;
+	isc_result_t result;
+	int persist = 0;
+	int no_dhclient_conf = 0;
+	int no_dhclient_db = 0;
+	int no_dhclient_pid = 0;
+	int no_dhclient_script = 0;
+#ifdef DHCPv6
+	int local_family_set = 0;
+#ifdef DHCP4o6
+	u_int16_t dhcp4o6_port = 0;
+#endif /* DHCP4o6 */
+#endif /* DHCPv6 */
+	char *s;
+
+#ifdef OLD_LOG_NAME
+	progname = "dhclient";
+#else
+	progname = argv[0];
+#endif
+	/* Initialize client globals. */
+	memset(&default_duid, 0, sizeof(default_duid));
+
+	local_port = 0;
+	remote_port = 0;
+
+	/* Make sure that file descriptors 0 (stdin), 1, (stdout), and
+	   2 (stderr) are open. To do this, we assume that when we
+	   open a file the lowest available file descriptor is used. */
+	fd = open("/dev/null", O_RDWR);
+	if (fd == 0)
+		fd = open("/dev/null", O_RDWR);
+	if (fd == 1)
+		fd = open("/dev/null", O_RDWR);
+	if (fd == 2)
+		log_perror = 0; /* No sense logging to /dev/null. */
+	else if (fd != -1)
+		close(fd);
+
+	openlog(isc_file_basename(progname), DHCP_LOG_OPTIONS, LOG_DAEMON);
+
+#if !(defined(DEBUG) || defined(__CYGWIN32__))
+	setlogmask(LOG_UPTO(LOG_INFO));
+#endif
+
+	/* Parse arguments changing no_daemon */
+	for (i = 1; i < argc; i++) {
+		if (!strcmp(argv[i], "-r")) {
+			no_daemon = 1;
+		} else if (!strcmp(argv[i], "-x")) {
+			no_daemon = 0;
+		} else if (!strcmp(argv[i], "-d")) {
+			no_daemon = 1;
+		} else if (!strcmp(argv[i], "--version")) {
+			const char vstring[] = "isc-dhclient-";
+			IGNORE_RET(write(STDERR_FILENO, vstring,
+					 strlen(vstring)));
+			IGNORE_RET(write(STDERR_FILENO,
+					 PACKAGE_VERSION,
+					 strlen(PACKAGE_VERSION)));
+			IGNORE_RET(write(STDERR_FILENO, "\n", 1));
+			exit(0);
+		} else if (!strcmp(argv[i], "--help") ||
+			   !strcmp(argv[i], "-h")) {
+			const char *pname = isc_file_basename(progname);
+			IGNORE_RET(write(STDERR_FILENO, "Usage: ", 7));
+			IGNORE_RET(write(STDERR_FILENO, pname, strlen(pname)));
+			IGNORE_RET(write(STDERR_FILENO, " ", 1));
+			IGNORE_RET(write(STDERR_FILENO, DHCLIENT_USAGE0,
+					 strlen(DHCLIENT_USAGE0)));
+			IGNORE_RET(write(STDERR_FILENO, DHCLIENT_USAGEC,
+					 strlen(DHCLIENT_USAGEC)));
+			IGNORE_RET(write(STDERR_FILENO, "\n", 1));
+			IGNORE_RET(write(STDERR_FILENO, "       ", 7));
+			IGNORE_RET(write(STDERR_FILENO, pname, strlen(pname)));
+			IGNORE_RET(write(STDERR_FILENO, " ", 1));
+			IGNORE_RET(write(STDERR_FILENO, DHCLIENT_USAGEH,
+					 strlen(DHCLIENT_USAGEH)));
+			IGNORE_RET(write(STDERR_FILENO, "\n", 1));
+			exit(0);
+		}
+	}
+	/* When not forbidden prepare to become a daemon */
+	if (!no_daemon) {
+		int pid;
+
+		if (pipe(dfd) == -1)
+			log_fatal("Can't get pipe: %m");
+		if ((pid = fork ()) < 0)
+			log_fatal("Can't fork daemon: %m");
+		if (pid != 0) {
+			/* Parent: wait for the child to start */
+			int n;
+
+			(void) close(dfd[1]);
+			do {
+				char buf;
+
+				n = read(dfd[0], &buf, 1);
+				if (n == 1)
+					_exit((int)buf);
+			} while (n == -1 && errno == EINTR);
+			_exit(1);
+		}
+		/* Child */
+		(void) close(dfd[0]);
+	}
+
+	/* Set up the isc and dns library managers */
+	status = dhcp_context_create(DHCP_CONTEXT_PRE_DB | DHCP_CONTEXT_POST_DB
+				     | DHCP_DNS_CLIENT_LAZY_INIT, NULL, NULL);
+	if (status != ISC_R_SUCCESS)
+		log_fatal("Can't initialize context: %s",
+			  isc_result_totext(status));
+
+	/* Set up the OMAPI. */
+	status = omapi_init();
+	if (status != ISC_R_SUCCESS)
+		log_fatal("Can't initialize OMAPI: %s",
+			  isc_result_totext(status));
+
+	/* Set up the OMAPI wrappers for various server database internal
+	   objects. */
+	dhcp_common_objects_setup();
+
+	dhcp_interface_discovery_hook = dhclient_interface_discovery_hook;
+	dhcp_interface_shutdown_hook = dhclient_interface_shutdown_hook;
+	dhcp_interface_startup_hook = dhclient_interface_startup_hook;
+
+	for (i = 1; i < argc; i++) {
+		if (!strcmp(argv[i], "-r")) {
+			release_mode = 1;
+			/* no_daemon = 1; */
+#ifdef DHCPv6
+		} else if (!strcmp(argv[i], "-4")) {
+			if (local_family_set && local_family != AF_INET)
+				log_fatal("Client can only do v4 or v6, not "
+					  "both.");
+			local_family_set = 1;
+			local_family = AF_INET;
+		} else if (!strcmp(argv[i], "-6")) {
+			if (local_family_set && local_family != AF_INET6)
+				log_fatal("Client can only do v4 or v6, not "
+					  "both.");
+			local_family_set = 1;
+			local_family = AF_INET6;
+#ifdef DHCP4o6
+		} else if (!strcmp(argv[i], "-4o6")) {
+			if (++i == argc)
+				usage(use_noarg, argv[i-1]);
+			dhcp4o6_port = validate_port_pair(argv[i]);
+
+			log_debug("DHCPv4 over DHCPv6 over ::1 port %d and %d",
+				  ntohs(dhcp4o6_port),
+				  ntohs(dhcp4o6_port) + 1);
+			dhcpv4_over_dhcpv6 = 1;
+#endif /* DHCP4o6 */
+#endif /* DHCPv6 */
+		} else if (!strcmp(argv[i], "-x")) { /* eXit, no release */
+			release_mode = 0;
+			/* no_daemon = 0; */
+			exit_mode = 1;
+		} else if (!strcmp(argv[i], "-p")) {
+			if (++i == argc)
+				usage(use_noarg, argv[i-1]);
+			local_port = validate_port(argv[i]);
+			log_debug("binding to user-specified port %d",
+				  ntohs(local_port));
+		} else if (!strcmp(argv[i], "-d")) {
+			/* no_daemon = 1; */
+			quiet = 0;
+		} else if (!strcmp(argv[i], "-pf")) {
+			if (++i == argc)
+				usage(use_noarg, argv[i-1]);
+			path_dhclient_pid = argv[i];
+			no_dhclient_pid = 1;
+		} else if (!strcmp(argv[i], "--no-pid")) {
+			no_pid_file = ISC_TRUE;
+		} else if (!strcmp(argv[i], "-cf")) {
+			if (++i == argc)
+				usage(use_noarg, argv[i-1]);
+			path_dhclient_conf = argv[i];
+			no_dhclient_conf = 1;
+		} else if (!strcmp(argv[i], "-df")) {
+			if (++i == argc)
+				usage(use_noarg, argv[i-1]);
+			path_dhclient_duid = argv[i];
+		} else if (!strcmp(argv[i], "-lf")) {
+			if (++i == argc)
+				usage(use_noarg, argv[i-1]);
+			path_dhclient_db = argv[i];
+			no_dhclient_db = 1;
+		} else if (!strcmp(argv[i], "-sf")) {
+			if (++i == argc)
+				usage(use_noarg, argv[i-1]);
+			path_dhclient_script = argv[i];
+			no_dhclient_script = 1;
+		} else if (!strcmp(argv[i], "-1")) {
+			onetry = 1;
+		} else if (!strcmp(argv[i], "-q")) {
+			quiet = 1;
+		} else if (!strcmp(argv[i], "-s")) {
+			if (++i == argc)
+				usage(use_noarg, argv[i-1]);
+			server = argv[i];
+		} else if (!strcmp(argv[i], "-g")) {
+			if (++i == argc)
+				usage(use_noarg, argv[i-1]);
+			mockup_relay = argv[i];
+		} else if (!strcmp(argv[i], "-nw")) {
+			nowait = 1;
+		} else if (!strcmp(argv[i], "-n")) {
+			/* do not start up any interfaces */
+			interfaces_requested = -1;
+		} else if (!strcmp(argv[i], "-w")) {
+			/* do not exit if there are no broadcast interfaces. */
+			persist = 1;
+		} else if (!strcmp(argv[i], "-e")) {
+			struct string_list *tmp;
+			if (++i == argc)
+				usage(use_noarg, argv[i-1]);
+			tmp = dmalloc(strlen(argv[i]) + sizeof *tmp, MDL);
+			if (!tmp)
+				log_fatal("No memory for %s", argv[i]);
+			strcpy(tmp->string, argv[i]);
+			tmp->next = client_env;
+			client_env = tmp;
+			client_env_count++;
+#ifdef DHCPv6
+		} else if (!strcmp(argv[i], "-S")) {
+			if (local_family_set && (local_family == AF_INET)) {
+				usage(use_v6command, argv[i]);
+			}
+			local_family_set = 1;
+			local_family = AF_INET6;
+			wanted_ia_na = 0;
+			stateless = 1;
+		} else if (!strcmp(argv[i], "-N")) {
+			if (local_family_set && (local_family == AF_INET)) {
+				usage(use_v6command, argv[i]);
+			}
+			local_family_set = 1;
+			local_family = AF_INET6;
+			if (wanted_ia_na < 0) {
+				wanted_ia_na = 0;
+			}
+			wanted_ia_na++;
+		} else if (!strcmp(argv[i], "-T")) {
+			if (local_family_set && (local_family == AF_INET)) {
+				usage(use_v6command, argv[i]);
+			}
+			local_family_set = 1;
+			local_family = AF_INET6;
+			if (wanted_ia_na < 0) {
+				wanted_ia_na = 0;
+			}
+			wanted_ia_ta++;
+		} else if (!strcmp(argv[i], "-P")) {
+			if (local_family_set && (local_family == AF_INET)) {
+				usage(use_v6command, argv[i]);
+			}
+			local_family_set = 1;
+			local_family = AF_INET6;
+			if (wanted_ia_na < 0) {
+				wanted_ia_na = 0;
+			}
+			wanted_ia_pd++;
+		} else if (!strcmp(argv[i], "-R")) {
+			if (local_family_set && (local_family == AF_INET)) {
+				usage(use_v6command, argv[i]);
+			}
+			local_family_set = 1;
+			local_family = AF_INET6;
+			require_all_ias = 1;
+		} else if (!strcmp(argv[i], "--dad-wait-time")) {
+			if (++i == argc) {
+				usage(use_noarg, argv[i-1]);
+			}
+			errno = 0;
+			dad_wait_time = (int)strtol(argv[i], &s, 10);
+			if (errno || (*s != '\0') || (dad_wait_time < 0)) {
+				usage("Invalid value for --dad-wait-time: %s",
+				      argv[i]);
+			}
+		} else if (!strcmp(argv[i], "--prefix-len-hint")) {
+			if (++i == argc) {
+				usage(use_noarg, argv[i-1]);
+			}
+
+			errno = 0;
+			prefix_len_hint = (int)strtol(argv[i], &s, 10);
+			if (errno || (*s != '\0') || (prefix_len_hint < 0)) {
+				usage("Invalid value for --prefix-len-hint: %s",
+				      argv[i]);
+			}
+		} else if (!strcmp(argv[i], "--address-prefix-len")) {
+			if (++i == argc) {
+				usage(use_noarg, argv[i-1]);
+			}
+			errno = 0;
+			address_prefix_len = (int)strtol(argv[i], &s, 10);
+			if (errno || (*s != '\0') ||
+			    (address_prefix_len < 0)) {
+				usage("Invalid value for"
+				      " --address-prefix-len: %s", argv[i]);
+			}
+#endif /* DHCPv6 */
+		} else if (!strcmp(argv[i], "--decline-wait-time")) {
+			if (++i == argc) {
+				usage(use_noarg, argv[i-1]);
+			}
+
+			errno = 0;
+			decline_wait_time = (int)strtol(argv[i], &s, 10);
+			if (errno || (*s != '\0') ||
+			    (decline_wait_time < 0)) {
+				usage("Invalid value for "
+				      "--decline-wait-time: %s", argv[i]);
+			}
+		} else if (!strcmp(argv[i], "-D")) {
+			duid_v4 = 1;
+			if (++i == argc)
+				usage(use_noarg, argv[i-1]);
+			if (!strcasecmp(argv[i], "LL")) {
+				duid_type = DUID_LL;
+			} else if (!strcasecmp(argv[i], "LLT")) {
+				duid_type = DUID_LLT;
+			} else {
+				usage("Unknown argument to -D: %s", argv[i]);
+			}
+		} else if (!strcmp(argv[i], "-i")) {
+			/* enable DUID support for DHCPv4 clients */
+			duid_v4 = 1;
+		} else if (!strcmp(argv[i], "-I")) {
+			/* enable standard DHCID support for DDNS updates */
+			std_dhcid = 1;
+		} else if (!strcmp(argv[i], "-v")) {
+			quiet = 0;
+		} else if (argv[i][0] == '-') {
+			usage("Unknown command: %s", argv[i]);
+		} else if (interfaces_requested < 0) {
+			usage("No interfaces comamnd -n and "
+			      " requested interface %s", argv[i]);
+		} else {
+		    struct interface_info *tmp = NULL;
+
+		    status = interface_allocate(&tmp, MDL);
+		    if (status != ISC_R_SUCCESS)
+			log_fatal("Can't record interface %s:%s",
+				  argv[i], isc_result_totext(status));
+		    if (strlen(argv[i]) >= sizeof(tmp->name))
+			    log_fatal("%s: interface name too long (is %ld)",
+				      argv[i], (long)strlen(argv[i]));
+		    strcpy(tmp->name, argv[i]);
+		    if (interfaces) {
+			    interface_reference(&tmp->next,
+						interfaces, MDL);
+			    interface_dereference(&interfaces, MDL);
+		    }
+		    interface_reference(&interfaces, tmp, MDL);
+		    tmp->flags = INTERFACE_REQUESTED;
+		    interfaces_requested++;
+		}
+	}
+
+	if (wanted_ia_na < 0) {
+		wanted_ia_na = 1;
+	}
+
+	/* Support only one (requested) interface for Prefix Delegation. */
+	if (wanted_ia_pd && (interfaces_requested != 1)) {
+		usage("PD %s only supports one requested interface", "-P");
+	}
+
+#if defined(DHCPv6) && defined(DHCP4o6)
+	if ((local_family == AF_INET6) && dhcpv4_over_dhcpv6 &&
+	    (exit_mode || release_mode))
+		log_error("Can't relay DHCPv4-over-DHCPv6 "
+			  "without a persistent DHCPv6 client");
+	if ((local_family == AF_INET) && dhcpv4_over_dhcpv6 &&
+	    (interfaces_requested != 1))
+		log_fatal("DHCPv4-over-DHCPv6 requires an explicit "
+			  "interface on which to be applied");
+#endif
+
+	if (!no_dhclient_conf && (s = getenv("PATH_DHCLIENT_CONF"))) {
+		path_dhclient_conf = s;
+	}
+	if (!no_dhclient_db && (s = getenv("PATH_DHCLIENT_DB"))) {
+		path_dhclient_db = s;
+	}
+	if (!no_dhclient_pid && (s = getenv("PATH_DHCLIENT_PID"))) {
+		path_dhclient_pid = s;
+	}
+	if (!no_dhclient_script && (s = getenv("PATH_DHCLIENT_SCRIPT"))) {
+		path_dhclient_script = s;
+	}
+
+	/* Set up the initial dhcp option universe. */
+	initialize_common_option_spaces();
+
+	/* Set up the initial client option universe. */
+	initialize_client_option_spaces();
+
+	/* Assign v4 or v6 specific running parameters. */
+	if (local_family == AF_INET)
+		dhcpv4_client_assignments();
+#ifdef DHCPv6
+	else if (local_family == AF_INET6)
+		dhcpv6_client_assignments();
+#endif /* DHCPv6 */
+	else
+		log_fatal("Impossible condition at %s:%d.", MDL);
+
+	/*
+	 * convert relative path names to absolute, for files that need
+	 * to be reopened after chdir() has been called
+	 */
+	if (path_dhclient_db[0] != '/') {
+		path_dhclient_db = absolute_path(path_dhclient_db);
+	}
+
+	if (path_dhclient_script[0] != '/') {
+		path_dhclient_script = absolute_path(path_dhclient_script);
+	}
+
+	/*
+	 * See if we should  kill off any currently running client
+	 * we don't try to kill it off if the user told us not
+	 * to write a pid file - we assume they are controlling
+	 * the process in some other fashion.
+	 */
+	if ((release_mode || exit_mode) && (no_pid_file == ISC_FALSE)) {
+		FILE *pidfd;
+		pid_t oldpid;
+		long temp;
+		int e;
+
+		if ((pidfd = fopen(path_dhclient_pid, "r")) != NULL) {
+			e = fscanf(pidfd, "%ld\n", &temp);
+			oldpid = (pid_t)temp;
+
+			if (e != 0 && e != EOF && oldpid) {
+				if (kill(oldpid, SIGTERM) == 0) {
+					log_info("Killed old client process");
+					(void) unlink(path_dhclient_pid);
+					/*
+					 * wait for the old process to
+					 * cleanly terminate.
+					 * Note kill() with sig=0 could
+					 * detect termination but only
+					 * the parent can be signaled...
+					 */
+					sleep(1);
+				} else if (errno == ESRCH) {
+					log_info("Removed stale PID file");
+					(void) unlink(path_dhclient_pid);
+				}
+			}
+			fclose(pidfd);
+		}
+	}
+
+	if (!quiet) {
+		log_info("%s %s", message, PACKAGE_VERSION);
+		log_info(copyright);
+		log_info(arr);
+		log_info(url);
+		log_info("%s", "");
+	} else {
+		log_perror = 0;
+		quiet_interface_discovery = 1;
+	}
+
+	/* If we're given a relay agent address to insert, for testing
+	   purposes, figure out what it is. */
+	if (mockup_relay) {
+		if (!inet_aton(mockup_relay, &giaddr)) {
+			struct hostent *he;
+			he = gethostbyname(mockup_relay);
+			if (he) {
+				memcpy(&giaddr, he->h_addr_list[0],
+				       sizeof giaddr);
+			} else {
+				log_fatal("%s: no such host", mockup_relay);
+			}
+		}
+	}
+
+	/* Get the current time... */
+	gettimeofday(&cur_tv, NULL);
+
+	sockaddr_broadcast.sin_family = AF_INET;
+	sockaddr_broadcast.sin_port = remote_port;
+	if (server) {
+		if (!inet_aton(server, &sockaddr_broadcast.sin_addr)) {
+			struct hostent *he;
+			he = gethostbyname(server);
+			if (he) {
+				memcpy(&sockaddr_broadcast.sin_addr,
+				       he->h_addr_list[0],
+				       sizeof sockaddr_broadcast.sin_addr);
+			} else
+				sockaddr_broadcast.sin_addr.s_addr =
+					INADDR_BROADCAST;
+		}
+	} else {
+		sockaddr_broadcast.sin_addr.s_addr = INADDR_BROADCAST;
+	}
+
+	inaddr_any.s_addr = INADDR_ANY;
+
+	/* Stateless special case. */
+	if (stateless) {
+		if (release_mode || (wanted_ia_na > 0) ||
+		    wanted_ia_ta || wanted_ia_pd ||
+		    (interfaces_requested != 1)) {
+			usage("Stateless command: %s incompatibile with "
+			      "other commands", "-S");
+		}
+#if defined(DHCPv6) && defined(DHCP4o6)
+		run_stateless(exit_mode, dhcp4o6_port);
+#else
+		run_stateless(exit_mode, 0);
+#endif
+		finish(0);
+	}
+
+	/* Discover all the network interfaces. */
+	discover_interfaces(DISCOVER_UNCONFIGURED);
+
+	/* Parse the dhclient.conf file. */
+	read_client_conf();
+
+	/* Parse the lease database. */
+	read_client_leases();
+
+	/* If desired parse the secondary lease database for a DUID */
+	if ((default_duid.len == 0) && (path_dhclient_duid != NULL)) {
+		read_client_duid();
+	}
+
+	/* Rewrite the lease database... */
+	rewrite_client_leases();
+
+	/* XXX */
+/* 	config_counter(&snd_counter, &rcv_counter); */
+
+	/*
+	 * If no broadcast interfaces were discovered, call the script
+	 * and tell it so.
+	 */
+	if (!interfaces) {
+		/*
+		 * Call dhclient-script with the NBI flag,
+		 * in case somebody cares.
+		 */
+		script_init(NULL, "NBI", NULL);
+		script_go(NULL);
+
+		/*
+		 * If we haven't been asked to persist, waiting for new
+		 * interfaces, then just exit.
+		 */
+		if (!persist) {
+			/* Nothing more to do. */
+			log_info("No broadcast interfaces found - exiting.");
+			finish(0);
+		}
+	} else if (!release_mode && !exit_mode) {
+		/* Call the script with the list of interfaces. */
+		for (ip = interfaces; ip; ip = ip->next) {
+			/*
+			 * If interfaces were specified, don't configure
+			 * interfaces that weren't specified!
+			 */
+			if ((interfaces_requested > 0) &&
+			    ((ip->flags & (INTERFACE_REQUESTED |
+					   INTERFACE_AUTOMATIC)) !=
+			     INTERFACE_REQUESTED))
+				continue;
+
+			if (local_family == AF_INET6) {
+				script_init(ip->client, "PREINIT6", NULL);
+			} else {
+				script_init(ip->client, "PREINIT", NULL);
+			    	if (ip->client->alias != NULL)
+					script_write_params(ip->client,
+							    "alias_",
+							    ip->client->alias);
+			}
+			script_go(ip->client);
+		}
+	}
+
+	/* At this point, all the interfaces that the script thinks
+	   are relevant should be running, so now we once again call
+	   discover_interfaces(), and this time ask it to actually set
+	   up the interfaces. */
+	discover_interfaces(interfaces_requested != 0
+			    ? DISCOVER_REQUESTED
+			    : DISCOVER_RUNNING);
+
+	/* Make up a seed for the random number generator from current
+	   time plus the sum of the last four bytes of each
+	   interface's hardware address interpreted as an integer.
+	   Not much entropy, but we're booting, so we're not likely to
+	   find anything better. */
+	seed = 0;
+	for (ip = interfaces; ip; ip = ip->next) {
+		int junk;
+		memcpy(&junk,
+		       &ip->hw_address.hbuf[ip->hw_address.hlen -
+					    sizeof seed], sizeof seed);
+		seed += junk;
+	}
+	srandom(seed + cur_time + (unsigned)getpid());
+
+
+	/*
+	 * Establish a default DUID.  We always do so for v6 and
+	 * do so if desired for v4 via the -D or -i options
+	 */
+	if ((local_family == AF_INET6) ||
+	    ((local_family == AF_INET) && (duid_v4 == 1))) {
+		if (default_duid.len == 0) {
+			if (default_duid.buffer != NULL)
+				data_string_forget(&default_duid, MDL);
+
+			form_duid(&default_duid, MDL);
+			write_duid(&default_duid);
+		}
+	}
+
+#if defined(DHCPv6) && defined(DHCP4o6)
+	if (dhcpv4_over_dhcpv6 && !exit_mode)
+		dhcp4o6_setup(dhcp4o6_port);
+#endif
+
+	/* Start a configuration state machine for each interface. */
+#ifdef DHCPv6
+	if (local_family == AF_INET6) {
+		for (ip = interfaces ; ip != NULL ; ip = ip->next) {
+			for (client = ip->client ; client != NULL ;
+			     client = client->next) {
+				if (release_mode) {
+					start_release6(client);
+					continue;
+				} else if (exit_mode) {
+					unconfigure6(client, "STOP6");
+					continue;
+				}
+
+				/* If we have a previous binding, Confirm
+				 * that we can (or can't) still use it.
+				 */
+				if ((client->active_lease != NULL) &&
+				    !client->active_lease->released)
+					start_confirm6(client);
+				else
+					start_init6(client);
+			}
+		}
+	} else
+#endif /* DHCPv6 */
+	{
+		for (ip = interfaces ; ip ; ip = ip->next) {
+			ip->flags |= INTERFACE_RUNNING;
+			for (client = ip->client ; client ;
+			     client = client->next) {
+				if (exit_mode)
+					state_stop(client);
+				if (release_mode)
+					do_release(client);
+				else {
+					client->state = S_INIT;
+
+					if (top_level_config.initial_delay>0)
+					{
+						tv.tv_sec = 0;
+						if (top_level_config.
+						    initial_delay>1)
+							tv.tv_sec = cur_time
+							+ random()
+							% (top_level_config.
+							   initial_delay-1);
+						tv.tv_usec = random()
+							% 1000000;
+						/*
+						 * this gives better
+						 * distribution than just
+						 *whole seconds
+						 */
+						add_timeout(&tv, state_reboot,
+						            client, 0, 0);
+					} else {
+						state_reboot(client);
+					}
+				}
+			}
+		}
+	}
+
+	if (exit_mode)
+		finish(0);
+	if (release_mode) {
+#ifndef DHCPv6
+		finish(0);
+#else
+		if ((local_family == AF_INET6) || dhcpv4_over_dhcpv6) {
+			if (onetry)
+				finish(0);
+		} else
+			finish(0);
+#endif /* DHCPv6 */
+	}
+
+	/* Start up a listener for the object management API protocol. */
+	if (top_level_config.omapi_port != -1) {
+		listener = NULL;
+		result = omapi_generic_new(&listener, MDL);
+		if (result != ISC_R_SUCCESS)
+			log_fatal("Can't allocate new generic object: %s\n",
+				  isc_result_totext(result));
+		result = omapi_protocol_listen(listener,
+					       (unsigned)
+					       top_level_config.omapi_port,
+					       1);
+		if (result != ISC_R_SUCCESS)
+			log_fatal("Can't start OMAPI protocol: %s",
+				  isc_result_totext (result));
+	}
+
+	/* Set up the bootp packet handler... */
+	bootp_packet_handler = do_packet;
+#ifdef DHCPv6
+	dhcpv6_packet_handler = do_packet6;
+#endif /* DHCPv6 */
+
+#if defined(DEBUG_MEMORY_LEAKAGE) || defined(DEBUG_MALLOC_POOL) || \
+		defined(DEBUG_MEMORY_LEAKAGE_ON_EXIT)
+	dmalloc_cutoff_generation = dmalloc_generation;
+	dmalloc_longterm = dmalloc_outstanding;
+	dmalloc_outstanding = 0;
+#endif
+
+#if defined(ENABLE_GENTLE_SHUTDOWN)
+	/* no signal handlers until we deal with the side effects */
+        /* install signal handlers */
+	signal(SIGINT, dhcp_signal_handler);   /* control-c */
+	signal(SIGTERM, dhcp_signal_handler);  /* kill */
+#endif
+
+	/* If we're not supposed to wait before getting the address,
+	   don't. */
+	if (nowait)
+		detach();
+
+	/* If we're not going to daemonize, write the pid file
+	   now. */
+	if (no_daemon || nowait)
+		write_client_pid_file();
+
+	/* Start dispatching packets and timeouts... */
+	dispatch();
+
+	/* In fact dispatch() never returns. */
+	return 0;
+}
+
+/*
+ * \brief Run the DHCPv6 stateless client (dhclient -6 -S)
+ *
+ * \param exist_mode set to 1 when dhclient was called with -x
+ * \param port DHCPv4-over-DHCPv6 client inter-process communication
+ *  UDP port pair (port,port+1 with port in network byte order)
+ */
+
+void run_stateless(int exit_mode, u_int16_t port)
+{
+#ifdef DHCPv6
+	struct client_state *client;
+	omapi_object_t *listener;
+	isc_result_t result;
+
+#ifndef DHCP4o6
+	IGNORE_UNUSED(port);
+#endif
+
+	/* Discover the network interface. */
+	discover_interfaces(DISCOVER_REQUESTED);
+
+	if (!interfaces)
+		usage("No interfaces available for stateless command: %s", "-S");
+
+	/* Parse the dhclient.conf file. */
+#ifdef DHCP4o6
+	if (dhcpv4_over_dhcpv6) {
+		/* Mark we want to request IRT too! */
+		dhcpv4_over_dhcpv6++;
+	}
+#endif
+	read_client_conf();
+
+	/* Parse the lease database. */
+	read_client_leases();
+
+	/* If desired parse the secondary lease database for a DUID */
+	if ((default_duid.len == 0) && (path_dhclient_duid != NULL)) {
+		read_client_duid();
+	}
+
+	/* Establish a default DUID. */
+	if (default_duid.len == 0) {
+		if (default_duid.buffer != NULL)
+			data_string_forget(&default_duid, MDL);
+
+		form_duid(&default_duid, MDL);
+	}
+
+#ifdef DHCP4o6
+	if (dhcpv4_over_dhcpv6 && !exit_mode)
+		dhcp4o6_setup(port);
+#endif
+
+	/* Start a configuration state machine. */
+	for (client = interfaces->client ;
+	     client != NULL ;
+	     client = client->next) {
+		if (exit_mode) {
+			unconfigure6(client, "STOP6");
+			continue;
+		}
+		start_info_request6(client);
+	}
+	if (exit_mode)
+		return;
+
+	/* Start up a listener for the object management API protocol. */
+	if (top_level_config.omapi_port != -1) {
+		listener = NULL;
+		result = omapi_generic_new(&listener, MDL);
+		if (result != ISC_R_SUCCESS)
+			log_fatal("Can't allocate new generic object: %s\n",
+				  isc_result_totext(result));
+		result = omapi_protocol_listen(listener,
+					       (unsigned)
+					       top_level_config.omapi_port,
+					       1);
+		if (result != ISC_R_SUCCESS)
+			log_fatal("Can't start OMAPI protocol: %s",
+				  isc_result_totext(result));
+	}
+
+	/* Set up the packet handler... */
+	dhcpv6_packet_handler = do_packet6;
+
+#if defined(DEBUG_MEMORY_LEAKAGE) || defined(DEBUG_MALLOC_POOL) || \
+		defined(DEBUG_MEMORY_LEAKAGE_ON_EXIT)
+	dmalloc_cutoff_generation = dmalloc_generation;
+	dmalloc_longterm = dmalloc_outstanding;
+	dmalloc_outstanding = 0;
+#endif
+
+	/* If we're not supposed to wait before getting the address,
+	   don't. */
+	if (nowait)
+		detach();
+
+	/* If we're not going to daemonize, write the pid file
+	   now. */
+	if (no_daemon || nowait)
+		write_client_pid_file();
+
+	/* Start dispatching packets and timeouts... */
+	dispatch();
+
+#endif /* DHCPv6 */
+	return;
+}
+#endif /* !UNIT_TEST */
+
+isc_result_t find_class (struct class **c,
+		const char *s, const char *file, int line)
+{
+	return 0;
+}
+
+int check_collection (packet, lease, collection)
+	struct packet *packet;
+	struct lease *lease;
+	struct collection *collection;
+{
+	return 0;
+}
+
+void classify (packet, class)
+	struct packet *packet;
+	struct class *class;
+{
+}
+
+void unbill_class (lease)
+	struct lease *lease;
+{
+}
+
+int find_subnet (struct subnet **sp,
+		 struct iaddr addr, const char *file, int line)
+{
+	return 0;
+}
+
+/* Individual States:
+ *
+ * Each routine is called from the dhclient_state_machine() in one of
+ * these conditions:
+ * -> entering INIT state
+ * -> recvpacket_flag == 0: timeout in this state
+ * -> otherwise: received a packet in this state
+ *
+ * Return conditions as handled by dhclient_state_machine():
+ * Returns 1, sendpacket_flag = 1: send packet, reset timer.
+ * Returns 1, sendpacket_flag = 0: just reset the timer (wait for a milestone).
+ * Returns 0: finish the nap which was interrupted for no good reason.
+ *
+ * Several per-interface variables are used to keep track of the process:
+ *   active_lease: the lease that is being used on the interface
+ *                 (null pointer if not configured yet).
+ *   offered_leases: leases corresponding to DHCPOFFER messages that have
+ *		     been sent to us by DHCP servers.
+ *   acked_leases: leases corresponding to DHCPACK messages that have been
+ *		   sent to us by DHCP servers.
+ *   sendpacket: DHCP packet we're trying to send.
+ *   destination: IP address to send sendpacket to
+ * In addition, there are several relevant per-lease variables.
+ *   T1_expiry, T2_expiry, lease_expiry: lease milestones
+ * In the active lease, these control the process of renewing the lease;
+ * In leases on the acked_leases list, this simply determines when we
+ * can no longer legitimately use the lease.
+ */
+
+void state_reboot (cpp)
+	void *cpp;
+{
+	struct client_state *client = cpp;
+
+#if defined(DHCPv6) && defined(DHCP4o6)
+	if (dhcpv4_over_dhcpv6 && (dhcp4o6_state <= 0)) {
+		if (dhcp4o6_state < 0)
+			dhcp4o6_poll(NULL);
+		client->pending = P_REBOOT;
+		return;
+	}
+#endif
+
+	client->pending= P_NONE;
+
+	/* If we don't remember an active lease, go straight to INIT. */
+	if (!client -> active ||
+	    client -> active -> is_bootp ||
+	    client -> active -> expiry <= cur_time) {
+		state_init (client);
+		return;
+	}
+
+	/* We are in the rebooting state. */
+	client -> state = S_REBOOTING;
+
+	/*
+	 * make_request doesn't initialize xid because it normally comes
+	 * from the DHCPDISCOVER, but we haven't sent a DHCPDISCOVER,
+	 * so pick an xid now.
+	 */
+	client -> xid = random ();
+
+	/*
+	 * Make a DHCPREQUEST packet, and set
+	 * appropriate per-interface flags.
+	 */
+	make_request (client, client -> active);
+	client -> destination = iaddr_broadcast;
+	client -> first_sending = cur_time;
+	client -> interval = client -> config -> initial_interval;
+
+	/* Zap the medium list... */
+	client -> medium = NULL;
+
+	/* Send out the first DHCPREQUEST packet. */
+	send_request (client);
+}
+
+/* Called when a lease has completely expired and we've been unable to
+   renew it. */
+
+void state_init (cpp)
+	void *cpp;
+{
+	struct client_state *client = cpp;
+
+	ASSERT_STATE(state, S_INIT);
+
+	/* Make a DHCPDISCOVER packet, and set appropriate per-interface
+	   flags. */
+	make_discover (client, client -> active);
+	client -> xid = client -> packet.xid;
+	client -> destination = iaddr_broadcast;
+	client -> state = S_SELECTING;
+	client -> first_sending = cur_time;
+	client -> interval = client -> config -> initial_interval;
+
+	/* Add an immediate timeout to cause the first DHCPDISCOVER packet
+	   to go out. */
+	send_discover (client);
+}
+
+/*
+ * state_selecting is called when one or more DHCPOFFER packets have been
+ * received and a configurable period of time has passed.
+ */
+
+void state_selecting (cpp)
+	void *cpp;
+{
+	struct client_state *client = cpp;
+	struct client_lease *lp, *next, *picked;
+
+
+	ASSERT_STATE(state, S_SELECTING);
+
+	/*
+	 * Cancel state_selecting and send_discover timeouts, since either
+	 * one could have got us here.
+	 */
+	cancel_timeout (state_selecting, client);
+	cancel_timeout (send_discover, client);
+
+	/*
+	 * We have received one or more DHCPOFFER packets.   Currently,
+	 * the only criterion by which we judge leases is whether or
+	 * not we get a response when we arp for them.
+	 */
+	picked = NULL;
+	for (lp = client -> offered_leases; lp; lp = next) {
+		next = lp -> next;
+
+		/*
+		 * Check to see if we got an ARPREPLY for the address
+		 * in this particular lease.
+		 */
+		if (!picked) {
+			picked = lp;
+			picked -> next = NULL;
+		} else {
+			destroy_client_lease (lp);
+		}
+	}
+	client -> offered_leases = NULL;
+
+	/*
+	 * If we just tossed all the leases we were offered, go back
+	 * to square one.
+	 */
+	if (!picked) {
+		client -> state = S_INIT;
+		state_init (client);
+		return;
+	}
+
+	/* If it was a BOOTREPLY, we can just take the address right now. */
+	if (picked -> is_bootp) {
+		client -> new = picked;
+
+		/* Make up some lease expiry times
+		   XXX these should be configurable. */
+		client -> new -> expiry = cur_time + 12000;
+		client -> new -> renewal += cur_time + 8000;
+		client -> new -> rebind += cur_time + 10000;
+
+		client -> state = S_REQUESTING;
+
+		/* Bind to the address we received. */
+		bind_lease (client);
+		return;
+	}
+
+	/* Go to the REQUESTING state. */
+	client -> destination = iaddr_broadcast;
+	client -> state = S_REQUESTING;
+	client -> first_sending = cur_time;
+	client -> interval = client -> config -> initial_interval;
+
+	/* Make a DHCPREQUEST packet from the lease we picked. */
+	make_request (client, picked);
+	client -> xid = client -> packet.xid;
+
+	/* Toss the lease we picked - we'll get it back in a DHCPACK. */
+	destroy_client_lease (picked);
+
+	/* Add an immediate timeout to send the first DHCPREQUEST packet. */
+	send_request (client);
+}
+
+/* state_requesting is called when we receive a DHCPACK message after
+   having sent out one or more DHCPREQUEST packets. */
+
+void dhcpack (packet)
+	struct packet *packet;
+{
+	struct interface_info *ip = packet -> interface;
+	struct client_state *client;
+	struct client_lease *lease;
+	struct option_cache *oc;
+	struct data_string ds;
+
+	/* If we're not receptive to an offer right now, or if the offer
+	   has an unrecognizable transaction id, then just drop it. */
+	for (client = ip -> client; client; client = client -> next) {
+		if (client -> xid == packet -> raw -> xid)
+			break;
+	}
+	if (!client ||
+	    (packet -> interface -> hw_address.hlen - 1 !=
+	     packet -> raw -> hlen) ||
+	    (memcmp (&packet -> interface -> hw_address.hbuf [1],
+		     packet -> raw -> chaddr, packet -> raw -> hlen))) {
+#if defined (DEBUG)
+		log_debug ("DHCPACK in wrong transaction.");
+#endif
+		return;
+	}
+
+	if (client -> state != S_REBOOTING &&
+	    client -> state != S_REQUESTING &&
+	    client -> state != S_RENEWING &&
+	    client -> state != S_REBINDING) {
+#if defined (DEBUG)
+		log_debug ("DHCPACK in wrong state.");
+#endif
+		return;
+	}
+
+	log_info ("DHCPACK of %s from %s",
+		  inet_ntoa(packet->raw->yiaddr),
+		  piaddr (packet->client_addr));
+
+	lease = packet_to_lease (packet, client);
+	if (!lease) {
+		log_info ("packet_to_lease failed.");
+		return;
+	}
+
+	client -> new = lease;
+
+	/* Stop resending DHCPREQUEST. */
+	cancel_timeout (send_request, client);
+
+	/* Figure out the lease time. */
+	oc = lookup_option (&dhcp_universe, client -> new -> options,
+			    DHO_DHCP_LEASE_TIME);
+	memset (&ds, 0, sizeof ds);
+	if (oc &&
+	    evaluate_option_cache (&ds, packet, (struct lease *)0, client,
+				   packet -> options, client -> new -> options,
+				   &global_scope, oc, MDL)) {
+		if (ds.len > 3)
+			client -> new -> expiry = getULong (ds.data);
+		else
+			client -> new -> expiry = 0;
+		data_string_forget (&ds, MDL);
+	} else
+			client -> new -> expiry = 0;
+
+	if (client->new->expiry == 0) {
+		struct timeval tv;
+
+		log_error ("no expiry time on offered lease.");
+
+		/* Quench this (broken) server.  Return to INIT to reselect. */
+		add_reject(packet);
+
+		/* 1/2 second delay to restart at INIT. */
+		tv.tv_sec = cur_tv.tv_sec;
+		tv.tv_usec = cur_tv.tv_usec + 500000;
+
+		if (tv.tv_usec >= 1000000) {
+			tv.tv_sec++;
+			tv.tv_usec -= 1000000;
+		}
+
+		add_timeout(&tv, state_init, client, 0, 0);
+		return;
+	}
+
+	/*
+	 * A number that looks negative here is really just very large,
+	 * because the lease expiry offset is unsigned.
+	 */
+	if (client->new->expiry < 0)
+		client->new->expiry = TIME_MAX;
+
+	/* Take the server-provided renewal time if there is one. */
+	oc = lookup_option (&dhcp_universe, client -> new -> options,
+			    DHO_DHCP_RENEWAL_TIME);
+	if (oc &&
+	    evaluate_option_cache (&ds, packet, (struct lease *)0, client,
+				   packet -> options, client -> new -> options,
+				   &global_scope, oc, MDL)) {
+		if (ds.len > 3)
+			client -> new -> renewal = getULong (ds.data);
+		else
+			client -> new -> renewal = 0;
+		data_string_forget (&ds, MDL);
+	} else
+			client -> new -> renewal = 0;
+
+	/* If it wasn't specified by the server, calculate it. */
+	if (!client -> new -> renewal)
+		client -> new -> renewal = client -> new -> expiry / 2 + 1;
+
+	if (client -> new -> renewal <= 0)
+		client -> new -> renewal = TIME_MAX;
+
+	/* Now introduce some randomness to the renewal time: */
+	if (client->new->renewal <= ((TIME_MAX / 3) - 3))
+		client->new->renewal = (((client->new->renewal * 3) + 3) / 4) +
+				(((random() % client->new->renewal) + 3) / 4);
+
+	/* Same deal with the rebind time. */
+	oc = lookup_option (&dhcp_universe, client -> new -> options,
+			    DHO_DHCP_REBINDING_TIME);
+	if (oc &&
+	    evaluate_option_cache (&ds, packet, (struct lease *)0, client,
+				   packet -> options, client -> new -> options,
+				   &global_scope, oc, MDL)) {
+		if (ds.len > 3)
+			client -> new -> rebind = getULong (ds.data);
+		else
+			client -> new -> rebind = 0;
+		data_string_forget (&ds, MDL);
+	} else
+			client -> new -> rebind = 0;
+
+	if (client -> new -> rebind <= 0) {
+		if (client -> new -> expiry <= TIME_MAX / 7)
+			client -> new -> rebind =
+					client -> new -> expiry * 7 / 8;
+		else
+			client -> new -> rebind =
+					client -> new -> expiry / 8 * 7;
+	}
+
+	/* Make sure our randomness didn't run the renewal time past the
+	   rebind time. */
+	if (client -> new -> renewal > client -> new -> rebind) {
+		if (client -> new -> rebind <= TIME_MAX / 3)
+			client -> new -> renewal =
+					client -> new -> rebind * 3 / 4;
+		else
+			client -> new -> renewal =
+					client -> new -> rebind / 4 * 3;
+	}
+
+	client -> new -> expiry += cur_time;
+	/* Lease lengths can never be negative. */
+	if (client -> new -> expiry < cur_time)
+		client -> new -> expiry = TIME_MAX;
+	client -> new -> renewal += cur_time;
+	if (client -> new -> renewal < cur_time)
+		client -> new -> renewal = TIME_MAX;
+	client -> new -> rebind += cur_time;
+	if (client -> new -> rebind < cur_time)
+		client -> new -> rebind = TIME_MAX;
+
+	bind_lease (client);
+}
+
+void bind_lease (client)
+	struct client_state *client;
+{
+	struct timeval tv;
+
+	/* Remember the medium. */
+	client->new->medium = client->medium;
+
+	/* Run the client script with the new parameters. */
+	script_init(client, (client->state == S_REQUESTING ? "BOUND" :
+			     (client->state == S_RENEWING ? "RENEW" :
+			      (client->state == S_REBOOTING ? "REBOOT" :
+			       "REBIND"))),
+		    client->new->medium);
+	if (client->active && client->state != S_REBOOTING)
+		script_write_params(client, "old_", client->active);
+	script_write_params(client, "new_", client->new);
+	script_write_requested(client);
+	if (client->alias)
+		script_write_params(client, "alias_", client->alias);
+
+	/* If the BOUND/RENEW code detects another machine using the
+	   offered address, it exits nonzero.  We need to send a
+	   DHCPDECLINE and toss the lease. */
+	if (script_go(client)) {
+		make_decline(client, client->new);
+		send_decline(client);
+		destroy_client_lease(client->new);
+		client->new = NULL;
+		if (onetry) {
+			if (!quiet) {
+				log_info("Unable to obtain a lease on first "
+					 "try (declined).  Exiting.");
+			}
+
+#if defined (CALL_SCRIPT_ON_ONETRY_FAIL)
+			/* Let's call a script and we're done */
+			script_init(client, "FAIL", (struct string_list *)0);
+			script_go(client);
+#endif
+			finish(2);
+		} else {
+			struct timeval tv;
+			tv.tv_sec = cur_tv.tv_sec + decline_wait_time;
+			tv.tv_usec = cur_tv.tv_usec;
+			add_timeout(&tv, state_init, client, 0, 0);
+			return;
+		}
+	}
+
+	/* Write out the new lease if it has been long enough. */
+	if (!client->last_write ||
+	    (cur_time - client->last_write) >= MIN_LEASE_WRITE)
+		write_client_lease(client, client->new, 0, 1);
+
+	/* Replace the old active lease with the new one. */
+	if (client->active) {
+		if (client->active->is_static) {
+			// We need to preserve the fallback lease in case
+			// we lose DHCP service again.
+			add_to_tail(&client->leases, client->active);
+		} else {
+			destroy_client_lease(client->active);
+		}
+	}
+
+	client->active = client->new;
+	client->new = NULL;
+
+	/* Set up a timeout to start the renewal process. */
+	tv.tv_sec = client->active->renewal;
+	tv.tv_usec = ((client->active->renewal - cur_tv.tv_sec) > 1) ?
+			random() % 1000000 : cur_tv.tv_usec;
+	add_timeout(&tv, state_bound, client, 0, 0);
+
+	log_info("bound to %s -- renewal in %ld seconds.",
+	      piaddr(client->active->address),
+	      (long)(client->active->renewal - cur_time));
+	client->state = S_BOUND;
+	reinitialize_interfaces();
+	detach();
+#if defined (NSUPDATE)
+	if (client->config->do_forward_update)
+		dhclient_schedule_updates(client, &client->active->address, 1);
+#endif /* defined NSUPDATE */
+
+}
+
+/* state_bound is called when we've successfully bound to a particular
+   lease, but the renewal time on that lease has expired.   We are
+   expected to unicast a DHCPREQUEST to the server that gave us our
+   original lease. */
+
+void state_bound (cpp)
+	void *cpp;
+{
+	struct client_state *client = cpp;
+	struct option_cache *oc;
+	struct data_string ds;
+
+	ASSERT_STATE(state, S_BOUND);
+
+	/* T1 has expired. */
+	make_request (client, client -> active);
+	client -> xid = client -> packet.xid;
+
+	memset (&ds, 0, sizeof ds);
+	oc = lookup_option (&dhcp_universe, client -> active -> options,
+			    DHO_DHCP_SERVER_IDENTIFIER);
+	if (oc &&
+	    evaluate_option_cache (&ds, (struct packet *)0, (struct lease *)0,
+				   client, (struct option_state *)0,
+				   client -> active -> options,
+				   &global_scope, oc, MDL)) {
+		if (ds.len > 3) {
+			memcpy (client -> destination.iabuf, ds.data, 4);
+			client -> destination.len = 4;
+		} else
+			client -> destination = iaddr_broadcast;
+
+		data_string_forget (&ds, MDL);
+	} else
+		client -> destination = iaddr_broadcast;
+
+	client -> first_sending = cur_time;
+	client -> interval = client -> config -> initial_interval;
+	client -> state = S_RENEWING;
+
+	/* Send the first packet immediately. */
+	send_request (client);
+}
+
+/* state_stop is called when we've been told to shut down.   We unconfigure
+   the interfaces, and then stop operating until told otherwise. */
+
+void state_stop (cpp)
+	void *cpp;
+{
+	struct client_state *client = cpp;
+
+	client->pending = P_NONE;
+
+	/* Cancel all timeouts. */
+	cancel_timeout(state_selecting, client);
+	cancel_timeout(send_discover, client);
+	cancel_timeout(send_request, client);
+	cancel_timeout(state_bound, client);
+
+	/* If we have an address, unconfigure it. */
+	if (client->active) {
+		script_init(client, "STOP", client->active->medium);
+		script_write_params(client, "old_", client->active);
+		script_write_requested(client);
+		if (client->alias)
+			script_write_params(client, "alias_", client->alias);
+		script_go(client);
+	}
+}
+
+int commit_leases ()
+{
+	return 0;
+}
+
+int write_lease (lease)
+	struct lease *lease;
+{
+	return 0;
+}
+
+int write_host (host)
+	struct host_decl *host;
+{
+	return 0;
+}
+
+void db_startup (testp)
+	int testp;
+{
+}
+
+void bootp (packet)
+	struct packet *packet;
+{
+	struct iaddrmatchlist *ap;
+	char addrbuf[4*16];
+	char maskbuf[4*16];
+
+	if (packet -> raw -> op != BOOTREPLY)
+		return;
+
+	/* If there's a reject list, make sure this packet's sender isn't
+	   on it. */
+	for (ap = packet -> interface -> client -> config -> reject_list;
+	     ap; ap = ap -> next) {
+		if (addr_match(&packet->client_addr, &ap->match)) {
+
+		        /* piaddr() returns its result in a static
+			   buffer sized 4*16 (see common/inet.c). */
+
+		        strcpy(addrbuf, piaddr(ap->match.addr));
+		        strcpy(maskbuf, piaddr(ap->match.mask));
+
+			log_info("BOOTREPLY from %s rejected by rule %s "
+				 "mask %s.", piaddr(packet->client_addr),
+				 addrbuf, maskbuf);
+			return;
+		}
+	}
+
+	dhcpoffer (packet);
+
+}
+
+void dhcp (packet)
+	struct packet *packet;
+{
+	struct iaddrmatchlist *ap;
+	void (*handler) (struct packet *);
+	const char *type;
+	char addrbuf[4*16];
+	char maskbuf[4*16];
+
+	switch (packet -> packet_type) {
+	      case DHCPOFFER:
+		handler = dhcpoffer;
+		type = "DHCPOFFER";
+		break;
+
+	      case DHCPNAK:
+		handler = dhcpnak;
+		type = "DHCPNACK";
+		break;
+
+	      case DHCPACK:
+		handler = dhcpack;
+		type = "DHCPACK";
+		break;
+
+	      default:
+		return;
+	}
+
+	/* If there's a reject list, make sure this packet's sender isn't
+	   on it. */
+	for (ap = packet -> interface -> client -> config -> reject_list;
+	     ap; ap = ap -> next) {
+		if (addr_match(&packet->client_addr, &ap->match)) {
+
+		        /* piaddr() returns its result in a static
+			   buffer sized 4*16 (see common/inet.c). */
+
+		        strcpy(addrbuf, piaddr(ap->match.addr));
+		        strcpy(maskbuf, piaddr(ap->match.mask));
+
+			log_info("%s from %s rejected by rule %s mask %s.",
+				 type, piaddr(packet->client_addr),
+				 addrbuf, maskbuf);
+			return;
+		}
+	}
+	(*handler) (packet);
+}
+
+#ifdef DHCPv6
+void
+dhcpv6(struct packet *packet) {
+	struct iaddrmatchlist *ap;
+	struct client_state *client;
+	char addrbuf[sizeof("ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff")];
+
+	/* Silently drop bogus messages. */
+	if (packet->dhcpv6_msg_type >= dhcpv6_type_name_max)
+		return;
+
+	/* Discard, with log, packets from quenched sources. */
+	for (ap = packet->interface->client->config->reject_list ;
+	     ap ; ap = ap->next) {
+		if (addr_match(&packet->client_addr, &ap->match)) {
+			strcpy(addrbuf, piaddr(packet->client_addr));
+			log_info("%s from %s rejected by rule %s",
+				 dhcpv6_type_names[packet->dhcpv6_msg_type],
+				 addrbuf,
+				 piaddrmask(&ap->match.addr, &ap->match.mask));
+			return;
+		}
+	}
+
+	/* Screen out nonsensical messages. */
+	switch(packet->dhcpv6_msg_type) {
+#ifdef DHCP4o6
+	      case DHCPV6_DHCPV4_RESPONSE:
+		if (dhcpv4_over_dhcpv6) {
+		  log_info("RCV: %s message on %s from %s.",
+			   dhcpv6_type_names[packet->dhcpv6_msg_type],
+			   packet->interface->name,
+			   piaddr(packet->client_addr));
+		  forw_dhcpv4_response(packet);
+		}
+		return;
+#endif
+	      case DHCPV6_ADVERTISE:
+	      case DHCPV6_RECONFIGURE:
+		if (stateless)
+		  return;
+	      /* Falls through */
+	      case DHCPV6_REPLY:
+		log_info("RCV: %s message on %s from %s.",
+			 dhcpv6_type_names[packet->dhcpv6_msg_type],
+			 packet->interface->name, piaddr(packet->client_addr));
+		break;
+
+	      default:
+		return;
+	}
+
+	/* Find a client state that matches the incoming XID. */
+	for (client = packet->interface->client ; client ;
+	     client = client->next) {
+		if (memcmp(&client->dhcpv6_transaction_id,
+			   packet->dhcpv6_transaction_id, 3) == 0) {
+			client->v6_handler(packet, client);
+			return;
+		}
+	}
+
+	/* XXX: temporary log for debugging */
+	log_info("Packet received, but nothing done with it.");
+}
+
+#ifdef DHCP4o6
+/*
+ * \brief Forward a DHCPv4-response to the DHCPv4 client.
+ *  (DHCPv6 client function)
+ *
+ * The DHCPv6 client receives a DHCPv4-response which is forwarded
+ * to the DHCPv4 client.
+ * Format: address:16 + DHCPv4 message content
+ * (we have no state to keep the address so it is transported in
+ *  DHCPv6 <-> DHCPv6 inter-process messages)
+ *
+ * \param packet the DHCPv4-response packet
+ */
+static void forw_dhcpv4_response(struct packet *packet)
+{
+	struct option_cache *oc;
+	struct data_string enc_opt_data;
+	struct data_string ds;
+	int cc;
+
+	/*
+	 * Discard if relay is not ready.
+	 */
+	if (dhcp4o6_state == -1) {
+		log_info("forw_dhcpv4_response: not ready.");
+		return;
+	}
+
+	if (packet->client_addr.len != 16) {
+		log_error("forw_dhcpv4_response: bad address");
+		return;
+	}
+
+	/*
+	 * Get our encapsulated DHCPv4 message.
+	 */
+	oc = lookup_option(&dhcpv6_universe, packet->options, D6O_DHCPV4_MSG);
+	if (oc == NULL) {
+		log_info("DHCPv4-response from %s missing "
+			 "DHCPv4 Message option.",
+			 piaddr(packet->client_addr));
+		return;
+	}
+
+	memset(&enc_opt_data, 0, sizeof(enc_opt_data));
+	if (!evaluate_option_cache(&enc_opt_data, NULL, NULL, NULL,
+				   NULL, NULL, &global_scope, oc, MDL)) {
+		log_error("forw_dhcpv4_response: error evaluating "
+			  "DHCPv4 message.");
+		data_string_forget(&enc_opt_data, MDL);
+		return;
+	}
+
+	if (enc_opt_data.len < DHCP_FIXED_NON_UDP) {
+		log_error("forw_dhcpv4_response: "
+			  "no memory for encapsulated packet.");
+		data_string_forget(&enc_opt_data, MDL);
+		return;
+	}
+
+	/*
+	 * Append address.
+	 */
+	memset(&ds, 0, sizeof(ds));
+	if (!buffer_allocate(&ds.buffer, enc_opt_data.len + 16, MDL)) {
+		log_error("forw_dhcpv4_response: no memory buffer.");
+		data_string_forget(&enc_opt_data, MDL);
+		return;
+	}
+	ds.data = ds.buffer->data;
+	ds.len = enc_opt_data.len + 16;
+	memcpy(ds.buffer->data, enc_opt_data.data, enc_opt_data.len);
+	memcpy(ds.buffer->data + enc_opt_data.len,
+	       packet->client_addr.iabuf, 16);
+	data_string_forget(&enc_opt_data, MDL);
+
+	/*
+	 * Forward them.
+	 */
+	cc = send(dhcp4o6_fd, ds.data, ds.len, 0);
+	if (cc < 0)
+		log_error("forw_dhcpv4_response: send(): %m");
+
+	data_string_forget(&ds, MDL);
+}
+
+/*
+ * \brief Receive a DHCPv4-response from the DHCPv6 client.
+ *  (DHCPv4 client function)
+ *
+ * The DHCPv4 client receives a DHCPv4-response forwarded
+ * by the DHCPv6 client (using \ref forw_dhcpv4_response())
+ *
+ * \param raw the DHCPv4-response raw packet
+ */
+static void recv_dhcpv4_response(struct data_string *raw)
+{
+	struct packet *packet;
+	struct iaddr from;
+
+	if (interfaces == NULL) {
+		log_error("recv_dhcpv4_response: no interfaces.");
+		return;
+	}
+
+	from.len = 16;
+	memcpy(from.iabuf, raw->data + (raw->len - 16), 16);
+
+	/*
+	 * Build a packet structure.
+	 */
+	packet = NULL;
+	if (!packet_allocate(&packet, MDL)) {
+		log_error("recv_dhcpv4_response: no memory for packet.");
+		return;
+	}
+
+	packet->raw = (struct dhcp_packet *) raw->data;
+	packet->packet_length = raw->len - 16;
+	packet->client_port = remote_port;
+	packet->client_addr = from;
+	interface_reference(&packet->interface, interfaces, MDL);
+
+	/* Allocate packet->options now so it is non-null for all packets */
+	if (!option_state_allocate (&packet->options, MDL)) {
+		log_error("recv_dhcpv4_response: no memory for options.");
+		packet_dereference (&packet, MDL);
+		return;
+	}
+
+	/* If there's an option buffer, try to parse it. */
+	if (packet->packet_length >= DHCP_FIXED_NON_UDP + 4) {
+		struct option_cache *op;
+		if (!parse_options(packet)) {
+			if (packet->options)
+				option_state_dereference
+					(&packet->options, MDL);
+			packet_dereference (&packet, MDL);
+			return;
+		}
+
+		if (packet->options_valid &&
+		    (op = lookup_option(&dhcp_universe,
+					packet->options,
+					DHO_DHCP_MESSAGE_TYPE))) {
+			struct data_string dp;
+			memset(&dp, 0, sizeof dp);
+			evaluate_option_cache(&dp, packet, NULL, NULL,
+					      packet->options, NULL,
+					      NULL, op, MDL);
+			if (dp.len > 0)
+				packet->packet_type = dp.data[0];
+			else
+				packet->packet_type = 0;
+			data_string_forget(&dp, MDL);
+		}
+	}
+
+	if (validate_packet(packet) != 0) {
+		if (packet->packet_type)
+			dhcp(packet);
+		else
+			bootp(packet);
+	}
+
+	/* If the caller kept the packet, they'll have upped the refcnt. */
+	packet_dereference(&packet, MDL);
+}
+#endif /* DHCP4o6 */
+#endif /* DHCPv6 */
+
+void dhcpoffer (packet)
+	struct packet *packet;
+{
+	struct interface_info *ip = packet -> interface;
+	struct client_state *client;
+	struct client_lease *lease, *lp;
+	struct option **req;
+	int i;
+	int stop_selecting;
+	const char *name = packet -> packet_type ? "DHCPOFFER" : "BOOTREPLY";
+	char obuf [1024];
+	struct timeval tv;
+
+#ifdef DEBUG_PACKET
+	dump_packet (packet);
+#endif
+
+	/* Find a client state that matches the xid... */
+	for (client = ip -> client; client; client = client -> next)
+		if (client -> xid == packet -> raw -> xid)
+			break;
+
+	/* If we're not receptive to an offer right now, or if the offer
+	   has an unrecognizable transaction id, then just drop it. */
+	if (!client ||
+	    client -> state != S_SELECTING ||
+	    (packet -> interface -> hw_address.hlen - 1 !=
+	     packet -> raw -> hlen) ||
+	    (memcmp (&packet -> interface -> hw_address.hbuf [1],
+		     packet -> raw -> chaddr, packet -> raw -> hlen))) {
+#if defined (DEBUG)
+		log_debug ("%s in wrong transaction.", name);
+#endif
+		return;
+	}
+
+	sprintf (obuf, "%s of %s from %s", name,
+		 inet_ntoa(packet->raw->yiaddr),
+		 piaddr(packet->client_addr));
+
+	/* If this lease doesn't supply the minimum required DHCPv4 parameters,
+	 * ignore it.
+	 */
+	req = client->config->required_options;
+	if (req != NULL) {
+		for (i = 0 ; req[i] != NULL ; i++) {
+			if ((req[i]->universe == &dhcp_universe) &&
+			    !lookup_option(&dhcp_universe, packet->options,
+					   req[i]->code)) {
+				struct option *option = NULL;
+				unsigned code = req[i]->code;
+
+				option_code_hash_lookup(&option,
+							dhcp_universe.code_hash,
+							&code, 0, MDL);
+
+				if (option)
+					log_info("%s: no %s option.", obuf,
+						 option->name);
+				else
+					log_info("%s: no unknown-%u option.",
+						 obuf, code);
+
+				option_dereference(&option, MDL);
+
+				return;
+			}
+		}
+	}
+
+	/* If we've already seen this lease, don't record it again. */
+	for (lease = client -> offered_leases; lease; lease = lease -> next) {
+		if (lease -> address.len == sizeof packet -> raw -> yiaddr &&
+		    !memcmp (lease -> address.iabuf,
+			     &packet -> raw -> yiaddr, lease -> address.len)) {
+			log_debug ("%s: already seen.", obuf);
+			return;
+		}
+	}
+
+	lease = packet_to_lease (packet, client);
+	if (!lease) {
+		log_info ("%s: packet_to_lease failed.", obuf);
+		return;
+	}
+
+	/* log it now, so it emits before the request goes out */
+	log_info("%s", obuf);
+
+	/* If this lease was acquired through a BOOTREPLY, record that
+	   fact. */
+	if (!packet -> options_valid || !packet -> packet_type)
+		lease -> is_bootp = 1;
+
+	/* Record the medium under which this lease was offered. */
+	lease -> medium = client -> medium;
+
+	/* Figure out when we're supposed to stop selecting. */
+	stop_selecting = (client -> first_sending +
+			  client -> config -> select_interval);
+
+	/* If this is the lease we asked for, put it at the head of the
+	   list, and don't mess with the arp request timeout. */
+	if (lease -> address.len == client -> requested_address.len &&
+	    !memcmp (lease -> address.iabuf,
+		     client -> requested_address.iabuf,
+		     client -> requested_address.len)) {
+		lease -> next = client -> offered_leases;
+		client -> offered_leases = lease;
+	} else {
+		/* Put the lease at the end of the list. */
+		lease -> next = (struct client_lease *)0;
+		if (!client -> offered_leases)
+			client -> offered_leases = lease;
+		else {
+			for (lp = client -> offered_leases; lp -> next;
+			     lp = lp -> next)
+				;
+			lp -> next = lease;
+		}
+	}
+
+	/* If the selecting interval has expired, go immediately to
+	   state_selecting().  Otherwise, time out into
+	   state_selecting at the select interval. */
+	if (stop_selecting <= cur_tv.tv_sec)
+		state_selecting (client);
+	else {
+		tv.tv_sec = stop_selecting;
+		tv.tv_usec = cur_tv.tv_usec;
+		add_timeout(&tv, state_selecting, client, 0, 0);
+		cancel_timeout(send_discover, client);
+	}
+}
+
+/* Allocate a client_lease structure and initialize it from the parameters
+   in the specified packet. */
+
+struct client_lease *packet_to_lease (packet, client)
+	struct packet *packet;
+	struct client_state *client;
+{
+	struct client_lease *lease;
+	unsigned i;
+	struct option_cache *oc;
+	struct option *option = NULL;
+	struct data_string data;
+
+	lease = (struct client_lease *)new_client_lease (MDL);
+
+	if (!lease) {
+		log_error("packet_to_lease: no memory to record lease.\n");
+		return NULL;
+	}
+
+	memset(lease, 0, sizeof(*lease));
+
+	/* Copy the lease options. */
+	option_state_reference(&lease->options, packet->options, MDL);
+
+	lease->address.len = sizeof(packet->raw->yiaddr);
+	memcpy(lease->address.iabuf, &packet->raw->yiaddr,
+	       lease->address.len);
+
+	lease->next_srv_addr.len = sizeof(packet->raw->siaddr);
+	memcpy(lease->next_srv_addr.iabuf, &packet->raw->siaddr,
+	       lease->next_srv_addr.len);
+
+	memset(&data, 0, sizeof(data));
+
+	if (client -> config -> vendor_space_name) {
+		i = DHO_VENDOR_ENCAPSULATED_OPTIONS;
+
+		/* See if there was a vendor encapsulation option. */
+		oc = lookup_option (&dhcp_universe, lease -> options, i);
+		if (oc &&
+		    client -> config -> vendor_space_name &&
+		    evaluate_option_cache (&data, packet,
+					   (struct lease *)0, client,
+					   packet -> options, lease -> options,
+					   &global_scope, oc, MDL)) {
+			if (data.len) {
+				if (!option_code_hash_lookup(&option,
+						dhcp_universe.code_hash,
+						&i, 0, MDL))
+					log_fatal("Unable to find VENDOR "
+						  "option (%s:%d).", MDL);
+				parse_encapsulated_suboptions
+					(packet -> options, option,
+					 data.data, data.len, &dhcp_universe,
+					 client -> config -> vendor_space_name
+						);
+
+				option_dereference(&option, MDL);
+			}
+			data_string_forget (&data, MDL);
+		}
+	} else
+		i = 0;
+
+	/* Figure out the overload flag. */
+	oc = lookup_option (&dhcp_universe, lease -> options,
+			    DHO_DHCP_OPTION_OVERLOAD);
+	if (oc &&
+	    evaluate_option_cache (&data, packet, (struct lease *)0, client,
+				   packet -> options, lease -> options,
+				   &global_scope, oc, MDL)) {
+		if (data.len > 0)
+			i = data.data [0];
+		else
+			i = 0;
+		data_string_forget (&data, MDL);
+	} else
+		i = 0;
+
+	/* If the server name was filled out, copy it. */
+	if (!(i & 2) && packet -> raw -> sname [0]) {
+		unsigned len;
+		/* Don't count on the NUL terminator. */
+		for (len = 0; len < DHCP_SNAME_LEN; len++)
+			if (!packet -> raw -> sname [len])
+				break;
+		lease -> server_name = dmalloc (len + 1, MDL);
+		if (!lease -> server_name) {
+			log_error ("dhcpoffer: no memory for server name.\n");
+			destroy_client_lease (lease);
+			return (struct client_lease *)0;
+		} else {
+			memcpy (lease -> server_name,
+				packet -> raw -> sname, len);
+			lease -> server_name [len] = 0;
+		}
+	}
+
+	/* Ditto for the filename. */
+	if (!(i & 1) && packet -> raw -> file [0]) {
+		unsigned len;
+		/* Don't count on the NUL terminator. */
+		for (len = 0; len < DHCP_FILE_LEN; len++)
+			if (!packet -> raw -> file [len])
+				break;
+		lease -> filename = dmalloc (len + 1, MDL);
+		if (!lease -> filename) {
+			log_error ("dhcpoffer: no memory for filename.\n");
+			destroy_client_lease (lease);
+			return (struct client_lease *)0;
+		} else {
+			memcpy (lease -> filename,
+				packet -> raw -> file, len);
+			lease -> filename [len] = 0;
+		}
+	}
+
+	execute_statements_in_scope(NULL, (struct packet *)packet, NULL,
+				    client, lease->options, lease->options,
+				    &global_scope, client->config->on_receipt,
+				    NULL, NULL);
+
+	return lease;
+}
+
+void dhcpnak (packet)
+	struct packet *packet;
+{
+	struct interface_info *ip = packet -> interface;
+	struct client_state *client;
+
+	/* Find a client state that matches the xid... */
+	for (client = ip -> client; client; client = client -> next)
+		if (client -> xid == packet -> raw -> xid)
+			break;
+
+	/* If we're not receptive to an offer right now, or if the offer
+	   has an unrecognizable transaction id, then just drop it. */
+	if (!client ||
+	    (packet -> interface -> hw_address.hlen - 1 !=
+	     packet -> raw -> hlen) ||
+	    (memcmp (&packet -> interface -> hw_address.hbuf [1],
+		     packet -> raw -> chaddr, packet -> raw -> hlen))) {
+#if defined (DEBUG)
+		log_debug ("DHCPNAK in wrong transaction.");
+#endif
+		return;
+	}
+
+	if (client -> state != S_REBOOTING &&
+	    client -> state != S_REQUESTING &&
+	    client -> state != S_RENEWING &&
+	    client -> state != S_REBINDING) {
+#if defined (DEBUG)
+		log_debug ("DHCPNAK in wrong state.");
+#endif
+		return;
+	}
+
+	log_info ("DHCPNAK from %s", piaddr (packet -> client_addr));
+
+	if (!client -> active) {
+#if defined (DEBUG)
+		log_info ("DHCPNAK with no active lease.\n");
+#endif
+		return;
+	}
+
+	/* If we get a DHCPNAK, we use the EXPIRE dhclient-script state
+	 * to indicate that we want all old bindings to be removed.  (It
+	 * is possible that we may get a NAK while in the RENEW state,
+	 * so we might have bindings active at that time)
+	 */
+	script_init(client, "EXPIRE", NULL);
+	script_write_params(client, "old_", client->active);
+	script_write_requested(client);
+	if (client->alias)
+		script_write_params(client, "alias_", client->alias);
+	script_go(client);
+
+	destroy_client_lease (client -> active);
+	client -> active = (struct client_lease *)0;
+
+	/* Stop sending DHCPREQUEST packets... */
+	cancel_timeout (send_request, client);
+
+	/* On some scripts, 'EXPIRE' causes the interface to be ifconfig'd
+	 * down (this expunges any routes and arp cache).  This makes the
+	 * interface unusable by state_init(), which we call next.  So, we
+	 * need to 'PREINIT' the interface to bring it back up.
+	 */
+	script_init(client, "PREINIT", NULL);
+	if (client->alias)
+		script_write_params(client, "alias_", client->alias);
+	script_go(client);
+
+	client -> state = S_INIT;
+	state_init (client);
+}
+
+/* Send out a DHCPDISCOVER packet, and set a timeout to send out another
+   one after the right interval has expired.  If we don't get an offer by
+   the time we reach the panic interval, call the panic function. */
+
+void send_discover (cpp)
+	void *cpp;
+{
+	struct client_state *client = cpp;
+
+	int result;
+	int interval;
+	int increase = 1;
+	struct timeval tv;
+
+	/* Figure out how long it's been since we started transmitting. */
+	interval = cur_time - client -> first_sending;
+
+	/* If we're past the panic timeout, call the script and tell it
+	   we haven't found anything for this interface yet. */
+	if (interval > client -> config -> timeout) {
+		state_panic (client);
+		return;
+	}
+
+	/* If we're selecting media, try the whole list before doing
+	   the exponential backoff, but if we've already received an
+	   offer, stop looping, because we obviously have it right. */
+	if (!client -> offered_leases &&
+	    client -> config -> media) {
+		int fail = 0;
+	      again:
+		if (client -> medium) {
+			client -> medium = client -> medium -> next;
+			increase = 0;
+		}
+		if (!client -> medium) {
+			if (fail)
+				log_fatal ("No valid media types for %s!",
+				       client -> interface -> name);
+			client -> medium =
+				client -> config -> media;
+			increase = 1;
+		}
+
+		log_info ("Trying medium \"%s\" %d",
+			  client -> medium -> string, increase);
+		script_init(client, "MEDIUM", client -> medium);
+		if (script_go(client)) {
+			fail = 1;
+			goto again;
+		}
+	}
+
+	/* If we're supposed to increase the interval, do so.  If it's
+	   currently zero (i.e., we haven't sent any packets yet), set
+	   it to initial_interval; otherwise, add to it a random number
+	   between zero and two times itself.  On average, this means
+	   that it will double with every transmission. */
+	if (increase) {
+		if (!client->interval)
+			client->interval = client->config->initial_interval;
+		else
+			client->interval += random() % (2 * client->interval);
+
+		/* Don't backoff past cutoff. */
+		if (client->interval > client->config->backoff_cutoff)
+			client->interval = (client->config->backoff_cutoff / 2)
+				 + (random() % client->config->backoff_cutoff);
+	} else if (!client->interval)
+		client->interval = client->config->initial_interval;
+
+	/* If the backoff would take us to the panic timeout, just use that
+	   as the interval. */
+	if (cur_time + client -> interval >
+	    client -> first_sending + client -> config -> timeout)
+		client -> interval =
+			(client -> first_sending +
+			 client -> config -> timeout) - cur_time + 1;
+
+	/* Record the number of seconds since we started sending. */
+	if (interval < 65536)
+		client -> packet.secs = htons (interval);
+	else
+		client -> packet.secs = htons (65535);
+	client -> secs = client -> packet.secs;
+
+#if defined(DHCPv6) && defined(DHCP4o6)
+	if (dhcpv4_over_dhcpv6) {
+		log_info ("DHCPDISCOVER interval %ld",
+			  (long)(client -> interval));
+	} else
+#endif
+	log_info ("DHCPDISCOVER on %s to %s port %d interval %ld",
+	      client -> name ? client -> name : client -> interface -> name,
+	      inet_ntoa (sockaddr_broadcast.sin_addr),
+	      ntohs (sockaddr_broadcast.sin_port), (long)(client -> interval));
+
+	/* Send out a packet. */
+#if defined(DHCPv6) && defined(DHCP4o6)
+	if (dhcpv4_over_dhcpv6) {
+		result = send_dhcpv4_query(client, 1);
+	} else
+#endif
+	result = send_packet(client->interface, NULL, &client->packet,
+			     client->packet_length, inaddr_any,
+                             &sockaddr_broadcast, NULL);
+        if (result < 0) {
+#if defined(DHCPv6) && defined(DHCP4o6)
+		if (dhcpv4_over_dhcpv6) {
+			log_error("%s:%d: Failed to send %d byte long packet.",
+				  MDL, client->packet_length);
+		} else
+#endif
+		log_error("%s:%d: Failed to send %d byte long packet over %s "
+			  "interface.", MDL, client->packet_length,
+			  client->interface->name);
+	}
+
+	/*
+	 * If we used 0 microseconds here, and there were other clients on the
+	 * same network with a synchronized local clock (ntp), and a similar
+	 * zero-microsecond-scheduler behavior, then we could be participating
+	 * in a sub-second DOS ttck.
+	 */
+	tv.tv_sec = cur_tv.tv_sec + client->interval;
+	tv.tv_usec = client->interval > 1 ? random() % 1000000 : cur_tv.tv_usec;
+	add_timeout(&tv, send_discover, client, 0, 0);
+}
+
+
+/*
+ * \brief Remove leases from a list of leases which duplicate a given lease
+ *
+ * Searches through a linked-list of leases, remove the first one matches the
+ * given lease's address and value of is_static.   The latter test is done
+ * so we only remove leases that are from the same source (i.e server/lease file
+ *  vs config file).  This ensures we do not discard "fallback" config file leases
+ * that happen to match non-config file leases.
+ *
+ * \param lease_list list of leases to clean
+ * \param lease lease for which duplicates should be removed
+ */
+void discard_duplicate (struct client_lease** lease_list, struct client_lease* lease) {
+	struct client_lease *cur, *prev, *next;
+
+	if (!lease_list || !lease) {
+		return;
+	}
+
+	prev = (struct client_lease *)0;
+	for (cur = *lease_list; cur; cur = next) {
+		next = cur->next;
+		if ((cur->is_static == lease->is_static) &&
+		    (cur->address.len == lease->address.len &&
+		     !memcmp (cur->address.iabuf, lease->address.iabuf,
+			      lease->address.len))) {
+			if (prev)
+				prev->next = next;
+			else
+				*lease_list = next;
+
+			destroy_client_lease (cur);
+			break;
+		} else {
+			prev = cur;
+		}
+	}
+}
+
+/*
+ * \brief Add a given lease to the end of list of leases
+ *
+ * Searches through a linked-list of leases, removing any that match the
+ * given lease's address and value of is_static.  The latter test is done
+ * so we only remove leases that are from the same source (i.e server/lease file
+ *  vs config file).  This ensures we do not discard "fallback" config file leases
+ * that happen to match non-config file leases.
+ *
+ * \param lease_list list of leases to clean
+ * \param lease lease for which duplicates should be removed
+ */
+void add_to_tail(struct client_lease** lease_list,
+		 struct client_lease* lease)
+{
+	if (!lease_list || !lease) {
+		return;
+	}
+
+	/* If there is already a lease for this address and
+	* is_static value, toss discard it.  This ensures
+	* we only keep one dynamic and/or one static lease
+	* for a given address. */
+	discard_duplicate(lease_list, lease);
+
+	/* Find the tail */
+	struct client_lease* tail;
+	for (tail = *lease_list; tail && tail->next; tail = tail->next){};
+
+	/* Ensure the tail points nowhere. */
+	lease->next = NULL;
+
+	/* Add to the tail. */
+	if (!tail) {
+		*lease_list = lease;
+	} else {
+		tail->next = lease;
+	}
+}
+
+#if 0
+void dbg_print_lease(char *text, struct client_lease* lease) {
+	if (!lease) {
+		log_debug("%s, lease is null", text);
+	} else {
+		log_debug ("%s: %p addr:%s expires:%ld :is_static? %d",
+			   text, lease, piaddr (lease->address),
+                           (lease->expiry - cur_time),
+			   lease->is_static);
+	}
+}
+#endif
+
+/* state_panic gets called if we haven't received any offers in a preset
+   amount of time.   When this happens, we try to use existing leases that
+   haven't yet expired, and failing that, we call the client script and
+   hope it can do something. */
+
+void state_panic (cpp)
+	void *cpp;
+{
+	struct client_state *client = cpp;
+	struct client_lease *loop;
+	struct client_lease *lp;
+	struct timeval tv;
+
+	loop = lp = client -> active;
+
+	log_info ("No DHCPOFFERS received.");
+
+	/* We may not have an active lease, but we may have some
+	   predefined leases that we can try. */
+	if (!client -> active && client -> leases)
+		goto activate_next;
+
+	/* Run through the list of leases and see if one can be used. */
+	while (client -> active) {
+		if (client -> active -> expiry > cur_time) {
+			log_info ("Trying %s lease %s",
+				  (client -> active -> is_static
+				   ? "fallback" : "recorded"),
+				  piaddr (client -> active -> address));
+			/* Run the client script with the existing
+			   parameters. */
+			script_init(client, "TIMEOUT",
+				     client -> active -> medium);
+			script_write_params(client, "new_", client -> active);
+			script_write_requested(client);
+			if (client -> alias)
+				script_write_params(client, "alias_",
+						    client -> alias);
+
+			/* If the old lease is still good and doesn't
+			   yet need renewal, go into BOUND state and
+			   timeout at the renewal time. */
+			if (!script_go(client)) {
+			    if (cur_time < client -> active -> renewal) {
+				client -> state = S_BOUND;
+				log_info ("bound: renewal in %ld %s.",
+					  (long)(client -> active -> renewal -
+						 cur_time), "seconds");
+				tv.tv_sec = client->active->renewal;
+				tv.tv_usec = ((client->active->renewal -
+						    cur_time) > 1) ?
+						random() % 1000000 :
+						cur_tv.tv_usec;
+				add_timeout(&tv, state_bound, client, 0, 0);
+			    } else {
+				client -> state = S_BOUND;
+				log_info ("bound: immediate renewal.");
+				state_bound (client);
+			    }
+			    reinitialize_interfaces ();
+			    detach ();
+			    return;
+			}
+		}
+
+		/* If there are no other leases, give up. */
+		if (!client -> leases) {
+			client -> leases = client -> active;
+			client -> active = (struct client_lease *)0;
+			break;
+		}
+
+	activate_next:
+		/* Otherwise, put the active lease at the end of the
+		   lease list, and try another lease.. */
+		add_to_tail(&client->leases, client->active);
+
+		client -> active = client -> leases;
+		client -> leases = client -> leases -> next;
+
+		/* If we already tried this lease, we've exhausted the
+		   set of leases, so we might as well give up for
+		   now. */
+		if (client -> active == loop)
+			break;
+		else if (!loop)
+			loop = client -> active;
+	}
+
+	/* No leases were available, or what was available didn't work, so
+	   tell the shell script that we failed to allocate an address,
+	   and try again later. */
+	if (onetry) {
+		if (!quiet) {
+			log_info ("Unable to obtain a lease on first try.%s",
+				  "  Exiting.");
+		}
+
+#if defined (CALL_SCRIPT_ON_ONETRY_FAIL)
+		/* Let's call a script and we're done */
+		script_init(client, "FAIL", (struct string_list *)0);
+		script_go(client);
+#endif
+		finish(2);
+	}
+
+	log_info ("No working leases in persistent database - sleeping.");
+	script_init(client, "FAIL", (struct string_list *)0);
+	if (client -> alias)
+		script_write_params(client, "alias_", client -> alias);
+	script_go(client);
+	client -> state = S_INIT;
+	tv.tv_sec = cur_tv.tv_sec + ((client->config->retry_interval + 1) / 2 +
+		    (random() % client->config->retry_interval));
+	tv.tv_usec = ((tv.tv_sec - cur_tv.tv_sec) > 1) ?
+			random() % 1000000 : cur_tv.tv_usec;
+	add_timeout(&tv, state_init, client, 0, 0);
+	detach ();
+}
+
+void send_request (cpp)
+	void *cpp;
+{
+	struct client_state *client = cpp;
+
+	int result;
+	int interval;
+	struct sockaddr_in destination;
+	struct in_addr from;
+	struct timeval tv;
+	char rip_buf[128];
+	const char* rip_str = "";
+
+	/* Figure out how long it's been since we started transmitting. */
+	interval = cur_time - client -> first_sending;
+
+	/* If we're in the INIT-REBOOT or REQUESTING state and we're
+	   past the reboot timeout, go to INIT and see if we can
+	   DISCOVER an address... */
+	/* XXX In the INIT-REBOOT state, if we don't get an ACK, it
+	   means either that we're on a network with no DHCP server,
+	   or that our server is down.  In the latter case, assuming
+	   that there is a backup DHCP server, DHCPDISCOVER will get
+	   us a new address, but we could also have successfully
+	   reused our old address.  In the former case, we're hosed
+	   anyway.  This is not a win-prone situation. */
+	if ((client -> state == S_REBOOTING ||
+	     client -> state == S_REQUESTING) &&
+	    interval > client -> config -> reboot_timeout) {
+	cancel:
+		client -> state = S_INIT;
+		cancel_timeout (send_request, client);
+		state_init (client);
+		return;
+	}
+
+	/* If we're in the reboot state, make sure the media is set up
+	   correctly. */
+	if (client -> state == S_REBOOTING &&
+	    !client -> medium &&
+	    client -> active -> medium ) {
+		script_init(client, "MEDIUM", client -> active -> medium);
+
+		/* If the medium we chose won't fly, go to INIT state. */
+		if (script_go(client))
+			goto cancel;
+
+		/* Record the medium. */
+		client -> medium = client -> active -> medium;
+	}
+
+	/* If the lease has expired, relinquish the address and go back
+	   to the INIT state. */
+	if (client -> state != S_REQUESTING &&
+	    cur_time > client -> active -> expiry) {
+		/* Run the client script with the new parameters. */
+		script_init(client, "EXPIRE", (struct string_list *)0);
+		script_write_params(client, "old_", client -> active);
+		script_write_requested(client);
+		if (client -> alias)
+			script_write_params(client, "alias_",
+					    client -> alias);
+		script_go(client);
+
+		/* Now do a preinit on the interface so that we can
+		   discover a new address. */
+		script_init(client, "PREINIT", (struct string_list *)0);
+		if (client -> alias)
+			script_write_params(client, "alias_",
+					    client -> alias);
+		script_go(client);
+
+		client -> state = S_INIT;
+		state_init (client);
+		return;
+	}
+
+	/* Do the exponential backoff... */
+	if (!client -> interval)
+		client -> interval = client -> config -> initial_interval;
+	else {
+		client -> interval += ((random () >> 2) %
+				       (2 * client -> interval));
+	}
+
+	/* Don't backoff past cutoff. */
+	if (client -> interval >
+	    client -> config -> backoff_cutoff)
+		client -> interval =
+			((client -> config -> backoff_cutoff / 2)
+			 + ((random () >> 2) %
+					client -> config -> backoff_cutoff));
+
+	/* If the backoff would take us to the expiry time, just set the
+	   timeout to the expiry time. */
+	if (client -> state != S_REQUESTING &&
+	    cur_time + client -> interval > client -> active -> expiry)
+		client -> interval =
+			client -> active -> expiry - cur_time + 1;
+
+	/* If the lease T2 time has elapsed, or if we're not yet bound,
+	   broadcast the DHCPREQUEST rather than unicasting. */
+	if (client -> state == S_REQUESTING ||
+	    client -> state == S_REBOOTING ||
+	    cur_time > client -> active -> rebind)
+		destination.sin_addr = sockaddr_broadcast.sin_addr;
+	else
+		memcpy (&destination.sin_addr.s_addr,
+			client -> destination.iabuf,
+			sizeof destination.sin_addr.s_addr);
+	destination.sin_port = remote_port;
+	destination.sin_family = AF_INET;
+#ifdef HAVE_SA_LEN
+	destination.sin_len = sizeof destination;
+#endif
+
+	if (client -> state == S_RENEWING ||
+	    client -> state == S_REBINDING)
+		memcpy (&from, client -> active -> address.iabuf,
+			sizeof from);
+	else
+		from.s_addr = INADDR_ANY;
+
+	/* Record the number of seconds since we started sending. */
+	if (client -> state == S_REQUESTING)
+		client -> packet.secs = client -> secs;
+	else {
+		if (interval < 65536)
+			client -> packet.secs = htons (interval);
+		else
+			client -> packet.secs = htons (65535);
+	}
+
+#if defined(DHCPv6) && defined(DHCP4o6)
+	if (dhcpv4_over_dhcpv6) {
+		log_info ("DHCPREQUEST");
+	} else
+#endif
+	memset(rip_buf, 0x0, sizeof(rip_buf));
+	if (client->state == S_BOUND || client->state == S_RENEWING ||
+	    client->state == S_REBINDING) {
+		rip_str = inet_ntoa(client->packet.ciaddr);
+	} else {
+		rip_str = piaddr(client->requested_address);
+	}
+
+	strncpy(rip_buf, rip_str, sizeof(rip_buf)-1);
+	log_info ("DHCPREQUEST for %s on %s to %s port %d", rip_buf,
+		  client->name ? client->name : client->interface->name,
+		  inet_ntoa(destination.sin_addr),
+		  ntohs (destination.sin_port));
+
+#if defined(DHCPv6) && defined(DHCP4o6)
+	if (dhcpv4_over_dhcpv6) {
+		int broadcast = 0;
+		if (destination.sin_addr.s_addr == INADDR_BROADCAST)
+			broadcast = 1;
+		result = send_dhcpv4_query(client, broadcast);
+		if (result < 0) {
+			log_error("%s:%d: Failed to send %d byte long packet.",
+				  MDL, client->packet_length);
+		}
+	} else
+#endif
+	if (destination.sin_addr.s_addr != INADDR_BROADCAST &&
+	    fallback_interface) {
+		result = send_packet(fallback_interface, NULL, &client->packet,
+				     client->packet_length, from, &destination,
+				     NULL);
+		if (result < 0) {
+			log_error("%s:%d: Failed to send %d byte long packet "
+				  "over %s interface.", MDL,
+				  client->packet_length,
+				  fallback_interface->name);
+		}
+        }
+	else {
+		/* Send out a packet. */
+		result = send_packet(client->interface, NULL, &client->packet,
+				     client->packet_length, from, &destination,
+				     NULL);
+		if (result < 0) {
+			log_error("%s:%d: Failed to send %d byte long packet"
+				  " over %s interface.", MDL,
+				  client->packet_length,
+				  client->interface->name);
+		}
+        }
+
+	tv.tv_sec = cur_tv.tv_sec + client->interval;
+	tv.tv_usec = ((tv.tv_sec - cur_tv.tv_sec) > 1) ?
+			random() % 1000000 : cur_tv.tv_usec;
+	add_timeout(&tv, send_request, client, 0, 0);
+}
+
+void send_decline (cpp)
+	void *cpp;
+{
+	struct client_state *client = cpp;
+
+	int result;
+
+#if defined(DHCPv6) && defined(DHCP4o6)
+	if (dhcpv4_over_dhcpv6) {
+		log_info ("DHCPDECLINE");
+	} else
+#endif
+	log_info ("DHCPDECLINE of %s on %s to %s port %d",
+		  piaddr(client->requested_address),
+		  (client->name ? client->name : client->interface->name),
+		  inet_ntoa(sockaddr_broadcast.sin_addr),
+		  ntohs(sockaddr_broadcast.sin_port));
+
+	/* Send out a packet. */
+#if defined(DHCPv6) && defined(DHCP4o6)
+	if (dhcpv4_over_dhcpv6) {
+		result = send_dhcpv4_query(client, 1);
+	} else
+#endif
+	result = send_packet(client->interface, NULL, &client->packet,
+			     client->packet_length, inaddr_any,
+			     &sockaddr_broadcast, NULL);
+	if (result < 0) {
+#if defined(DHCPv6) && defined(DHCP4o6)
+		if (dhcpv4_over_dhcpv6) {
+			log_error("%s:%d: Failed to send %d byte long packet.",
+				  MDL, client->packet_length);
+		} else
+#endif
+		log_error("%s:%d: Failed to send %d byte long packet over %s"
+			  " interface.", MDL, client->packet_length,
+			  client->interface->name);
+	}
+}
+
+void send_release (cpp)
+	void *cpp;
+{
+	struct client_state *client = cpp;
+
+	int result;
+	struct sockaddr_in destination;
+	struct in_addr from;
+
+	memcpy (&from, client -> active -> address.iabuf,
+		sizeof from);
+	memcpy (&destination.sin_addr.s_addr,
+		client -> destination.iabuf,
+		sizeof destination.sin_addr.s_addr);
+	destination.sin_port = remote_port;
+	destination.sin_family = AF_INET;
+#ifdef HAVE_SA_LEN
+	destination.sin_len = sizeof destination;
+#endif
+
+	/* Set the lease to end now, so that we don't accidentally
+	   reuse it if we restart before the old expiry time. */
+	client -> active -> expiry =
+		client -> active -> renewal =
+		client -> active -> rebind = cur_time;
+	if (!write_client_lease (client, client -> active, 1, 1)) {
+		log_error ("Can't release lease: lease write failed.");
+		return;
+	}
+
+#if defined(DHCPv6) && defined(DHCP4o6)
+	if (dhcpv4_over_dhcpv6) {
+		log_info ("DHCPRELEASE");
+	} else
+#endif
+	log_info ("DHCPRELEASE of %s on %s to %s port %d",
+		  piaddr(client->active->address),
+		  client->name ? client->name : client->interface->name,
+		  inet_ntoa (destination.sin_addr),
+		  ntohs (destination.sin_port));
+
+#if defined(DHCPv6) && defined(DHCP4o6)
+	if (dhcpv4_over_dhcpv6) {
+		int broadcast = 0;
+		if (destination.sin_addr.s_addr == INADDR_BROADCAST)
+			broadcast = 1;
+		result = send_dhcpv4_query(client, broadcast);
+		if (result < 0) {
+			log_error("%s:%d: Failed to send %d byte long packet.",
+				  MDL, client->packet_length);
+		}
+	} else
+#endif
+	if (fallback_interface) {
+		result = send_packet(fallback_interface, NULL, &client->packet,
+				      client->packet_length, from, &destination,
+				      NULL);
+		if (result < 0) {
+			log_error("%s:%d: Failed to send %d byte long packet"
+				  " over %s interface.", MDL,
+				  client->packet_length,
+				  fallback_interface->name);
+		}
+        } else {
+		/* Send out a packet. */
+		result = send_packet(client->interface, NULL, &client->packet,
+				      client->packet_length, from, &destination,
+				      NULL);
+		if (result < 0) {
+			log_error ("%s:%d: Failed to send %d byte long packet"
+				   " over %s interface.", MDL,
+				   client->packet_length,
+				   client->interface->name);
+		}
+
+        }
+}
+
+#if defined(DHCPv6) && defined(DHCP4o6)
+/*
+ * \brief Send a DHCPv4-query to the DHCPv6 client
+ *  (DHCPv4 client function)
+ *
+ * The DHCPv4 client sends a DHCPv4-query to the DHCPv6 client over
+ * the inter-process communication socket.
+ *
+ * \param client the DHCPv4 client state
+ * \param broadcast the broadcast flag
+ * \return the sent byte count (-1 on error)
+ */
+static int send_dhcpv4_query(struct client_state *client, int broadcast) {
+	struct data_string ds;
+	struct dhcpv4_over_dhcpv6_packet *query;
+	int ofs, len, cc;
+
+	if (dhcp4o6_state <= 0) {
+		log_info("send_dhcpv4_query: not ready.");
+		return -1;
+	}
+
+	/*
+	 * Compute buffer length and allocate it.
+	 */
+	len = ofs = (int)(offsetof(struct dhcpv4_over_dhcpv6_packet, options));
+	len += dhcpv6_universe.tag_size + dhcpv6_universe.length_size;
+	len += client->packet_length;
+	memset(&ds, 0, sizeof(ds));
+	if (!buffer_allocate(&ds.buffer, len, MDL)) {
+		log_error("Unable to allocate memory for DHCPv4-query.");
+		return -1;
+	}
+	ds.data = ds.buffer->data;
+	ds.len = len;
+
+	/*
+	 * Fill header.
+	 */
+	query = (struct dhcpv4_over_dhcpv6_packet *)ds.data;
+	query->msg_type = DHCPV6_DHCPV4_QUERY;
+	query->flags[0] = query->flags[1] = query->flags[2] = 0;
+	if (!broadcast)
+		query->flags[0] |= DHCP4O6_QUERY_UNICAST;
+
+	/*
+	 * Append DHCPv4 message.
+	 */
+	dhcpv6_universe.store_tag(ds.buffer->data + ofs, D6O_DHCPV4_MSG);
+	ofs += dhcpv6_universe.tag_size;
+	dhcpv6_universe.store_length(ds.buffer->data + ofs,
+				     client->packet_length);
+	ofs += dhcpv6_universe.length_size;
+	memcpy(ds.buffer->data + ofs, &client->packet, client->packet_length);
+
+	/*
+	 * Send DHCPv6 message.
+	 */
+	cc = send(dhcp4o6_fd, ds.data, ds.len, 0);
+	if (cc < 0)
+		log_error("send_dhcpv4_query: send(): %m");
+
+	data_string_forget(&ds, MDL);
+
+	return cc;
+}
+
+/*
+ * \brief Forward a DHCPv4-query to all DHCPv4 over DHCPv6 server addresses.
+ *  (DHCPv6 client function)
+ *
+ * \param raw the DHCPv6 DHCPv4-query message raw content
+ */
+static void forw_dhcpv4_query(struct data_string *raw) {
+	struct interface_info *ip;
+	struct client_state *client;
+	struct dhc6_lease *lease;
+	struct option_cache *oc;
+	struct data_string addrs;
+	struct sockaddr_in6 sin6;
+	int i, send_ret, attempt, success;
+
+	attempt = success = 0;
+	memset(&sin6, 0, sizeof(sin6));
+	sin6.sin6_family = AF_INET6;
+	sin6.sin6_port = remote_port;
+#ifdef HAVE_SA_LEN
+	sin6.sin6_len = sizeof(sin6);
+#endif
+	memset(&addrs, 0, sizeof(addrs));
+	for (ip = interfaces; ip != NULL; ip = ip->next) {
+		for (client = ip->client; client != NULL;
+		     client = client->next) {
+			if ((client->state != S_BOUND) &&
+			    (client->state != S_RENEWING) &&
+			    (client->state != S_REBINDING))
+				continue;
+			lease = client->active_lease;
+			if ((lease == NULL) || lease->released)
+				continue;
+			oc = lookup_option(&dhcpv6_universe,
+					   lease->options,
+					   D6O_DHCP4_O_DHCP6_SERVER);
+			if ((oc == NULL) ||
+			    !evaluate_option_cache(&addrs, NULL, NULL, NULL,
+						   lease->options, NULL,
+						   &global_scope, oc, MDL) ||
+			    ((addrs.len % sizeof(sin6.sin6_addr)) != 0)) {
+				data_string_forget(&addrs, MDL);
+				continue;
+			}
+			if (addrs.len == 0) {
+				/* note there is nothing to forget */
+				inet_pton(AF_INET6,
+					  All_DHCP_Relay_Agents_and_Servers,
+					  &sin6.sin6_addr);
+				attempt++;
+				send_ret = send_packet6(ip, raw->data,
+							raw->len, &sin6);
+				if (send_ret == raw->len)
+					success++;
+				continue;
+			}
+			for (i = 0; i < addrs.len;
+			     i += sizeof(sin6.sin6_addr)) {
+				memcpy(&sin6.sin6_addr, addrs.data + i,
+				       sizeof(sin6.sin6_addr));
+				attempt++;
+				send_ret = send_packet6(ip, raw->data,
+							raw->len, &sin6);
+				if (send_ret == raw->len)
+					success++;
+			}
+			data_string_forget(&addrs, MDL);
+		}
+	}
+
+	log_info("forw_dhcpv4_query: sent(%d): %d/%d",
+		 raw->len, success, attempt);
+
+	if (attempt == 0)
+		dhcp4o6_stop();
+}
+#endif
+
+void
+make_client_options(struct client_state *client, struct client_lease *lease,
+		    u_int8_t *type, struct option_cache *sid,
+		    struct iaddr *rip, struct option **prl,
+		    struct option_state **op)
+{
+	unsigned i;
+	struct option_cache *oc;
+	struct option *option = NULL;
+	struct buffer *bp = NULL;
+
+	/* If there are any leftover options, get rid of them. */
+	if (*op)
+		option_state_dereference(op, MDL);
+
+	/* Allocate space for options. */
+	option_state_allocate(op, MDL);
+
+	/* Send the server identifier if provided. */
+	if (sid)
+		save_option(&dhcp_universe, *op, sid);
+
+	oc = NULL;
+
+	/* Send the requested address if provided. */
+	if (rip) {
+		client->requested_address = *rip;
+		i = DHO_DHCP_REQUESTED_ADDRESS;
+		if (!(option_code_hash_lookup(&option, dhcp_universe.code_hash,
+					      &i, 0, MDL) &&
+		      make_const_option_cache(&oc, NULL, rip->iabuf, rip->len,
+					      option, MDL)))
+			log_error ("can't make requested address cache.");
+		else {
+			save_option(&dhcp_universe, *op, oc);
+			option_cache_dereference(&oc, MDL);
+		}
+		option_dereference(&option, MDL);
+	} else {
+		client->requested_address.len = 0;
+	}
+
+	i = DHO_DHCP_MESSAGE_TYPE;
+	if (!(option_code_hash_lookup(&option, dhcp_universe.code_hash, &i, 0,
+				      MDL) &&
+	      make_const_option_cache(&oc, NULL, type, 1, option, MDL)))
+		log_error("can't make message type.");
+	else {
+		save_option(&dhcp_universe, *op, oc);
+		option_cache_dereference(&oc, MDL);
+	}
+	option_dereference(&option, MDL);
+
+	if (prl) {
+		int len;
+
+		/* Probe the length of the list. */
+		len = 0;
+		for (i = 0 ; prl[i] != NULL ; i++)
+			if (prl[i]->universe == &dhcp_universe)
+				len++;
+
+		if (!buffer_allocate(&bp, len, MDL))
+			log_error("can't make parameter list buffer.");
+		else {
+			unsigned code = DHO_DHCP_PARAMETER_REQUEST_LIST;
+
+			len = 0;
+			for (i = 0 ; prl[i] != NULL ; i++)
+				if (prl[i]->universe == &dhcp_universe)
+					bp->data[len++] = prl[i]->code;
+
+			if (!(option_code_hash_lookup(&option,
+						      dhcp_universe.code_hash,
+						      &code, 0, MDL) &&
+			      make_const_option_cache(&oc, &bp, NULL, len,
+						      option, MDL))) {
+				if (bp != NULL)
+					buffer_dereference(&bp, MDL);
+				log_error ("can't make option cache");
+			} else {
+				save_option(&dhcp_universe, *op, oc);
+				option_cache_dereference(&oc, MDL);
+			}
+			option_dereference(&option, MDL);
+		}
+	}
+
+	/*
+	 * If requested (duid_v4 == 1) add an RFC4361 compliant client-identifier
+	 * This can be overridden by including a client id in the configuration
+	 * file.
+	 */
+ 	if (duid_v4 == 1) {
+		struct data_string client_identifier;
+		int hw_idx, hw_len;
+
+		memset(&client_identifier, 0, sizeof(client_identifier));
+		client_identifier.len = 1 + 4 + default_duid.len;
+		if (!buffer_allocate(&client_identifier.buffer,
+				     client_identifier.len, MDL))
+			log_fatal("no memory for default DUID!");
+		client_identifier.data = client_identifier.buffer->data;
+
+		i = DHO_DHCP_CLIENT_IDENTIFIER;
+
+		/* Client-identifier type : 1 byte */
+		*client_identifier.buffer->data = 255;
+
+		/* IAID : 4 bytes
+		 * we use the low 4 bytes from the interface address
+		 */
+		if (client->interface->hw_address.hlen > 4) {
+			hw_idx = client->interface->hw_address.hlen - 4;
+			hw_len = 4;
+		} else {
+			hw_idx = 0;
+			hw_len = client->interface->hw_address.hlen;
+		}
+		memcpy(&client_identifier.buffer->data + 5 - hw_len,
+		       client->interface->hw_address.hbuf + hw_idx,
+		       hw_len);
+
+		/* Add the default duid */
+		memcpy(&client_identifier.buffer->data+(1+4),
+		       default_duid.data, default_duid.len);
+
+		/* And save the option */
+		if (!(option_code_hash_lookup(&option, dhcp_universe.code_hash,
+					      &i, 0, MDL) &&
+		      make_const_option_cache(&oc, NULL,
+					      (u_int8_t *)client_identifier.data,
+					      client_identifier.len,
+					      option, MDL)))
+			log_error ("can't make requested client id cache..");
+		else {
+			save_option (&dhcp_universe, *op, oc);
+			option_cache_dereference (&oc, MDL);
+		}
+		option_dereference(&option, MDL);
+	}
+
+	/* Run statements that need to be run on transmission. */
+	if (client->config->on_transmission)
+		execute_statements_in_scope(NULL, NULL, NULL, client,
+					    (lease ? lease->options : NULL),
+					    *op, &global_scope,
+					    client->config->on_transmission,
+					    NULL, NULL);
+}
+
+void make_discover (client, lease)
+	struct client_state *client;
+	struct client_lease *lease;
+{
+	unsigned char discover = DHCPDISCOVER;
+	struct option_state *options = (struct option_state *)0;
+
+	memset (&client -> packet, 0, sizeof (client -> packet));
+
+	make_client_options (client,
+			     lease, &discover, (struct option_cache *)0,
+			     lease ? &lease -> address : (struct iaddr *)0,
+			     client -> config -> requested_options,
+			     &options);
+
+	/* Set up the option buffer... */
+	client -> packet_length =
+		cons_options ((struct packet *)0, &client -> packet,
+			      (struct lease *)0, client,
+			      /* maximum packet size */1500,
+			      (struct option_state *)0,
+			      options,
+			      /* scope */ &global_scope,
+			      /* overload */ 0,
+			      /* terminate */0,
+			      /* bootpp    */0,
+			      (struct data_string *)0,
+			      client -> config -> vendor_space_name);
+
+	option_state_dereference (&options, MDL);
+	if (client -> packet_length < BOOTP_MIN_LEN)
+		client -> packet_length = BOOTP_MIN_LEN;
+
+	client -> packet.op = BOOTREQUEST;
+	client -> packet.htype = client -> interface -> hw_address.hbuf [0];
+	/* Assumes hw_address is known, otherwise a random value may result */
+	client -> packet.hlen = client -> interface -> hw_address.hlen - 1;
+	client -> packet.hops = 0;
+	client -> packet.xid = random ();
+	client -> packet.secs = 0; /* filled in by send_discover. */
+
+	if (can_receive_unicast_unconfigured (client -> interface))
+		client -> packet.flags = 0;
+	else
+		client -> packet.flags = htons (BOOTP_BROADCAST);
+
+	memset (&(client -> packet.ciaddr),
+		0, sizeof client -> packet.ciaddr);
+	memset (&(client -> packet.yiaddr),
+		0, sizeof client -> packet.yiaddr);
+	memset (&(client -> packet.siaddr),
+		0, sizeof client -> packet.siaddr);
+	client -> packet.giaddr = giaddr;
+	if (client -> interface -> hw_address.hlen > 0)
+	    memcpy (client -> packet.chaddr,
+		    &client -> interface -> hw_address.hbuf [1],
+		    (unsigned)(client -> interface -> hw_address.hlen - 1));
+
+#ifdef DEBUG_PACKET
+	dump_raw ((unsigned char *)&client -> packet, client -> packet_length);
+#endif
+}
+
+
+void make_request (client, lease)
+	struct client_state *client;
+	struct client_lease *lease;
+{
+	unsigned char request = DHCPREQUEST;
+	struct option_cache *oc;
+
+	memset (&client -> packet, 0, sizeof (client -> packet));
+
+	if (client -> state == S_REQUESTING)
+		oc = lookup_option (&dhcp_universe, lease -> options,
+				    DHO_DHCP_SERVER_IDENTIFIER);
+	else
+		oc = (struct option_cache *)0;
+
+	if (client -> sent_options)
+		option_state_dereference (&client -> sent_options, MDL);
+
+	make_client_options (client, lease, &request, oc,
+			     ((client -> state == S_REQUESTING ||
+			       client -> state == S_REBOOTING)
+			      ? &lease -> address
+			      : (struct iaddr *)0),
+			     client -> config -> requested_options,
+			     &client -> sent_options);
+
+	/* Set up the option buffer... */
+	client -> packet_length =
+		cons_options ((struct packet *)0, &client -> packet,
+			      (struct lease *)0, client,
+			      /* maximum packet size */1500,
+			      (struct option_state *)0,
+			      client -> sent_options,
+			      /* scope */ &global_scope,
+			      /* overload */ 0,
+			      /* terminate */0,
+			      /* bootpp    */0,
+			      (struct data_string *)0,
+			      client -> config -> vendor_space_name);
+
+	if (client -> packet_length < BOOTP_MIN_LEN)
+		client -> packet_length = BOOTP_MIN_LEN;
+
+	client -> packet.op = BOOTREQUEST;
+	client -> packet.htype = client -> interface -> hw_address.hbuf [0];
+	/* Assumes hw_address is known, otherwise a random value may result */
+	client -> packet.hlen = client -> interface -> hw_address.hlen - 1;
+	client -> packet.hops = 0;
+	client -> packet.xid = client -> xid;
+	client -> packet.secs = 0; /* Filled in by send_request. */
+
+	/* If we own the address we're requesting, put it in ciaddr;
+	   otherwise set ciaddr to zero. */
+	if (client -> state == S_BOUND ||
+	    client -> state == S_RENEWING ||
+	    client -> state == S_REBINDING) {
+		memcpy (&client -> packet.ciaddr,
+			lease -> address.iabuf, lease -> address.len);
+		client -> packet.flags = 0;
+	} else {
+		memset (&client -> packet.ciaddr, 0,
+			sizeof client -> packet.ciaddr);
+		if (can_receive_unicast_unconfigured (client -> interface))
+			client -> packet.flags = 0;
+		else
+			client -> packet.flags = htons (BOOTP_BROADCAST);
+	}
+
+	memset (&client -> packet.yiaddr, 0,
+		sizeof client -> packet.yiaddr);
+	memset (&client -> packet.siaddr, 0,
+		sizeof client -> packet.siaddr);
+	if (client -> state != S_BOUND &&
+	    client -> state != S_RENEWING)
+		client -> packet.giaddr = giaddr;
+	else
+		memset (&client -> packet.giaddr, 0,
+			sizeof client -> packet.giaddr);
+	if (client -> interface -> hw_address.hlen > 0)
+	    memcpy (client -> packet.chaddr,
+		    &client -> interface -> hw_address.hbuf [1],
+		    (unsigned)(client -> interface -> hw_address.hlen - 1));
+
+#ifdef DEBUG_PACKET
+	dump_raw ((unsigned char *)&client -> packet, client -> packet_length);
+#endif
+}
+
+void make_decline (client, lease)
+	struct client_state *client;
+	struct client_lease *lease;
+{
+	unsigned char decline = DHCPDECLINE;
+	struct option_cache *oc;
+
+	struct option_state *options = (struct option_state *)0;
+
+	/* Create the options cache. */
+	oc = lookup_option (&dhcp_universe, lease -> options,
+			    DHO_DHCP_SERVER_IDENTIFIER);
+	make_client_options(client, lease, &decline, oc, &lease->address,
+			    NULL, &options);
+
+	/* Consume the options cache into the option buffer. */
+	memset (&client -> packet, 0, sizeof (client -> packet));
+	client -> packet_length =
+		cons_options ((struct packet *)0, &client -> packet,
+			      (struct lease *)0, client, 0,
+			      (struct option_state *)0, options,
+			      &global_scope, 0, 0, 0, (struct data_string *)0,
+			      client -> config -> vendor_space_name);
+
+	/* Destroy the options cache. */
+	option_state_dereference (&options, MDL);
+
+	if (client -> packet_length < BOOTP_MIN_LEN)
+		client -> packet_length = BOOTP_MIN_LEN;
+
+	client -> packet.op = BOOTREQUEST;
+	client -> packet.htype = client -> interface -> hw_address.hbuf [0];
+	/* Assumes hw_address is known, otherwise a random value may result */
+	client -> packet.hlen = client -> interface -> hw_address.hlen - 1;
+	client -> packet.hops = 0;
+	client -> packet.xid = client -> xid;
+	client -> packet.secs = 0; /* Filled in by send_request. */
+	if (can_receive_unicast_unconfigured (client -> interface))
+		client -> packet.flags = 0;
+	else
+		client -> packet.flags = htons (BOOTP_BROADCAST);
+
+	/* ciaddr must always be zero. */
+	memset (&client -> packet.ciaddr, 0,
+		sizeof client -> packet.ciaddr);
+	memset (&client -> packet.yiaddr, 0,
+		sizeof client -> packet.yiaddr);
+	memset (&client -> packet.siaddr, 0,
+		sizeof client -> packet.siaddr);
+	client -> packet.giaddr = giaddr;
+	memcpy (client -> packet.chaddr,
+		&client -> interface -> hw_address.hbuf [1],
+		client -> interface -> hw_address.hlen);
+
+#ifdef DEBUG_PACKET
+	dump_raw ((unsigned char *)&client -> packet, client -> packet_length);
+#endif
+}
+
+void make_release (client, lease)
+	struct client_state *client;
+	struct client_lease *lease;
+{
+	unsigned char request = DHCPRELEASE;
+	struct option_cache *oc;
+
+	struct option_state *options = (struct option_state *)0;
+
+	memset (&client -> packet, 0, sizeof (client -> packet));
+
+	oc = lookup_option (&dhcp_universe, lease -> options,
+			    DHO_DHCP_SERVER_IDENTIFIER);
+	make_client_options(client, lease, &request, oc, NULL, NULL, &options);
+
+	/* Set up the option buffer... */
+	client -> packet_length =
+		cons_options ((struct packet *)0, &client -> packet,
+			      (struct lease *)0, client,
+			      /* maximum packet size */1500,
+			      (struct option_state *)0,
+			      options,
+			      /* scope */ &global_scope,
+			      /* overload */ 0,
+			      /* terminate */0,
+			      /* bootpp    */0,
+			      (struct data_string *)0,
+			      client -> config -> vendor_space_name);
+
+	if (client -> packet_length < BOOTP_MIN_LEN)
+		client -> packet_length = BOOTP_MIN_LEN;
+	option_state_dereference (&options, MDL);
+
+	client -> packet.op = BOOTREQUEST;
+	client -> packet.htype = client -> interface -> hw_address.hbuf [0];
+	/* Assumes hw_address is known, otherwise a random value may result */
+	client -> packet.hlen = client -> interface -> hw_address.hlen - 1;
+	client -> packet.hops = 0;
+	client -> packet.xid = random ();
+	client -> packet.secs = 0;
+	client -> packet.flags = 0;
+	memcpy (&client -> packet.ciaddr,
+		lease -> address.iabuf, lease -> address.len);
+	memset (&client -> packet.yiaddr, 0,
+		sizeof client -> packet.yiaddr);
+	memset (&client -> packet.siaddr, 0,
+		sizeof client -> packet.siaddr);
+	client -> packet.giaddr = giaddr;
+	memcpy (client -> packet.chaddr,
+		&client -> interface -> hw_address.hbuf [1],
+		client -> interface -> hw_address.hlen);
+
+#ifdef DEBUG_PACKET
+	dump_raw ((unsigned char *)&client -> packet, client -> packet_length);
+#endif
+}
+
+void destroy_client_lease (lease)
+	struct client_lease *lease;
+{
+	if (lease -> server_name)
+		dfree (lease -> server_name, MDL);
+	if (lease -> filename)
+		dfree (lease -> filename, MDL);
+	option_state_dereference (&lease -> options, MDL);
+	free_client_lease (lease, MDL);
+}
+
+FILE *leaseFile = NULL;
+int leases_written = 0;
+
+void rewrite_client_leases ()
+{
+	struct interface_info *ip;
+	struct client_state *client;
+	struct client_lease *lp;
+
+	if (leaseFile != NULL)
+		fclose (leaseFile);
+	leaseFile = fopen (path_dhclient_db, "w");
+	if (leaseFile == NULL) {
+		log_error ("can't create %s: %m", path_dhclient_db);
+		return;
+	}
+
+	/* If there is a default duid, write it out. */
+	if (default_duid.len != 0)
+		write_duid(&default_duid);
+
+	/* Write out all the leases attached to configured interfaces that
+	   we know about. */
+	for (ip = interfaces; ip; ip = ip -> next) {
+		for (client = ip -> client; client; client = client -> next) {
+			for (lp = client -> leases; lp; lp = lp -> next) {
+				write_client_lease (client, lp, 1, 0);
+			}
+			if (client -> active)
+				write_client_lease (client,
+						    client -> active, 1, 0);
+
+			if (client->active_lease != NULL)
+				write_client6_lease(client,
+						    client->active_lease,
+						    1, 0);
+
+			/* Reset last_write after rewrites. */
+			client->last_write = 0;
+		}
+	}
+
+	/* Write out any leases that are attached to interfaces that aren't
+	   currently configured. */
+	for (ip = dummy_interfaces; ip; ip = ip -> next) {
+		for (client = ip -> client; client; client = client -> next) {
+			for (lp = client -> leases; lp; lp = lp -> next) {
+				write_client_lease (client, lp, 1, 0);
+			}
+			if (client -> active)
+				write_client_lease (client,
+						    client -> active, 1, 0);
+
+			if (client->active_lease != NULL)
+				write_client6_lease(client,
+						    client->active_lease,
+						    1, 0);
+
+			/* Reset last_write after rewrites. */
+			client->last_write = 0;
+		}
+	}
+	fflush (leaseFile);
+}
+
+void write_lease_option (struct option_cache *oc,
+			 struct packet *packet, struct lease *lease,
+			 struct client_state *client_state,
+			 struct option_state *in_options,
+			 struct option_state *cfg_options,
+			 struct binding_scope **scope,
+			 struct universe *u, void *stuff)
+{
+	const char *name, *dot;
+	struct data_string ds;
+	char *preamble = stuff;
+
+	memset (&ds, 0, sizeof ds);
+
+	if (u != &dhcp_universe) {
+		name = u -> name;
+		dot = ".";
+	} else {
+		name = "";
+		dot = "";
+	}
+	if (evaluate_option_cache (&ds, packet, lease, client_state,
+				   in_options, cfg_options, scope, oc, MDL)) {
+		/* The option name */
+		fprintf(leaseFile, "%soption %s%s%s", preamble,
+			name, dot, oc->option->name);
+
+		/* The option value if there is one */
+		if ((oc->option->format == NULL) ||
+		    (oc->option->format[0] != 'Z')) {
+			fprintf(leaseFile, " %s",
+				pretty_print_option(oc->option, ds.data,
+						    ds.len, 1, 1));
+		}
+
+		/* The closing semi-colon and newline */
+		fprintf(leaseFile, ";\n");
+
+		data_string_forget (&ds, MDL);
+	}
+}
+
+/* Write an option cache to the lease store. */
+static void
+write_options(struct client_state *client, struct option_state *options,
+	      const char *preamble)
+{
+	int i;
+
+	for (i = 0; i < options->universe_count; i++) {
+		option_space_foreach(NULL, NULL, client, NULL, options,
+				     &global_scope, universes[i],
+				     (char *)preamble, write_lease_option);
+	}
+}
+
+/*
+ * The "best" default DUID, since we cannot predict any information
+ * about the system (such as whether or not the hardware addresses are
+ * integrated into the motherboard or similar), is the "LLT", link local
+ * plus time, DUID. For real stateless "LL" is better.
+ *
+ * Once generated, this duid is stored into the state database, and
+ * retained across restarts.
+ *
+ * For the time being, there is probably a different state database for
+ * every daemon, so this winds up being a per-interface identifier...which
+ * is not how it is intended.  Upcoming rearchitecting the client should
+ * address this "one daemon model."
+ */
+void
+form_duid(struct data_string *duid, const char *file, int line)
+{
+	struct interface_info *ip;
+	int len;
+	char *str;
+
+	/* For now, just use the first interface on the list. */
+	ip = interfaces;
+
+	if (ip == NULL)
+		log_fatal("Impossible condition at %s:%d.", MDL);
+
+	if ((ip->hw_address.hlen == 0) ||
+	    (ip->hw_address.hlen > sizeof(ip->hw_address.hbuf)))
+		log_fatal("Impossible hardware address length at %s:%d.", MDL);
+
+	if (duid_type == 0)
+		duid_type = stateless ? DUID_LL : DUID_LLT;
+
+	/*
+	 * 2 bytes for the 'duid type' field.
+	 * 2 bytes for the 'htype' field.
+	 * (DUID_LLT) 4 bytes for the 'current time'.
+	 * enough bytes for the hardware address (note that hw_address has
+	 * the 'htype' on byte zero).
+	 */
+	len = 4 + (ip->hw_address.hlen - 1);
+	if (duid_type == DUID_LLT)
+		len += 4;
+	if (!buffer_allocate(&duid->buffer, len, MDL))
+		log_fatal("no memory for default DUID!");
+	duid->data = duid->buffer->data;
+	duid->len = len;
+
+	/* Basic Link Local Address type of DUID. */
+	if (duid_type == DUID_LLT) {
+		putUShort(duid->buffer->data, DUID_LLT);
+		putUShort(duid->buffer->data + 2, ip->hw_address.hbuf[0]);
+		putULong(duid->buffer->data + 4, cur_time - DUID_TIME_EPOCH);
+		memcpy(duid->buffer->data + 8, ip->hw_address.hbuf + 1,
+		       ip->hw_address.hlen - 1);
+	} else {
+		putUShort(duid->buffer->data, DUID_LL);
+		putUShort(duid->buffer->data + 2, ip->hw_address.hbuf[0]);
+		memcpy(duid->buffer->data + 4, ip->hw_address.hbuf + 1,
+		       ip->hw_address.hlen - 1);
+	}
+
+	/* Now format the output based on lease-id-format */
+	str = format_lease_id(duid->data, duid->len,
+			      top_level_config.lease_id_format, MDL);
+	if (str == NULL) {
+		log_info("form_duid: Couldn't allocate memory to log duid!");
+	} else {
+		log_info("Created duid %s.", str);
+		dfree(str, MDL);
+	}
+}
+
+/* Write the default DUID to the lease store. */
+static isc_result_t
+write_duid(struct data_string *duid)
+{
+	char *str;
+	int stat;
+
+	if ((duid == NULL) || (duid->len <= 2))
+		return DHCP_R_INVALIDARG;
+
+	if (leaseFile == NULL) {	/* XXX? */
+		leaseFile = fopen(path_dhclient_db, "w");
+		if (leaseFile == NULL) {
+			log_error("can't create %s: %m", path_dhclient_db);
+			return ISC_R_IOERROR;
+		}
+	}
+
+	/* Generate a formatted duid string per lease-id-format */
+	str = format_lease_id(duid->data, duid->len,
+			      top_level_config.lease_id_format, MDL);
+	if (str == NULL)
+		return ISC_R_NOMEMORY;
+
+	stat = fprintf(leaseFile, "default-duid %s;\n", str);
+	dfree(str, MDL);
+	if (stat <= 0)
+		return ISC_R_IOERROR;
+
+	if (fflush(leaseFile) != 0)
+		return ISC_R_IOERROR;
+
+	return ISC_R_SUCCESS;
+}
+
+/* Write a DHCPv6 lease to the store. */
+isc_result_t
+write_client6_lease(struct client_state *client, struct dhc6_lease *lease,
+		    int rewrite, int sync)
+{
+	struct dhc6_ia *ia;
+	struct dhc6_addr *addr;
+	int stat;
+	const char *ianame;
+
+	/* This should include the current lease. */
+	if (!rewrite && (leases_written++ > 20)) {
+		rewrite_client_leases();
+		leases_written = 0;
+		return ISC_R_SUCCESS;
+	}
+
+	if (client == NULL || lease == NULL)
+		return DHCP_R_INVALIDARG;
+
+	if (leaseFile == NULL) {	/* XXX? */
+		leaseFile = fopen(path_dhclient_db, "w");
+		if (leaseFile == NULL) {
+			log_error("can't create %s: %m", path_dhclient_db);
+			return ISC_R_IOERROR;
+		}
+	}
+
+	stat = fprintf(leaseFile, "lease6 {\n");
+	if (stat <= 0)
+		return ISC_R_IOERROR;
+
+	stat = fprintf(leaseFile, "  interface \"%s\";\n",
+		       client->interface->name);
+	if (stat <= 0)
+		return ISC_R_IOERROR;
+
+	for (ia = lease->bindings ; ia != NULL ; ia = ia->next) {
+		switch (ia->ia_type) {
+			case D6O_IA_NA:
+			default:
+				ianame = "ia-na";
+				break;
+			case D6O_IA_TA:
+				ianame = "ia-ta";
+				break;
+			case D6O_IA_PD:
+				ianame = "ia-pd";
+				break;
+		}
+
+		/* For some reason IAID was never octal or hex, but string or
+		 * hex. Go figure.  So for compatibilty's sake we will either
+		 * do hex or "legacy" i.e string rather than octal. What a
+		 * cluster. */
+		switch(top_level_config.lease_id_format) {
+			case TOKEN_HEX: {
+				char* iaid_str = format_lease_id(
+					(const unsigned char *) &ia->iaid, 4,
+					top_level_config.lease_id_format, MDL);
+
+				if (!iaid_str) {
+					log_error("Can't format iaid");
+					return ISC_R_IOERROR;
+				}
+
+				stat = fprintf(leaseFile, "  %s %s {\n",
+					       ianame, iaid_str);
+				dfree(iaid_str, MDL);
+				break;
+			}
+
+			case TOKEN_OCTAL:
+			default:
+				stat = fprintf(leaseFile, "  %s %s {\n", ianame,
+					       print_hex_1(4, ia->iaid, 12));
+				break;
+		}
+
+		if (stat <= 0)
+			return ISC_R_IOERROR;
+
+		if (ia->ia_type != D6O_IA_TA)
+			stat = fprintf(leaseFile, "    starts %d;\n"
+						  "    renew %u;\n"
+						  "    rebind %u;\n",
+				       (int)ia->starts, ia->renew, ia->rebind);
+		else
+			stat = fprintf(leaseFile, "    starts %d;\n",
+				       (int)ia->starts);
+		if (stat <= 0)
+			return ISC_R_IOERROR;
+
+		for (addr = ia->addrs ; addr != NULL ; addr = addr->next) {
+			if (ia->ia_type != D6O_IA_PD)
+				stat = fprintf(leaseFile,
+					       "    iaaddr %s {\n",
+					       piaddr(addr->address));
+			else
+				stat = fprintf(leaseFile,
+					       "    iaprefix %s/%d {\n",
+					       piaddr(addr->address),
+					       (int)addr->plen);
+			if (stat <= 0)
+				return ISC_R_IOERROR;
+
+			stat = fprintf(leaseFile, "      starts %d;\n"
+						  "      preferred-life %u;\n"
+						  "      max-life %u;\n",
+				       (int)addr->starts, addr->preferred_life,
+				       addr->max_life);
+			if (stat <= 0)
+				return ISC_R_IOERROR;
+
+			if (addr->options != NULL)
+				write_options(client, addr->options, "      ");
+
+			stat = fprintf(leaseFile, "    }\n");
+			if (stat <= 0)
+				return ISC_R_IOERROR;
+		}
+
+		if (ia->options != NULL)
+			write_options(client, ia->options, "    ");
+
+		stat = fprintf(leaseFile, "  }\n");
+		if (stat <= 0)
+			return ISC_R_IOERROR;
+	}
+
+	if (lease->released) {
+		stat = fprintf(leaseFile, "  released;\n");
+		if (stat <= 0)
+			return ISC_R_IOERROR;
+	}
+
+	if (lease->options != NULL)
+		write_options(client, lease->options, "  ");
+
+	stat = fprintf(leaseFile, "}\n");
+	if (stat <= 0)
+		return ISC_R_IOERROR;
+
+	if (fflush(leaseFile) != 0)
+		return ISC_R_IOERROR;
+
+	if (sync) {
+		if (fsync(fileno(leaseFile)) < 0) {
+			log_error("write_client_lease: fsync(): %m");
+			return ISC_R_IOERROR;
+		}
+	}
+
+	return ISC_R_SUCCESS;
+}
+
+int write_client_lease (client, lease, rewrite, makesure)
+	struct client_state *client;
+	struct client_lease *lease;
+	int rewrite;
+	int makesure;
+{
+	struct data_string ds;
+	int errors = 0;
+	char *s;
+	const char *tval;
+
+	if (!rewrite) {
+		if (leases_written++ > 20) {
+			rewrite_client_leases ();
+			leases_written = 0;
+		}
+	}
+
+	/* If the lease came from the config file, we don't need to stash
+	   a copy in the lease database. */
+	if (lease -> is_static)
+		return 1;
+
+	if (leaseFile == NULL) {	/* XXX */
+		leaseFile = fopen (path_dhclient_db, "w");
+		if (leaseFile == NULL) {
+			log_error ("can't create %s: %m", path_dhclient_db);
+			return 0;
+		}
+	}
+
+	errno = 0;
+	fprintf (leaseFile, "lease {\n");
+	if (lease -> is_bootp) {
+		fprintf (leaseFile, "  bootp;\n");
+		if (errno) {
+			++errors;
+			errno = 0;
+		}
+	}
+	fprintf (leaseFile, "  interface \"%s\";\n",
+		 client -> interface -> name);
+	if (errno) {
+		++errors;
+		errno = 0;
+	}
+	if (client -> name) {
+		fprintf (leaseFile, "  name \"%s\";\n", client -> name);
+		if (errno) {
+			++errors;
+			errno = 0;
+		}
+	}
+	fprintf (leaseFile, "  fixed-address %s;\n",
+		 piaddr (lease -> address));
+	if (errno) {
+		++errors;
+		errno = 0;
+	}
+	if (lease -> filename) {
+		s = quotify_string (lease -> filename, MDL);
+		if (s) {
+			fprintf (leaseFile, "  filename \"%s\";\n", s);
+			if (errno) {
+				++errors;
+				errno = 0;
+			}
+			dfree (s, MDL);
+		} else
+			errors++;
+
+	}
+	if (lease->server_name != NULL) {
+		s = quotify_string(lease->server_name, MDL);
+		if (s != NULL) {
+			fprintf(leaseFile, "  server-name \"%s\";\n", s);
+			if (errno) {
+				++errors;
+				errno = 0;
+			}
+			dfree(s, MDL);
+		} else
+			++errors;
+	}
+	if (lease -> medium) {
+		s = quotify_string (lease -> medium -> string, MDL);
+		if (s) {
+			fprintf (leaseFile, "  medium \"%s\";\n", s);
+			if (errno) {
+				++errors;
+				errno = 0;
+			}
+			dfree (s, MDL);
+		} else
+			errors++;
+	}
+	if (errno != 0) {
+		errors++;
+		errno = 0;
+	}
+
+	memset (&ds, 0, sizeof ds);
+
+	write_options(client, lease->options, "  ");
+
+	tval = print_time(lease->renewal);
+	if (tval == NULL ||
+	    fprintf(leaseFile, "  renew %s\n", tval) < 0)
+		errors++;
+
+	tval = print_time(lease->rebind);
+	if (tval == NULL ||
+	    fprintf(leaseFile, "  rebind %s\n", tval) < 0)
+		errors++;
+
+	tval = print_time(lease->expiry);
+	if (tval == NULL ||
+	    fprintf(leaseFile, "  expire %s\n", tval) < 0)
+		errors++;
+
+	if (fprintf(leaseFile, "}\n") < 0)
+		errors++;
+
+	if (fflush(leaseFile) != 0)
+		errors++;
+
+	client->last_write = cur_time;
+
+	if (!errors && makesure) {
+		if (fsync (fileno (leaseFile)) < 0) {
+			log_info ("write_client_lease: %m");
+			return 0;
+		}
+	}
+
+	return errors ? 0 : 1;
+}
+
+/* Variables holding name of script and file pointer for writing to
+   script.   Needless to say, this is not reentrant - only one script
+   can be invoked at a time. */
+char scriptName [256];
+FILE *scriptFile;
+
+/**
+ * @brief Initializes basic variables for a script
+ *
+ * This function is called as an initial preparation for calling a script.
+ * It sets up a number of common env. variables that will be passed to
+ * the script. For actual script calling, see @ref script_go .
+ *
+ * @param client variables will be stored here (if null, the whole function
+ *               is no-op)
+ * @param reason specified the reason for calling a script (must be non-null)
+ * @param medium if specified, defines medium type (may be null)
+ */
+void script_init(struct client_state *client, const char *reason,
+                 struct string_list *medium)
+{
+	struct string_list *sl, *next;
+
+	if (client) {
+		for (sl = client -> env; sl; sl = next) {
+			next = sl -> next;
+			dfree (sl, MDL);
+		}
+		client -> env = (struct string_list *)0;
+		client -> envc = 0;
+
+		if (client -> interface) {
+			client_envadd (client, "", "interface", "%s",
+				       client -> interface -> name);
+		}
+		if (client -> name)
+			client_envadd (client,
+				       "", "client", "%s", client -> name);
+		if (medium)
+			client_envadd (client,
+				       "", "medium", "%s", medium -> string);
+
+		client_envadd (client, "", "reason", "%s", reason);
+		client_envadd (client, "", "pid", "%ld", (long int)getpid ());
+#if defined(DHCPv6)
+		client_envadd (client, "", "dad_wait_time", "%ld",
+					   (long int)dad_wait_time);
+#endif
+	}
+}
+
+void client_option_envadd (struct option_cache *oc,
+			   struct packet *packet, struct lease *lease,
+			   struct client_state *client_state,
+			   struct option_state *in_options,
+			   struct option_state *cfg_options,
+			   struct binding_scope **scope,
+			   struct universe *u, void *stuff)
+{
+	struct envadd_state *es = stuff;
+	struct data_string data;
+	memset (&data, 0, sizeof data);
+
+	if (evaluate_option_cache (&data, packet, lease, client_state,
+				   in_options, cfg_options, scope, oc, MDL)) {
+		if (data.len) {
+			char name [256];
+			if (dhcp_option_ev_name (name, sizeof name,
+						 oc->option)) {
+				const char *value;
+				size_t length;
+				value = pretty_print_option(oc->option,
+							    data.data,
+							    data.len, 0, 0);
+				length = strlen(value);
+
+				if (check_option_values(oc->option->universe,
+							oc->option->code,
+							value, length) == 0) {
+					client_envadd(es->client, es->prefix,
+						      name, "%s", value);
+				} else {
+					log_error("suspect value in %s "
+						  "option - discarded",
+						  name);
+				}
+			}
+		}
+
+		data_string_forget (&data, MDL);
+	}
+}
+
+/**
+ * @brief Adds parameters to environment variables for a script
+ *
+ * This function add details of specified lease to a list of env. variables
+ * to be passed to a script. The lease details will be prepended with
+ * specified prefix (e.g. "old_") and added to the list stored in client.
+ * Following variables may be set:
+ * - ip_address
+ * - next_server
+ * - network_number
+ * - broadcast_address
+ * - filename
+ * - server_name
+ * - expiry
+ *
+ * @param client env. variables will be stored here
+ * @param prefix textual prefix to be added to each variable (e.g. "old_")
+ * @param lease lease details will be extracted from here
+ */
+void script_write_params(struct client_state *client, const char *prefix,
+			 struct client_lease *lease)
+{
+	int i;
+	struct data_string data;
+	struct option_cache *oc;
+	struct envadd_state es;
+
+	es.client = client;
+	es.prefix = prefix;
+
+	client_envadd (client,
+		       prefix, "ip_address", "%s", piaddr (lease -> address));
+
+	/* If we've set the next server address in the lease structure
+	   put it into an environment variable for the script */
+	if (lease->next_srv_addr.len != 0) {
+		client_envadd(client, prefix, "next_server", "%s",
+			      piaddr(lease->next_srv_addr));
+	}
+
+	/* For the benefit of Linux (and operating systems which may
+	   have similar needs), compute the network address based on
+	   the supplied ip address and netmask, if provided.  Also
+	   compute the broadcast address (the host address all ones
+	   broadcast address, not the host address all zeroes
+	   broadcast address). */
+
+	memset (&data, 0, sizeof data);
+	oc = lookup_option (&dhcp_universe, lease -> options, DHO_SUBNET_MASK);
+	if (oc && evaluate_option_cache (&data, (struct packet *)0,
+					 (struct lease *)0, client,
+					 (struct option_state *)0,
+					 lease -> options,
+					 &global_scope, oc, MDL)) {
+		if (data.len > 3) {
+			struct iaddr netmask, subnet, broadcast;
+
+			/*
+			 * No matter the length of the subnet-mask option,
+			 * use only the first four octets.  Note that
+			 * subnet-mask options longer than 4 octets are not
+			 * in conformance with RFC 2132, but servers with this
+			 * flaw do exist.
+			 */
+			memcpy(netmask.iabuf, data.data, 4);
+			netmask.len = 4;
+			data_string_forget (&data, MDL);
+
+			subnet = subnet_number (lease -> address, netmask);
+			if (subnet.len) {
+			    client_envadd (client, prefix, "network_number",
+					   "%s", piaddr (subnet));
+
+			    oc = lookup_option (&dhcp_universe,
+						lease -> options,
+						DHO_BROADCAST_ADDRESS);
+			    if (!oc ||
+				!(evaluate_option_cache
+				  (&data, (struct packet *)0,
+				   (struct lease *)0, client,
+				   (struct option_state *)0,
+				   lease -> options,
+				   &global_scope, oc, MDL))) {
+				broadcast = broadcast_addr (subnet, netmask);
+				if (broadcast.len) {
+				    client_envadd (client,
+						   prefix, "broadcast_address",
+						   "%s", piaddr (broadcast));
+				}
+			    }
+			}
+		}
+		data_string_forget (&data, MDL);
+	}
+
+	if (lease->filename) {
+		if (check_option_values(NULL, DHO_ROOT_PATH,
+					lease->filename,
+					strlen(lease->filename)) == 0) {
+			client_envadd(client, prefix, "filename",
+				      "%s", lease->filename);
+		} else {
+			log_error("suspect value in %s "
+				  "option - discarded",
+				  lease->filename);
+		}
+	}
+
+	if (lease->server_name) {
+		if (check_option_values(NULL, DHO_HOST_NAME,
+					lease->server_name,
+					strlen(lease->server_name)) == 0 ) {
+			client_envadd (client, prefix, "server_name",
+				       "%s", lease->server_name);
+		} else {
+			log_error("suspect value in %s "
+				  "option - discarded",
+				  lease->server_name);
+		}
+	}
+
+	for (i = 0; i < lease -> options -> universe_count; i++) {
+		option_space_foreach ((struct packet *)0, (struct lease *)0,
+				      client, (struct option_state *)0,
+				      lease -> options, &global_scope,
+				      universes [i],
+				      &es, client_option_envadd);
+	}
+
+	client_envadd (client, prefix, "expiry", "%lu",
+		       (unsigned long)(lease -> expiry));
+}
+
+/**
+ * @brief Write out the environent variable the client requested.
+ * Write out the environment variables for the objects that the
+ * client requested.  If the object was requested the variable will be:
+ * requested_<option_name>=1
+ * If it wasn't requested there won't be a variable.
+ *
+ * @param client client structure
+ */
+void script_write_requested(struct client_state *client)
+{
+	int i;
+	struct option **req;
+	char name[256];
+	req = client->config->requested_options;
+
+	if (req == NULL)
+		return;
+
+	for (i = 0 ; req[i] != NULL ; i++) {
+		if ((req[i]->universe == &dhcp_universe) &&
+		    dhcp_option_ev_name(name, sizeof(name), req[i])) {
+			client_envadd(client, "requested_", name, "%d", 1);
+		}
+	}
+}
+
+/**
+ * @brief Calls external script.
+ *
+ * External script is specified either using -sf command line or
+ * script parameter in the configuration file.
+ *
+ * @param client specifies client information (environment variables,
+ *        and other parameters will be extracted and passed to the script.
+ * @return If positive, it contains exit code of the process running script.
+ *         If negative, returns the signal number that cause the script process
+ *         to terminate.
+ */
+int script_go(struct client_state *client)
+{
+	char *scriptName;
+	char *argv [2];
+	char **envp;
+	char reason [] = "REASON=NBI";
+	static char client_path [] = CLIENT_PATH;
+	int i;
+	struct string_list *sp, *next;
+	int pid, wpid, wstatus;
+
+	if (client)
+		scriptName = client -> config -> script_name;
+	else
+		scriptName = top_level_config.script_name;
+
+	envp = dmalloc (((client ? client -> envc : 2) +
+			 client_env_count + 2) * sizeof (char *), MDL);
+	if (!envp) {
+		log_error ("No memory for client script environment.");
+		return 0;
+	}
+	i = 0;
+	/* Copy out the environment specified on the command line,
+	   if any. */
+	for (sp = client_env; sp; sp = sp -> next) {
+		envp [i++] = sp -> string;
+	}
+	/* Copy out the environment specified by dhclient. */
+	if (client) {
+		for (sp = client -> env; sp; sp = sp -> next) {
+			envp [i++] = sp -> string;
+		}
+	} else {
+		envp [i++] = reason;
+	}
+	/* Set $PATH. */
+	envp [i++] = client_path;
+	envp [i] = (char *)0;
+
+	argv [0] = scriptName;
+	argv [1] = (char *)0;
+
+	pid = fork ();
+	if (pid < 0) {
+		log_error ("fork: %m");
+		wstatus = 0;
+	} else if (pid) {
+		do {
+			wpid = wait (&wstatus);
+		} while (wpid != pid && wpid > 0);
+		if (wpid < 0) {
+			log_error ("wait: %m");
+			wstatus = 0;
+		}
+	} else {
+		/* We don't want to pass an open file descriptor for
+		 * dhclient.leases when executing dhclient-script.
+		 */
+		if (leaseFile != NULL)
+			fclose(leaseFile);
+		execve (scriptName, argv, envp);
+		log_error ("execve (%s, ...): %m", scriptName);
+		exit (0);
+	}
+
+	if (client) {
+		for (sp = client -> env; sp; sp = next) {
+			next = sp -> next;
+			dfree (sp, MDL);
+		}
+		client -> env = (struct string_list *)0;
+		client -> envc = 0;
+	}
+	dfree (envp, MDL);
+	gettimeofday(&cur_tv, NULL);
+	return (WIFEXITED (wstatus) ?
+		WEXITSTATUS (wstatus) : -WTERMSIG (wstatus));
+}
+
+void client_envadd (struct client_state *client,
+		    const char *prefix, const char *name, const char *fmt, ...)
+{
+	char spbuf [1024];
+	char *s;
+	unsigned len;
+	struct string_list *val;
+	va_list list;
+
+	va_start (list, fmt);
+	len = vsnprintf (spbuf, sizeof spbuf, fmt, list);
+	va_end (list);
+
+	val = dmalloc (strlen (prefix) + strlen (name) + 1 /* = */ +
+		       len + sizeof *val, MDL);
+	if (!val) {
+		log_error ("client_envadd: cannot allocate space for variable");
+		return;
+	}
+
+	s = val -> string;
+	strcpy (s, prefix);
+	strcat (s, name);
+	s += strlen (s);
+	*s++ = '=';
+	if (len >= sizeof spbuf) {
+		va_start (list, fmt);
+		vsnprintf (s, len + 1, fmt, list);
+		va_end (list);
+	} else {
+		strcpy (s, spbuf);
+	}
+
+	val -> next = client -> env;
+	client -> env = val;
+	client -> envc++;
+}
+
+int dhcp_option_ev_name (buf, buflen, option)
+	char *buf;
+	size_t buflen;
+	struct option *option;
+{
+	int i, j;
+	const char *s;
+
+	j = 0;
+	if (option -> universe != &dhcp_universe) {
+		s = option -> universe -> name;
+		i = 0;
+	} else {
+		s = option -> name;
+		i = 1;
+	}
+
+	do {
+		while (*s) {
+			if (j + 1 == buflen)
+				return 0;
+			if (*s == '-')
+				buf [j++] = '_';
+			else
+				buf [j++] = *s;
+			++s;
+		}
+		if (!i) {
+			s = option -> name;
+			if (j + 1 == buflen)
+				return 0;
+			buf [j++] = '_';
+		}
+		++i;
+	} while (i != 2);
+
+	buf [j] = 0;
+	return 1;
+}
+
+void finish (char ret)
+{
+	if (no_daemon || dfd[0] == -1 || dfd[1] == -1)
+		exit((int)ret);
+	if (write(dfd[1], &ret, 1) != 1)
+		log_fatal("write to parent: %m");
+	(void) close(dfd[1]);
+	dfd[0] = dfd[1] = -1;
+	exit((int)ret);
+}
+
+void detach ()
+{
+	char buf = 0;
+
+	/* Don't become a daemon if the user requested otherwise. */
+	if (no_daemon) {
+		write_client_pid_file ();
+		return;
+	}
+
+	/* Only do it once. */
+	if (dfd[0] == -1 || dfd[1] == -1)
+		return;
+
+	/* Signal parent we started successfully. */
+	if (write(dfd[1], &buf, 1) != 1)
+		log_fatal("write to parent: %m");
+	(void) close(dfd[1]);
+	dfd[0] = dfd[1] = -1;
+
+	/* Stop logging to stderr... */
+	log_perror = 0;
+
+	/* Become session leader and get pid... */
+	(void) setsid ();
+
+	/* Close standard I/O descriptors. */
+	(void) close(0);
+	(void) close(1);
+	(void) close(2);
+
+	/* Reopen them on /dev/null. */
+	(void) open("/dev/null", O_RDWR);
+	(void) open("/dev/null", O_RDWR);
+	(void) open("/dev/null", O_RDWR);
+
+	write_client_pid_file ();
+
+	IGNORE_RET (chdir("/"));
+
+}
+
+void write_client_pid_file ()
+{
+	FILE *pf;
+	int pfdesc;
+
+	/* nothing to do if the user doesn't want a pid file */
+	if (no_pid_file == ISC_TRUE) {
+		return;
+	}
+
+	pfdesc = open (path_dhclient_pid, O_CREAT | O_TRUNC | O_WRONLY, 0644);
+
+	if (pfdesc < 0) {
+		log_error ("Can't create %s: %m", path_dhclient_pid);
+		return;
+	}
+
+	pf = fdopen (pfdesc, "w");
+	if (!pf) {
+		close(pfdesc);
+		log_error ("Can't fdopen %s: %m", path_dhclient_pid);
+	} else {
+		fprintf (pf, "%ld\n", (long)getpid ());
+		fclose (pf);
+	}
+}
+
+void client_location_changed ()
+{
+	struct interface_info *ip;
+	struct client_state *client;
+
+	for (ip = interfaces; ip; ip = ip -> next) {
+		for (client = ip -> client; client; client = client -> next) {
+			switch (client -> state) {
+			      case S_SELECTING:
+				cancel_timeout (send_discover, client);
+				break;
+
+			      case S_BOUND:
+				cancel_timeout (state_bound, client);
+				break;
+
+			      case S_REBOOTING:
+			      case S_REQUESTING:
+			      case S_RENEWING:
+				cancel_timeout (send_request, client);
+				break;
+
+			      case S_INIT:
+			      case S_REBINDING:
+			      case S_STOPPED:
+			      case S_DECLINING:
+				break;
+			}
+			client -> state = S_INIT;
+			state_reboot (client);
+		}
+	}
+}
+
+void do_release(client)
+	struct client_state *client;
+{
+	struct data_string ds;
+	struct option_cache *oc;
+
+#if defined(DHCPv6) && defined(DHCP4o6)
+	if (dhcpv4_over_dhcpv6 && (dhcp4o6_state <= 0)) {
+		if (dhcp4o6_state < 0)
+			dhcp4o6_poll(NULL);
+		client->pending = P_RELEASE;
+		return;
+	}
+#endif
+
+	/* Pick a random xid. */
+	client -> xid = random ();
+
+	/* is there even a lease to release? */
+	if (client -> active) {
+		/* Make a DHCPRELEASE packet, and set appropriate per-interface
+		   flags. */
+		make_release (client, client -> active);
+
+		memset (&ds, 0, sizeof ds);
+		oc = lookup_option (&dhcp_universe,
+				    client -> active -> options,
+				    DHO_DHCP_SERVER_IDENTIFIER);
+		if (oc &&
+		    evaluate_option_cache (&ds, (struct packet *)0,
+					   (struct lease *)0, client,
+					   (struct option_state *)0,
+					   client -> active -> options,
+					   &global_scope, oc, MDL)) {
+			if (ds.len > 3) {
+				memcpy (client -> destination.iabuf,
+					ds.data, 4);
+				client -> destination.len = 4;
+			} else
+				client -> destination = iaddr_broadcast;
+
+			data_string_forget (&ds, MDL);
+		} else
+			client -> destination = iaddr_broadcast;
+		client -> first_sending = cur_time;
+		client -> interval = client -> config -> initial_interval;
+
+		/* Zap the medium list... */
+		client -> medium = (struct string_list *)0;
+
+		/* Send out the first and only DHCPRELEASE packet. */
+		send_release (client);
+
+		/* Do the client script RELEASE operation. */
+		script_init (client,
+			     "RELEASE", (struct string_list *)0);
+		if (client -> alias)
+			script_write_params(client, "alias_",
+					    client -> alias);
+		script_write_params(client, "old_", client -> active);
+		script_write_requested(client);
+		script_go(client);
+	}
+
+	/* Cancel any timeouts. */
+	cancel_timeout (state_bound, client);
+	cancel_timeout (send_discover, client);
+	cancel_timeout (state_init, client);
+	cancel_timeout (send_request, client);
+	cancel_timeout (state_reboot, client);
+	client -> state = S_STOPPED;
+
+#if defined(DHCPv6) && defined(DHCP4o6)
+	if (dhcpv4_over_dhcpv6)
+		finish(0);
+#endif
+}
+
+int dhclient_interface_shutdown_hook (struct interface_info *interface)
+{
+	do_release (interface -> client);
+
+	return 1;
+}
+
+int dhclient_interface_discovery_hook (struct interface_info *tmp)
+{
+	struct interface_info *last, *ip;
+	/* See if we can find the client from dummy_interfaces */
+	last = 0;
+	for (ip = dummy_interfaces; ip; ip = ip -> next) {
+		if (!strcmp (ip -> name, tmp -> name)) {
+			/* Remove from dummy_interfaces */
+			if (last) {
+				ip = (struct interface_info *)0;
+				interface_reference (&ip, last -> next, MDL);
+				interface_dereference (&last -> next, MDL);
+				if (ip -> next) {
+					interface_reference (&last -> next,
+							     ip -> next, MDL);
+					interface_dereference (&ip -> next,
+							       MDL);
+				}
+			} else {
+				ip = (struct interface_info *)0;
+				interface_reference (&ip,
+						     dummy_interfaces, MDL);
+				interface_dereference (&dummy_interfaces, MDL);
+				if (ip -> next) {
+					interface_reference (&dummy_interfaces,
+							     ip -> next, MDL);
+					interface_dereference (&ip -> next,
+							       MDL);
+				}
+			}
+			/* Copy "client" to tmp */
+			if (ip -> client) {
+				tmp -> client = ip -> client;
+				tmp -> client -> interface = tmp;
+			}
+			interface_dereference (&ip, MDL);
+			break;
+		}
+		last = ip;
+	}
+	return 1;
+}
+
+isc_result_t dhclient_interface_startup_hook (struct interface_info *interface)
+{
+	struct interface_info *ip;
+	struct client_state *client;
+
+	/* This code needs some rethinking.   It doesn't test against
+	   a signal name, and it just kind of bulls into doing something
+	   that may or may not be appropriate. */
+
+	if (interfaces) {
+		interface_reference (&interface -> next, interfaces, MDL);
+		interface_dereference (&interfaces, MDL);
+	}
+	interface_reference (&interfaces, interface, MDL);
+
+	discover_interfaces (DISCOVER_UNCONFIGURED);
+
+	for (ip = interfaces; ip; ip = ip -> next) {
+		/* If interfaces were specified, don't configure
+		   interfaces that weren't specified! */
+		if (ip -> flags & INTERFACE_RUNNING ||
+		   (ip -> flags & (INTERFACE_REQUESTED |
+				     INTERFACE_AUTOMATIC)) !=
+		     INTERFACE_REQUESTED)
+			continue;
+		script_init (ip -> client,
+			     "PREINIT", (struct string_list *)0);
+		if (ip -> client -> alias)
+			script_write_params(ip -> client, "alias_",
+					    ip -> client -> alias);
+		script_go(ip -> client);
+	}
+
+	discover_interfaces (interfaces_requested != 0
+			     ? DISCOVER_REQUESTED
+			     : DISCOVER_RUNNING);
+
+	for (ip = interfaces; ip; ip = ip -> next) {
+		if (ip -> flags & INTERFACE_RUNNING)
+			continue;
+		ip -> flags |= INTERFACE_RUNNING;
+		for (client = ip->client ; client ; client = client->next) {
+			client->state = S_INIT;
+			state_reboot(client);
+		}
+	}
+	return ISC_R_SUCCESS;
+}
+
+/* The client should never receive a relay agent information option,
+   so if it does, log it and discard it. */
+
+int parse_agent_information_option (packet, len, data)
+	struct packet *packet;
+	int len;
+	u_int8_t *data;
+{
+	return 1;
+}
+
+/* The client never sends relay agent information options. */
+
+unsigned cons_agent_information_options (cfg_options, outpacket,
+					 agentix, length)
+	struct option_state *cfg_options;
+	struct dhcp_packet *outpacket;
+	unsigned agentix;
+	unsigned length;
+{
+	return length;
+}
+
+static void shutdown_exit (void *foo)
+{
+	/* get rid of the pid if we can */
+	if (no_pid_file == ISC_FALSE)
+		(void) unlink(path_dhclient_pid);
+	finish(0);
+}
+
+#if defined (NSUPDATE)
+/*
+ * If the first query fails, the updater MUST NOT delete the DNS name.  It
+ * may be that the host whose lease on the server has expired has moved
+ * to another network and obtained a lease from a different server,
+ * which has caused the client's A RR to be replaced. It may also be
+ * that some other client has been configured with a name that matches
+ * the name of the DHCP client, and the policy was that the last client
+ * to specify the name would get the name.  In this case, the DHCID RR
+ * will no longer match the updater's notion of the client-identity of
+ * the host pointed to by the DNS name.
+ *   -- "Interaction between DHCP and DNS"
+ */
+
+/* The first and second stages are pretty similar so we combine them */
+void
+client_dns_remove_action(dhcp_ddns_cb_t *ddns_cb,
+			 isc_result_t    eresult)
+{
+
+	isc_result_t result;
+
+	if ((eresult == ISC_R_SUCCESS) &&
+	    (ddns_cb->state == DDNS_STATE_REM_FW_YXDHCID)) {
+		/* Do the second stage of the FWD removal */
+		ddns_cb->state = DDNS_STATE_REM_FW_NXRR;
+
+		result = ddns_modify_fwd(ddns_cb, MDL);
+		if (result == ISC_R_SUCCESS) {
+			return;
+		}
+	}
+
+	/* If we are done or have an error clean up */
+	dhclient_ddns_cb_free(ddns_cb, MDL);
+	return;
+}
+
+void
+client_dns_remove(struct client_state *client,
+		  struct iaddr        *addr)
+{
+	dhcp_ddns_cb_t *ddns_cb;
+	isc_result_t result;
+
+	/* if we have an old ddns request for this client, cancel it */
+	if (client->ddns_cb != NULL) {
+		ddns_cancel(client->ddns_cb, MDL);
+		client->ddns_cb = NULL;
+	}
+
+	ddns_cb = ddns_cb_alloc(MDL);
+	if (ddns_cb != NULL) {
+		ddns_cb->address = *addr;
+		ddns_cb->timeout = 0;
+
+		ddns_cb->state = DDNS_STATE_REM_FW_YXDHCID;
+		ddns_cb->flags = DDNS_UPDATE_ADDR;
+		ddns_cb->cur_func = client_dns_remove_action;
+
+		result = client_dns_update(client, ddns_cb);
+
+		if (result != ISC_R_TIMEDOUT) {
+			dhclient_ddns_cb_free(ddns_cb, MDL);
+		}
+	}
+}
+#endif /* defined NSUPDATE */
+
+
+isc_result_t dhcp_set_control_state (control_object_state_t oldstate,
+				     control_object_state_t newstate)
+{
+	struct interface_info *ip;
+	struct client_state *client;
+	struct timeval tv;
+
+	if (newstate == server_shutdown) {
+		/* Re-entry */
+		if (shutdown_signal == SIGUSR1)
+			return ISC_R_SUCCESS;
+		/* Log shutdown on signal. */
+		if ((shutdown_signal == SIGINT) ||
+		    (shutdown_signal == SIGTERM)) {
+			log_info("Received signal %d, initiating shutdown.",
+				 shutdown_signal);
+		}
+		/* Mark it was called. */
+		shutdown_signal = SIGUSR1;
+	}
+
+	/* Do the right thing for each interface. */
+	for (ip = interfaces; ip; ip = ip -> next) {
+	    for (client = ip -> client; client; client = client -> next) {
+		switch (newstate) {
+		  case server_startup:
+		    return ISC_R_SUCCESS;
+
+		  case server_running:
+		    return ISC_R_SUCCESS;
+
+		  case server_shutdown:
+		    if (client -> active &&
+			client -> active -> expiry > cur_time) {
+#if defined (NSUPDATE)
+			    if (client->config->do_forward_update) {
+				    client_dns_remove(client,
+						      &client->active->address);
+			    }
+#endif /* defined NSUPDATE */
+
+			    do_release (client);
+		    }
+		    break;
+
+		  case server_hibernate:
+		    state_stop (client);
+		    break;
+
+		  case server_awaken:
+		    state_reboot (client);
+		    break;
+		}
+	    }
+	}
+
+	if (newstate == server_shutdown) {
+		tv.tv_sec = cur_tv.tv_sec;
+		tv.tv_usec = cur_tv.tv_usec + 1;
+		add_timeout(&tv, shutdown_exit, 0, 0, 0);
+	}
+	return ISC_R_SUCCESS;
+}
+
+#if defined (NSUPDATE)
+/*
+ * Called after a timeout if the DNS update failed on the previous try.
+ * Starts the retry process.  If the retry times out it will schedule
+ * this routine to run again after a 10x wait.
+ */
+void
+client_dns_update_timeout (void *cp)
+{
+	dhcp_ddns_cb_t *ddns_cb = (dhcp_ddns_cb_t *)cp;
+	struct client_state *client = (struct client_state *)ddns_cb->lease;
+	isc_result_t status = ISC_R_FAILURE;
+
+	if ((client != NULL) &&
+	    ((client->active != NULL) ||
+	     (client->active_lease != NULL)))
+		status = client_dns_update(client, ddns_cb);
+
+	/*
+	 * A status of timedout indicates that we started the update and
+	 * have released control of the control block.  Any other status
+	 * indicates that we should clean up the control block.  We either
+	 * got a success which indicates that we didn't really need to
+	 * send an update or some other error in which case we weren't able
+	 * to start the update process.  In both cases we still own
+	 * the control block and should free it.
+	 */
+	if (status != ISC_R_TIMEDOUT) {
+		dhclient_ddns_cb_free(ddns_cb, MDL);
+	}
+}
+
+/*
+ * If the first query succeeds, the updater can conclude that it
+ * has added a new name whose only RRs are the A and DHCID RR records.
+ * The A RR update is now complete (and a client updater is finished,
+ * while a server might proceed to perform a PTR RR update).
+ *   -- "Interaction between DHCP and DNS"
+ *
+ * If the second query succeeds, the updater can conclude that the current
+ * client was the last client associated with the domain name, and that
+ * the name now contains the updated A RR. The A RR update is now
+ * complete (and a client updater is finished, while a server would
+ * then proceed to perform a PTR RR update).
+ *   -- "Interaction between DHCP and DNS"
+ *
+ * If the second query fails with NXRRSET, the updater must conclude
+ * that the client's desired name is in use by another host.  At this
+ * juncture, the updater can decide (based on some administrative
+ * configuration outside of the scope of this document) whether to let
+ * the existing owner of the name keep that name, and to (possibly)
+ * perform some name disambiguation operation on behalf of the current
+ * client, or to replace the RRs on the name with RRs that represent
+ * the current client. If the configured policy allows replacement of
+ * existing records, the updater submits a query that deletes the
+ * existing A RR and the existing DHCID RR, adding A and DHCID RRs that
+ * represent the IP address and client-identity of the new client.
+ *   -- "Interaction between DHCP and DNS"
+ */
+
+/* The first and second stages are pretty similar so we combine them */
+void
+client_dns_update_action(dhcp_ddns_cb_t *ddns_cb,
+			 isc_result_t    eresult)
+{
+	isc_result_t result;
+	struct timeval tv;
+
+	switch(eresult) {
+	case ISC_R_SUCCESS:
+	default:
+		/* Either we succeeded or broke in a bad way, clean up */
+		break;
+
+	case DNS_R_YXRRSET:
+		/*
+		 * This is the only difference between the two stages,
+		 * check to see if it is the first stage, in which case
+		 * start the second stage
+		 */
+		if (ddns_cb->state == DDNS_STATE_ADD_FW_NXDOMAIN) {
+			ddns_cb->state = DDNS_STATE_ADD_FW_YXDHCID;
+			ddns_cb->cur_func = client_dns_update_action;
+
+			result = ddns_modify_fwd(ddns_cb, MDL);
+			if (result == ISC_R_SUCCESS) {
+				return;
+			}
+		}
+		break;
+
+	case ISC_R_TIMEDOUT:
+		/*
+		 * We got a timeout response from the DNS module.  Schedule
+		 * another attempt for later.  We forget the name, dhcid and
+		 * zone so if it gets changed we will get the new information.
+		 */
+		data_string_forget(&ddns_cb->fwd_name, MDL);
+		data_string_forget(&ddns_cb->dhcid, MDL);
+		if (ddns_cb->zone != NULL) {
+			forget_zone((struct dns_zone **)&ddns_cb->zone);
+		}
+
+		/* Reset to doing the first stage */
+		ddns_cb->state    = DDNS_STATE_ADD_FW_NXDOMAIN;
+		ddns_cb->cur_func = client_dns_update_action;
+
+		/* and update our timer */
+		if (ddns_cb->timeout < 3600)
+			ddns_cb->timeout *= 10;
+		tv.tv_sec = cur_tv.tv_sec + ddns_cb->timeout;
+		tv.tv_usec = cur_tv.tv_usec;
+		add_timeout(&tv, client_dns_update_timeout,
+			    ddns_cb, NULL, NULL);
+		return;
+	}
+
+	dhclient_ddns_cb_free(ddns_cb, MDL);
+	return;
+}
+
+/* See if we should do a DNS update, and if so, do it. */
+
+isc_result_t
+client_dns_update(struct client_state *client, dhcp_ddns_cb_t *ddns_cb)
+{
+	struct data_string client_identifier;
+	struct option_cache *oc;
+	int ignorep;
+	int result;
+	int ddns_v4_type;
+	isc_result_t rcode;
+
+	/* If we didn't send an FQDN option, we certainly aren't going to
+	   be doing an update. */
+	if (!client -> sent_options)
+		return ISC_R_SUCCESS;
+
+	/* If we don't have a lease, we can't do an update. */
+	if ((client->active == NULL) && (client->active_lease == NULL))
+		return ISC_R_SUCCESS;
+
+	/* If we set the no client update flag, don't do the update. */
+	if ((oc = lookup_option (&fqdn_universe, client -> sent_options,
+				  FQDN_NO_CLIENT_UPDATE)) &&
+	    evaluate_boolean_option_cache (&ignorep, (struct packet *)0,
+					   (struct lease *)0, client,
+					   client -> sent_options,
+					   (struct option_state *)0,
+					   &global_scope, oc, MDL))
+		return ISC_R_SUCCESS;
+
+	/* If we set the "server, please update" flag, or didn't set it
+	   to false, don't do the update. */
+	if (!(oc = lookup_option (&fqdn_universe, client -> sent_options,
+				  FQDN_SERVER_UPDATE)) ||
+	    evaluate_boolean_option_cache (&ignorep, (struct packet *)0,
+					   (struct lease *)0, client,
+					   client -> sent_options,
+					   (struct option_state *)0,
+					   &global_scope, oc, MDL))
+		return ISC_R_SUCCESS;
+
+	/* If no FQDN option was supplied, don't do the update. */
+	if (!(oc = lookup_option (&fqdn_universe, client -> sent_options,
+				  FQDN_FQDN)) ||
+	    !evaluate_option_cache (&ddns_cb->fwd_name, (struct packet *)0,
+				    (struct lease *)0, client,
+				    client -> sent_options,
+				    (struct option_state *)0,
+				    &global_scope, oc, MDL))
+		return ISC_R_SUCCESS;
+
+	/*
+	 * Construct the DHCID value for use in the DDNS update process
+	 * We have the newer standard version and the older interim version
+	 * chosen by the '-I' option.  The interim version is left as is
+	 * for backwards compatibility.  The standard version is based on
+	 * RFC 4701 section 3.3
+	 */
+
+	result = 0;
+	POST(result);
+	memset(&client_identifier, 0, sizeof(client_identifier));
+
+	if (std_dhcid == 1) {
+		/* standard style */
+		ddns_cb->dhcid_class = dns_rdatatype_dhcid;
+		ddns_v4_type = 1;
+	} else {
+		/* interim style */
+		ddns_cb->dhcid_class = dns_rdatatype_txt;
+		/* for backwards compatibility */
+		ddns_v4_type = DHO_DHCP_CLIENT_IDENTIFIER;
+	}
+	if (client->active_lease != NULL) {
+		/* V6 request, get the client identifier, then
+		 * construct the dhcid for either standard
+		 * or interim */
+		if (((oc = lookup_option(&dhcpv6_universe,
+					 client->sent_options,
+					 D6O_CLIENTID)) != NULL) &&
+		    evaluate_option_cache(&client_identifier, NULL,
+					  NULL, client,
+					  client->sent_options, NULL,
+					  &global_scope, oc, MDL)) {
+			result = get_dhcid(ddns_cb, 2,
+					   client_identifier.data,
+					   client_identifier.len);
+			data_string_forget(&client_identifier, MDL);
+		} else
+			log_fatal("Impossible condition at %s:%d.", MDL);
+	} else {
+		/*
+		 * V4 request, use the client id if there is one or the
+		 * mac address if there isn't.  If we have a client id
+		 * we check to see if it is an embedded DUID.
+		 */
+		if (((oc = lookup_option(&dhcp_universe,
+					 client->sent_options,
+					 DHO_DHCP_CLIENT_IDENTIFIER)) != NULL) &&
+		    evaluate_option_cache(&client_identifier, NULL,
+					  NULL, client,
+					  client->sent_options, NULL,
+					  &global_scope, oc, MDL)) {
+			if ((std_dhcid == 1) && (duid_v4 == 1) &&
+			    (client_identifier.data[0] == 255)) {
+				/*
+				 * This appears to be an embedded DUID,
+				 * extract it and treat it as such
+				 */
+				if (client_identifier.len <= 5)
+					log_fatal("Impossible condition at %s:%d.",
+						  MDL);
+				result = get_dhcid(ddns_cb, 2,
+						   client_identifier.data + 5,
+						   client_identifier.len - 5);
+			} else {
+				result = get_dhcid(ddns_cb, ddns_v4_type,
+						   client_identifier.data,
+						   client_identifier.len);
+			}
+			data_string_forget(&client_identifier, MDL);
+		} else
+			result = get_dhcid(ddns_cb, 0,
+					   client->interface->hw_address.hbuf,
+					   client->interface->hw_address.hlen);
+	}
+
+	if (!result) {
+		return ISC_R_SUCCESS;
+	}
+
+	/*
+	 * Perform updates.
+	 */
+	if (ddns_cb->fwd_name.len && ddns_cb->dhcid.len) {
+		rcode = ddns_modify_fwd(ddns_cb, MDL);
+	} else
+		rcode = ISC_R_FAILURE;
+
+	/*
+	 * A success from the modify routine means we are performing
+	 * async processing, for which we use the timedout error message.
+	 */
+	if (rcode == ISC_R_SUCCESS) {
+		rcode = ISC_R_TIMEDOUT;
+	}
+
+	return rcode;
+}
+
+
+/*
+ * Schedule the first update.  They will continue to retry occasionally
+ * until they no longer time out (or fail).
+ */
+void
+dhclient_schedule_updates(struct client_state *client,
+			  struct iaddr        *addr,
+			  int                  offset)
+{
+	dhcp_ddns_cb_t *ddns_cb;
+	struct timeval tv;
+
+	if (!client->config->do_forward_update)
+		return;
+
+	/* cancel any outstanding ddns requests */
+	if (client->ddns_cb != NULL) {
+		ddns_cancel(client->ddns_cb, MDL);
+		client->ddns_cb = NULL;
+	}
+
+	ddns_cb = ddns_cb_alloc(MDL);
+
+	if (ddns_cb != NULL) {
+		ddns_cb->lease = (void *)client;
+		ddns_cb->address = *addr;
+		ddns_cb->timeout = 1;
+
+		/*
+		 * XXX: DNS TTL is a problem we need to solve properly.
+		 * Until that time, 300 is a placeholder default for
+		 * something that is less insane than a value scaled
+		 * by lease timeout.
+		 */
+		ddns_cb->ttl = 300;
+
+		ddns_cb->state = DDNS_STATE_ADD_FW_NXDOMAIN;
+		ddns_cb->cur_func = client_dns_update_action;
+		ddns_cb->flags = DDNS_UPDATE_ADDR | DDNS_INCLUDE_RRSET;
+
+		client->ddns_cb = ddns_cb;
+		tv.tv_sec = cur_tv.tv_sec + offset;
+		tv.tv_usec = cur_tv.tv_usec;
+		add_timeout(&tv, client_dns_update_timeout,
+			    ddns_cb, NULL, NULL);
+	} else {
+		log_error("Unable to allocate dns update state for %s",
+			  piaddr(*addr));
+	}
+}
+#endif /* defined NSUPDATE */
+
+void
+dhcpv4_client_assignments(void)
+{
+	struct servent *ent;
+
+	if (path_dhclient_pid == NULL)
+		path_dhclient_pid = _PATH_DHCLIENT_PID;
+	if (path_dhclient_db == NULL)
+		path_dhclient_db = _PATH_DHCLIENT_DB;
+
+	/* Default to the DHCP/BOOTP port. */
+	if (!local_port) {
+		/* If we're faking a relay agent, and we're not using loopback,
+		   use the server port, not the client port. */
+		if (mockup_relay && giaddr.s_addr != htonl(INADDR_LOOPBACK)) {
+			local_port = htons(67);
+		} else {
+			ent = getservbyname("dhcpc", "udp");
+			if (ent == NULL)
+				ent = getservbyname("bootpc", "udp");
+			if (ent == NULL)
+				local_port = htons(68);
+			else
+				local_port = ent->s_port;
+#ifndef __CYGWIN32__
+			endservent ();
+#endif
+		}
+	}
+
+	/* If we're faking a relay agent, and we're not using loopback,
+	   we're using the server port, not the client port. */
+	if (mockup_relay && giaddr.s_addr != htonl(INADDR_LOOPBACK)) {
+		remote_port = local_port;
+	} else
+		remote_port = htons(ntohs(local_port) - 1);   /* XXX */
+}
+
+/*
+ * The following routines are used to check that certain
+ * strings are reasonable before we pass them to the scripts.
+ * This avoids some problems with scripts treating the strings
+ * as commands - see ticket 23722
+ * The domain checking code should be done as part of assembling
+ * the string but we are doing it here for now due to time
+ * constraints.
+ */
+
+static int check_domain_name(const char *ptr, size_t len, int dots)
+{
+	const char *p;
+
+	/* not empty or complete length not over 255 characters   */
+	if ((len == 0) || (len > 256))
+		return(-1);
+
+	/* consists of [[:alnum:]-]+ labels separated by [.]      */
+	/* a [_] is against RFC but seems to be "widely used"...  */
+	for (p=ptr; (*p != 0) && (len-- > 0); p++) {
+		if ((*p == '-') || (*p == '_')) {
+			/* not allowed at begin or end of a label */
+			if (((p - ptr) == 0) || (len == 0) || (p[1] == '.'))
+				return(-1);
+		} else if (*p == '.') {
+			/* each label has to be 1-63 characters;
+			   we allow [.] at the end ('foo.bar.')   */
+			size_t d = p - ptr;
+			if ((d <= 0) || (d >= 64))
+				return(-1);
+			ptr = p + 1; /* jump to the next label    */
+			if ((dots > 0) && (len > 0))
+				dots--;
+		} else if (isalnum((unsigned char)*p) == 0) {
+			/* also numbers at the begin are fine     */
+			return(-1);
+		}
+	}
+	return(dots ? -1 : 0);
+}
+
+static int check_domain_name_list(const char *ptr, size_t len, int dots)
+{
+	const char *p;
+	int ret = -1; /* at least one needed */
+
+	if ((ptr == NULL) || (len == 0))
+		return(-1);
+
+	for (p=ptr; (*p != 0) && (len > 0); p++, len--) {
+		if (*p != ' ')
+			continue;
+		if (p > ptr) {
+			if (check_domain_name(ptr, p - ptr, dots) != 0)
+				return(-1);
+			ret = 0;
+		}
+		ptr = p + 1;
+	}
+	if (p > ptr)
+		return(check_domain_name(ptr, p - ptr, dots));
+	else
+		return(ret);
+}
+
+static int check_option_values(struct universe *universe,
+			       unsigned int opt,
+			       const char *ptr,
+			       size_t len)
+{
+	if (ptr == NULL)
+		return(-1);
+
+	/* just reject options we want to protect, will be escaped anyway */
+	if ((universe == NULL) || (universe == &dhcp_universe)) {
+		switch(opt) {
+		      case DHO_DOMAIN_NAME:
+#ifdef ACCEPT_LIST_IN_DOMAIN_NAME
+			      return check_domain_name_list(ptr, len, 0);
+#else
+			      return check_domain_name(ptr, len, 0);
+#endif
+		      case DHO_HOST_NAME:
+		      case DHO_NIS_DOMAIN:
+		      case DHO_NETBIOS_SCOPE:
+			return check_domain_name(ptr, len, 0);
+			break;
+		      case DHO_DOMAIN_SEARCH:
+			return check_domain_name_list(ptr, len, 0);
+			break;
+		      case DHO_ROOT_PATH:
+			if (len == 0)
+				return(-1);
+			for (; (*ptr != 0) && (len-- > 0); ptr++) {
+				if(!(isalnum((unsigned char)*ptr) ||
+				     *ptr == '#'  || *ptr == '%' ||
+				     *ptr == '+'  || *ptr == '-' ||
+				     *ptr == '_'  || *ptr == ':' ||
+				     *ptr == '.'  || *ptr == ',' ||
+				     *ptr == '@'  || *ptr == '~' ||
+				     *ptr == '\\' || *ptr == '/' ||
+				     *ptr == '['  || *ptr == ']' ||
+				     *ptr == '='  || *ptr == ' '))
+					return(-1);
+			}
+			return(0);
+			break;
+		}
+	}
+
+#ifdef DHCPv6
+	if (universe == &dhcpv6_universe) {
+		switch(opt) {
+		      case D6O_SIP_SERVERS_DNS:
+		      case D6O_DOMAIN_SEARCH:
+		      case D6O_NIS_DOMAIN_NAME:
+		      case D6O_NISP_DOMAIN_NAME:
+			return check_domain_name_list(ptr, len, 0);
+			break;
+		}
+	}
+#endif
+
+	return(0);
+}
+
+static void
+add_reject(struct packet *packet) {
+	struct iaddrmatchlist *list;
+
+	list = dmalloc(sizeof(struct iaddrmatchlist), MDL);
+	if (!list)
+		log_fatal ("no memory for reject list!");
+
+	/*
+	 * client_addr is misleading - it is set to source address in common
+	 * code.
+	 */
+	list->match.addr = packet->client_addr;
+	/* Set mask to indicate host address. */
+	list->match.mask.len = list->match.addr.len;
+	memset(list->match.mask.iabuf, 0xff, sizeof(list->match.mask.iabuf));
+
+	/* Append to reject list for the source interface. */
+	list->next = packet->interface->client->config->reject_list;
+	packet->interface->client->config->reject_list = list;
+
+	/*
+	 * We should inform user that we won't be accepting this server
+	 * anymore.
+	 */
+	log_info("Server added to list of rejected servers.");
+}
+
+#if defined(NSUPDATE)
+/* Wrapper function around common ddns_cb_free function that ensures
+ * we set the client_state pointer to the control block to NULL. */
+static void
+dhclient_ddns_cb_free(dhcp_ddns_cb_t *ddns_cb, char* file, int line) {
+    if (ddns_cb) {
+        struct client_state *client = (struct client_state *)ddns_cb->lease;
+        if (client != NULL) {
+            client->ddns_cb = NULL;
+        }
+
+        ddns_cb_free(ddns_cb, file, line);
+    }
+}
+#endif /* defined NSUPDATE */
+
+#if defined(DHCPv6) && defined(DHCP4o6)
+/*
+ * \brief Omapi I/O handler
+ *
+ * The inter-process communication receive handler.
+ *
+ * On the DHCPv6 side, the message is either a POLL (which is answered
+ *  by a START or a STOP) or a DHCPv4-QUERY (which is forwarded to
+ *  DHCPv4 over DHCPv6 servers by forw_dhcpv4_query()).
+ *
+ * On the DHCPv4 side, the message is either a START, a STOP
+ *  (both for the DHCP4 over DHCPv6 state machine) or a DHCPv4-RESPONSE
+ *  (which is processed by recv_dhcpv4_response()).
+ *
+ * \param h the OMAPI object
+ * \return a result for I/O success or error (used by the I/O subsystem)
+ */
+isc_result_t dhcpv4o6_handler(omapi_object_t *h) {
+	char buf[65536];
+	char start_msg[5] = { 'S', 'T', 'A', 'R', 'T' };
+	char stop_msg[4] = { 'S', 'T', 'O', 'P' };
+	char poll_msg[4] = { 'P', 'O', 'L', 'L' };
+	struct data_string raw;
+	int cc;
+
+	if (h->type != dhcp4o6_type)
+		return DHCP_R_INVALIDARG;
+
+	cc = recv(dhcp4o6_fd, buf, sizeof(buf), 0);
+	if (cc <= 0)
+		return ISC_R_UNEXPECTED;
+
+	if (local_family == AF_INET6) {
+		if ((cc == 4) &&
+		    (memcmp(buf, poll_msg, sizeof(poll_msg)) == 0)) {
+			log_info("RCV: POLL");
+			if (dhcp4o6_state < 0)
+				cc = send(dhcp4o6_fd, stop_msg,
+					  sizeof(stop_msg), 0);
+			else
+				cc = send(dhcp4o6_fd, start_msg,
+					  sizeof(start_msg), 0);
+			if (cc < 0) {
+				log_error("dhcpv4o6_handler: send(): %m");
+				return ISC_R_IOERROR;
+			}
+		} else {
+			if (cc < DHCP_FIXED_NON_UDP + 8)
+				return ISC_R_UNEXPECTED;
+			memset(&raw, 0, sizeof(raw));
+			if (!buffer_allocate(&raw.buffer, cc, MDL)) {
+				log_error("dhcpv4o6_handler: "
+					  "no memory buffer.");
+				return ISC_R_NOMEMORY;
+			}
+			raw.data = raw.buffer->data;
+			raw.len = cc;
+			memcpy(raw.buffer->data, buf, cc);
+
+			forw_dhcpv4_query(&raw);
+
+			data_string_forget(&raw, MDL);
+		}
+	} else {
+		if ((cc == 4) &&
+		    (memcmp(buf, stop_msg, sizeof(stop_msg)) == 0)) {
+			log_info("RCV: STOP");
+			if (dhcp4o6_state > 0) {
+				dhcp4o6_state = 0;
+				dhcp4o6_poll(NULL);
+			}
+		} else if ((cc == 5) &&
+			   (memcmp(buf, start_msg, sizeof(start_msg)) == 0)) {
+			log_info("RCV: START");
+			if (dhcp4o6_state == 0)
+				cancel_timeout(dhcp4o6_poll, NULL);
+			dhcp4o6_state = 1;
+			dhcp4o6_resume();
+		} else {
+			if (cc < DHCP_FIXED_NON_UDP + 16)
+				return ISC_R_UNEXPECTED;
+			memset(&raw, 0, sizeof(raw));
+			if (!buffer_allocate(&raw.buffer, cc, MDL)) {
+				log_error("dhcpv4o6_handler: "
+					  "no memory buffer.");
+				return ISC_R_NOMEMORY;
+			}
+			raw.data = raw.buffer->data;
+			raw.len = cc;
+			memcpy(raw.buffer->data, buf, cc);
+
+			recv_dhcpv4_response(&raw);
+
+			data_string_forget(&raw, MDL);
+		}
+	}
+
+	return ISC_R_SUCCESS;
+}
+
+/*
+ * \brief Poll the DHCPv6 client
+ *  (DHCPv4 client function)
+ *
+ * A POLL message is sent to the DHCPv6 client periodically to check
+ * if the DHCPv6 is ready (i.e., has a valid DHCPv4-over-DHCPv6 server
+ * address option).
+ */
+static void dhcp4o6_poll(void *dummy) {
+	char msg[4] = { 'P', 'O', 'L', 'L' };
+	struct timeval tv;
+	int cc;
+
+	IGNORE_UNUSED(dummy);
+
+	if (dhcp4o6_state < 0)
+		dhcp4o6_state = 0;
+
+	log_info("POLL");
+
+	cc = send(dhcp4o6_fd, msg, sizeof(msg), 0);
+	if (cc < 0)
+		log_error("dhcp4o6_poll: send(): %m");
+
+	tv.tv_sec = cur_time + 60;
+	tv.tv_usec = random() % 1000000;
+
+	add_timeout(&tv, dhcp4o6_poll, NULL, 0, 0);
+}
+
+/*
+ * \brief Resume pending operations
+ *  (DHCPv4 client function)
+ *
+ * A START message was received from the DHCPv6 client so pending
+ * operations (RELEASE or REBOOT) must be resumed.
+ */
+static void dhcp4o6_resume() {
+	struct interface_info *ip;
+	struct client_state *client;
+
+	for (ip = interfaces; ip != NULL; ip = ip->next) {
+		for (client = ip->client; client != NULL;
+		     client = client->next) {
+			if (client->pending == P_RELEASE)
+				do_release(client);
+			else if (client->pending == P_REBOOT)
+				state_reboot(client);
+		}
+	}
+}
+
+/*
+ * \brief Send a START to the DHCPv4 client
+ *  (DHCPv6 client function)
+ *
+ * First check if there is a valid DHCPv4-over-DHCPv6 server address option,
+ * and when found go UP and on a transition from another state send
+ * a START message to the DHCPv4 client.
+ */
+void dhcp4o6_start() {
+	struct interface_info *ip;
+	struct client_state *client;
+	struct dhc6_lease *lease;
+	struct option_cache *oc;
+	struct data_string addrs;
+	char msg[5] = { 'S', 'T', 'A', 'R', 'T' };
+	int cc;
+
+	memset(&addrs, 0, sizeof(addrs));
+	for (ip = interfaces; ip != NULL; ip = ip->next) {
+		for (client = ip->client; client != NULL;
+		     client = client->next) {
+			if ((client->state != S_BOUND) &&
+			    (client->state != S_RENEWING) &&
+			    (client->state != S_REBINDING))
+				continue;
+			lease = client->active_lease;
+			if ((lease == NULL) || lease->released)
+				continue;
+			oc = lookup_option(&dhcpv6_universe,
+					   lease->options,
+					   D6O_DHCP4_O_DHCP6_SERVER);
+			if ((oc == NULL) ||
+			    !evaluate_option_cache(&addrs, NULL, NULL, NULL,
+						   lease->options, NULL,
+						   &global_scope, oc, MDL))
+				continue;
+			if ((addrs.len % 16) != 0) {
+				data_string_forget(&addrs, MDL);
+				continue;
+			}
+			data_string_forget(&addrs, MDL);
+			goto found;
+		}
+	}
+	log_info("dhcp4o6_start: failed");
+	dhcp4o6_stop();
+	return;
+
+found:
+	if (dhcp4o6_state == 1)
+		return;
+	log_info("dhcp4o6_start: go to UP");
+	dhcp4o6_state = 1;
+
+	cc = send(dhcp4o6_fd, msg, sizeof(msg), 0);
+	if (cc < 0)
+		log_info("dhcp4o6_start: send(): %m");
+}
+
+/*
+ * Send a STOP to the DHCPv4 client
+ *  (DHCPv6 client function)
+ *
+ * Go DOWN and on a transition from another state send a STOP message
+ * to the DHCPv4 client.
+ */
+static void dhcp4o6_stop() {
+	char msg[4] = { 'S', 'T', 'O', 'P' };
+	int cc;
+
+	if (dhcp4o6_state == -1)
+		return;
+
+	log_info("dhcp4o6_stop: go to DOWN");
+	dhcp4o6_state = -1;
+
+	cc = send(dhcp4o6_fd, msg, sizeof(msg), 0);
+	if (cc < 0)
+		log_error("dhcp4o6_stop: send(): %m");
+}
+#endif /* DHCPv6 && DHCP4o6 */
Index: client
===================================================================
--- client	(nonexistent)
+++ client	(revision 5)

Property changes on: client
___________________________________________________________________
Added: svn:ignore
## -0,0 +1,73 ##
+
+# install dir
+dist
+
+# Target build dirs
+.a1x-newlib
+.a2x-newlib
+.at91sam7s-newlib
+
+.build-machine
+
+.a1x-glibc
+.a2x-glibc
+.h3-glibc
+.h5-glibc
+.i586-glibc
+.i686-glibc
+.imx6-glibc
+.jz47xx-glibc
+.makefile
+.am335x-glibc
+.omap543x-glibc
+.p5600-glibc
+.power8-glibc
+.power8le-glibc
+.power9-glibc
+.power9le-glibc
+.m1000-glibc
+.riscv64-glibc
+.rk328x-glibc
+.rk33xx-glibc
+.rk339x-glibc
+.s8xx-glibc
+.s9xx-glibc
+.x86_64-glibc
+
+# Hidden files (each file)
+.makefile
+.dist
+.rootfs
+
+# src & hw requires
+.src_requires
+.src_requires_depend
+.requires
+.requires_depend
+
+# Tarballs
+*.gz
+*.bz2
+*.lz
+*.xz
+*.tgz
+*.txz
+
+# Signatures
+*.asc
+*.sig
+*.sign
+*.sha1sum
+
+# Patches
+*.patch
+
+# Descriptions
+*.dsc
+*.txt
+
+# Default linux config files
+*.defconfig
+
+# backup copies
+*~
Index: relay/dhcrelay.c
===================================================================
--- relay/dhcrelay.c	(nonexistent)
+++ relay/dhcrelay.c	(revision 5)
@@ -0,0 +1,2151 @@
+/* dhcrelay.c
+
+   DHCP/BOOTP Relay Agent. */
+
+/*
+ * Copyright(c) 2004-2020 by Internet Systems Consortium, Inc.("ISC")
+ * Copyright(c) 1997-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.
+ *   950 Charter Street
+ *   Redwood City, CA 94063
+ *   <info@isc.org>
+ *   https://www.isc.org/
+ *
+ */
+
+#include "dhcpd.h"
+#include <syslog.h>
+#include <signal.h>
+#include <sys/time.h>
+#include <isc/file.h>
+
+TIME default_lease_time = 43200; /* 12 hours... */
+TIME max_lease_time = 86400; /* 24 hours... */
+struct tree_cache *global_options[256];
+
+struct option *requested_opts[2];
+
+/* Needed to prevent linking against conflex.c. */
+int lexline;
+int lexchar;
+char *token_line;
+char *tlname;
+
+const char *path_dhcrelay_pid = _PATH_DHCRELAY_PID;
+isc_boolean_t no_dhcrelay_pid = ISC_FALSE;
+/* False (default) => we write and use a pid file */
+isc_boolean_t no_pid_file = ISC_FALSE;
+
+int bogus_agent_drops = 0;	/* Packets dropped because agent option
+				   field was specified and we're not relaying
+				   packets that already have an agent option
+				   specified. */
+int bogus_giaddr_drops = 0;	/* Packets sent to us to relay back to a
+				   client, but with a bogus giaddr. */
+int client_packets_relayed = 0;	/* Packets relayed from client to server. */
+int server_packet_errors = 0;	/* Errors sending packets to servers. */
+int server_packets_relayed = 0;	/* Packets relayed from server to client. */
+int client_packet_errors = 0;	/* Errors sending packets to clients. */
+
+int add_agent_options = 0;	/* If nonzero, add relay agent options. */
+int add_rfc3527_suboption = 0;	/* If nonzero, add RFC3527 link selection sub-option. */
+
+int agent_option_errors = 0;    /* Number of packets forwarded without
+				   agent options because there was no room. */
+int drop_agent_mismatches = 0;	/* If nonzero, drop server replies that
+				   don't have matching circuit-id's. */
+int corrupt_agent_options = 0;	/* Number of packets dropped because
+				   relay agent information option was bad. */
+int missing_agent_option = 0;	/* Number of packets dropped because no
+				   RAI option matching our ID was found. */
+int bad_circuit_id = 0;		/* Circuit ID option in matching RAI option
+				   did not match any known circuit ID. */
+int missing_circuit_id = 0;	/* Circuit ID option in matching RAI option
+				   was missing. */
+int max_hop_count = 10;		/* Maximum hop count */
+
+int no_daemon = 0;
+int dfd[2] = { -1, -1 };
+
+#ifdef DHCPv6
+	/* Force use of DHCPv6 interface-id option. */
+isc_boolean_t use_if_id = ISC_FALSE;
+#endif
+
+	/* Maximum size of a packet with agent options added. */
+int dhcp_max_agent_option_packet_length = DHCP_MTU_MIN;
+
+	/* What to do about packets we're asked to relay that
+	   already have a relay option: */
+enum { forward_and_append,	/* Forward and append our own relay option. */
+       forward_and_replace,	/* Forward, but replace theirs with ours. */
+       forward_untouched,	/* Forward without changes. */
+       discard } agent_relay_mode = forward_and_replace;
+
+/* Relay agent server list. */
+struct server_list {
+	struct server_list *next;
+	struct sockaddr_in to;
+} *servers;
+
+struct interface_info *uplink = NULL;
+
+#ifdef DHCPv6
+struct stream_list {
+	struct stream_list *next;
+	struct interface_info *ifp;
+	struct sockaddr_in6 link;
+	int id;
+} *downstreams, *upstreams;
+
+#ifndef UNIT_TEST
+static struct stream_list *parse_downstream(char *);
+static struct stream_list *parse_upstream(char *);
+static void setup_streams(void);
+#endif /* UNIT_TEST */
+
+/*
+ * A pointer to a subscriber id to add to the message we forward.
+ * This is primarily for testing purposes as we only have one id
+ * for the entire relay and don't determine one per client which
+ * would be more useful.
+ */
+char *dhcrelay_sub_id = NULL;
+#endif
+
+#ifndef UNIT_TEST
+static void do_relay4(struct interface_info *, struct dhcp_packet *,
+	              unsigned int, unsigned int, struct iaddr,
+		      struct hardware *);
+#endif /* UNIT_TEST */
+
+extern int add_relay_agent_options(struct interface_info *,
+				            struct dhcp_packet *, unsigned,
+				            struct in_addr);
+extern int find_interface_by_agent_option(struct dhcp_packet *,
+			                       struct interface_info **, u_int8_t *, int);
+
+extern int strip_relay_agent_options(struct interface_info *,
+				              struct interface_info **,
+				              struct dhcp_packet *, unsigned);
+
+#ifndef UNIT_TEST
+static void request_v4_interface(const char* name, int flags);
+
+static const char copyright[] =
+"Copyright 2004-2020 Internet Systems Consortium.";
+static const char arr[] = "All rights reserved.";
+static const char message[] =
+"Internet Systems Consortium DHCP Relay Agent";
+static const char url[] =
+"For info, please visit https://www.isc.org/software/dhcp/";
+
+char *progname;
+
+#ifdef DHCPv6
+#ifdef RELAY_PORT
+#define DHCRELAY_USAGE \
+"Usage: %s [-4] [-d] [-q] [-a] [-D]\n" \
+"                     [-A <length>] [-c <hops>]\n" \
+"                     [-p <port> | -rp <relay-port>]\n" \
+"                     [-pf <pid-file>] [--no-pid]\n"\
+"                     [-m append|replace|forward|discard]\n" \
+"                     [-i interface0 [ ... -i interfaceN]\n" \
+"                     [-iu interface0 [ ... -iu interfaceN]\n" \
+"                     [-id interface0 [ ... -id interfaceN]\n" \
+"                     [-U interface]\n" \
+"                     server0 [ ... serverN]\n\n" \
+"       %s -6   [-d] [-q] [-I] [-c <hops>]\n" \
+"                     [-p <port> | -rp <relay-port>]\n" \
+"                     [-pf <pid-file>] [--no-pid]\n" \
+"                     [-s <subscriber-id>]\n" \
+"                     -l lower0 [ ... -l lowerN]\n" \
+"                     -u upper0 [ ... -u upperN]\n" \
+"           lower (client link): [address%%]interface[#index]\n" \
+"           upper (server link): [address%%]interface\n\n" \
+"       %s {--version|--help|-h}"
+#else
+#define DHCRELAY_USAGE \
+"Usage: %s [-4] [-d] [-q] [-a] [-D]\n" \
+"                     [-A <length>] [-c <hops>] [-p <port>]\n" \
+"                     [-pf <pid-file>] [--no-pid]\n"\
+"                     [-m append|replace|forward|discard]\n" \
+"                     [-i interface0 [ ... -i interfaceN]\n" \
+"                     [-iu interface0 [ ... -iu interfaceN]\n" \
+"                     [-id interface0 [ ... -id interfaceN]\n" \
+"                     [-U interface]\n" \
+"                     server0 [ ... serverN]\n\n" \
+"       %s -6   [-d] [-q] [-I] [-c <hops>] [-p <port>]\n" \
+"                     [-pf <pid-file>] [--no-pid]\n" \
+"                     [-s <subscriber-id>]\n" \
+"                     -l lower0 [ ... -l lowerN]\n" \
+"                     -u upper0 [ ... -u upperN]\n" \
+"           lower (client link): [address%%]interface[#index]\n" \
+"           upper (server link): [address%%]interface\n\n" \
+"       %s {--version|--help|-h}"
+#endif
+#else /* !DHCPv6 */
+#ifdef RELAY_PORT
+#define DHCRELAY_USAGE \
+"Usage: %s [-d] [-q] [-a] [-D] [-A <length>] [-c <hops>]\n" \
+"                [-p <port> | -rp <relay-port>]\n" \
+"                [-pf <pid-file>] [--no-pid]\n" \
+"                [-m append|replace|forward|discard]\n" \
+"                [-i interface0 [ ... -i interfaceN]\n" \
+"                [-iu interface0 [ ... -iu interfaceN]\n" \
+"                [-id interface0 [ ... -id interfaceN]\n" \
+"                [-U interface]\n" \
+"                server0 [ ... serverN]\n\n" \
+"       %s {--version|--help|-h}"
+#else
+#define DHCRELAY_USAGE \
+"Usage: %s [-d] [-q] [-a] [-D] [-A <length>] [-c <hops>] [-p <port>]\n" \
+"                [-pf <pid-file>] [--no-pid]\n" \
+"                [-m append|replace|forward|discard]\n" \
+"                [-i interface0 [ ... -i interfaceN]\n" \
+"                [-iu interface0 [ ... -iu interfaceN]\n" \
+"                [-id interface0 [ ... -id interfaceN]\n" \
+"                [-U interface]\n" \
+"                server0 [ ... serverN]\n\n" \
+"       %s {--version|--help|-h}"
+#endif
+#endif
+
+/*!
+ *
+ * \brief Print the generic usage message
+ *
+ * If the user has provided an incorrect command line print out
+ * the description of the command line.  The arguments provide
+ * a way for the caller to request more specific information about
+ * the error be printed as well.  Mostly this will be that some
+ * comamnd doesn't include its argument.
+ *
+ * \param sfmt - The basic string and format for the specific error
+ * \param sarg - Generally the offending argument from the comamnd line.
+ *
+ * \return Nothing
+ */
+static const char use_noarg[] = "No argument for command: %s";
+#ifdef RELAY_PORT
+static const char use_port_defined[] = "Port already set, %s inappropriate";
+#if !defined (USE_BPF_RECEIVE) && !defined (USE_LPF_RECEIVE)
+static const char bpf_sock_support[] = "Only LPF and BPF are supported: %s";
+#endif
+#endif
+#ifdef DHCPv6
+static const char use_badproto[] = "Protocol already set, %s inappropriate";
+static const char use_v4command[] = "Command not used for DHCPv6: %s";
+static const char use_v6command[] = "Command not used for DHCPv4: %s";
+#endif
+
+static void
+usage(const char *sfmt, const char *sarg) {
+	log_info("%s %s", message, PACKAGE_VERSION);
+	log_info(copyright);
+	log_info(arr);
+	log_info(url);
+
+	/* If desired print out the specific error message */
+#ifdef PRINT_SPECIFIC_CL_ERRORS
+	if (sfmt != NULL)
+		log_error(sfmt, sarg);
+#endif
+
+	log_fatal(DHCRELAY_USAGE,
+#ifdef DHCPv6
+		  isc_file_basename(progname),
+#endif
+		  isc_file_basename(progname),
+		  isc_file_basename(progname));
+}
+
+int
+main(int argc, char **argv) {
+	isc_result_t status;
+	struct servent *ent;
+	struct server_list *sp = NULL;
+	char *service_local = NULL, *service_remote = NULL;
+	u_int16_t port_local = 0, port_remote = 0;
+	int quiet = 0;
+	int fd;
+	int i;
+#ifdef RELAY_PORT
+	int port_defined = 0;
+#endif
+#ifdef DHCPv6
+	struct stream_list *sl = NULL;
+	int local_family_set = 0;
+#endif
+
+#ifdef OLD_LOG_NAME
+	progname = "dhcrelay";
+#else
+	progname = argv[0];
+#endif
+
+	local_port = 0;
+	remote_port = 0;
+
+	/* Make sure that file descriptors 0(stdin), 1,(stdout), and
+	   2(stderr) are open. To do this, we assume that when we
+	   open a file the lowest available file descriptor is used. */
+	fd = open("/dev/null", O_RDWR);
+	if (fd == 0)
+		fd = open("/dev/null", O_RDWR);
+	if (fd == 1)
+		fd = open("/dev/null", O_RDWR);
+	if (fd == 2)
+		log_perror = 0; /* No sense logging to /dev/null. */
+	else if (fd != -1)
+		close(fd);
+
+	openlog(isc_file_basename(progname), DHCP_LOG_OPTIONS, LOG_DAEMON);
+
+#if !defined(DEBUG)
+	setlogmask(LOG_UPTO(LOG_INFO));
+#endif
+
+	/* Parse arguments changing no_daemon */
+	for (i = 1; i < argc; i++) {
+		if (!strcmp(argv[i], "-d")) {
+			no_daemon = 1;
+		} else if (!strcmp(argv[i], "--version")) {
+			log_info("isc-dhcrelay-%s", PACKAGE_VERSION);
+			exit(0);
+		} else if (!strcmp(argv[i], "--help") ||
+			   !strcmp(argv[i], "-h")) {
+			log_info(DHCRELAY_USAGE,
+#ifdef DHCPv6
+				 isc_file_basename(progname),
+#endif
+				 isc_file_basename(progname),
+				 isc_file_basename(progname));
+			exit(0);
+		}
+	}
+	/* When not forbidden prepare to become a daemon */
+	if (!no_daemon) {
+		int pid;
+
+		if (pipe(dfd) == -1)
+			log_fatal("Can't get pipe: %m");
+		if ((pid = fork ()) < 0)
+			log_fatal("Can't fork daemon: %m");
+		if (pid != 0) {
+			/* Parent: wait for the child to start */
+			int n;
+
+			(void) close(dfd[1]);
+			do {
+				char buf;
+
+				n = read(dfd[0], &buf, 1);
+				if (n == 1)
+					_exit(0);
+			} while (n == -1 && errno == EINTR);
+			_exit(1);
+		}
+		/* Child */
+		(void) close(dfd[0]);
+	}
+
+
+	/* Set up the isc and dns library managers */
+	status = dhcp_context_create(DHCP_CONTEXT_PRE_DB, NULL, NULL);
+	if (status != ISC_R_SUCCESS)
+		log_fatal("Can't initialize context: %s",
+			  isc_result_totext(status));
+
+	/* Set up the OMAPI. */
+	status = omapi_init();
+	if (status != ISC_R_SUCCESS)
+		log_fatal("Can't initialize OMAPI: %s",
+			   isc_result_totext(status));
+
+	/* Set up the OMAPI wrappers for the interface object. */
+	interface_setup();
+
+	for (i = 1; i < argc; i++) {
+		if (!strcmp(argv[i], "-4")) {
+#ifdef DHCPv6
+			if (local_family_set && (local_family == AF_INET6)) {
+				usage(use_badproto, "-4");
+			}
+			local_family_set = 1;
+			local_family = AF_INET;
+		} else if (!strcmp(argv[i], "-6")) {
+			if (local_family_set && (local_family == AF_INET)) {
+				usage(use_badproto, "-6");
+			}
+			local_family_set = 1;
+			local_family = AF_INET6;
+#endif
+		} else if (!strcmp(argv[i], "-d")) {
+			/* no_daemon = 1; */
+		} else if (!strcmp(argv[i], "-q")) {
+			quiet = 1;
+			quiet_interface_discovery = 1;
+		} else if (!strcmp(argv[i], "-p")) {
+			if (++i == argc)
+				usage(use_noarg, argv[i-1]);
+#ifdef RELAY_PORT
+			if (port_defined)
+				usage(use_port_defined, argv[i-1]);
+			port_defined = 1;
+#endif
+			local_port = validate_port(argv[i]);
+			log_debug("binding to user-specified port %d",
+				  ntohs(local_port));
+#ifdef RELAY_PORT
+		} else if (!strcmp(argv[i], "-rp")) {
+			if (++i == argc)
+				usage(use_noarg, argv[i-1]);
+			if (port_defined)
+				usage(use_port_defined, argv[i-1]);
+			port_defined = 1;
+			relay_port = validate_port(argv[i]);
+			log_debug("binding to user-specified relay port %d",
+				  ntohs(relay_port));
+			add_agent_options = 1;
+#endif
+		} else if (!strcmp(argv[i], "-c")) {
+			int hcount;
+			if (++i == argc)
+				usage(use_noarg, argv[i-1]);
+			hcount = atoi(argv[i]);
+			if (hcount <= 255)
+				max_hop_count= hcount;
+			else
+				usage("Bad hop count to -c: %s", argv[i]);
+ 		} else if (!strcmp(argv[i], "-i")) {
+#ifdef DHCPv6
+			if (local_family_set && (local_family == AF_INET6)) {
+				usage(use_v4command, argv[i]);
+			}
+			local_family_set = 1;
+			local_family = AF_INET;
+#endif
+			if (++i == argc) {
+				usage(use_noarg, argv[i-1]);
+			}
+
+			request_v4_interface(argv[i], INTERFACE_STREAMS);
+		} else if (!strcmp(argv[i], "-iu")) {
+#ifdef DHCPv6
+			if (local_family_set && (local_family == AF_INET6)) {
+				usage(use_v4command, argv[i]);
+			}
+			local_family_set = 1;
+			local_family = AF_INET;
+#endif
+			if (++i == argc) {
+				usage(use_noarg, argv[i-1]);
+			}
+
+			request_v4_interface(argv[i], INTERFACE_UPSTREAM);
+		} else if (!strcmp(argv[i], "-id")) {
+#ifdef DHCPv6
+			if (local_family_set && (local_family == AF_INET6)) {
+				usage(use_v4command, argv[i]);
+			}
+			local_family_set = 1;
+			local_family = AF_INET;
+#endif
+			if (++i == argc) {
+				usage(use_noarg, argv[i-1]);
+			}
+
+			request_v4_interface(argv[i], INTERFACE_DOWNSTREAM);
+		} else if (!strcmp(argv[i], "-a")) {
+#ifdef DHCPv6
+			if (local_family_set && (local_family == AF_INET6)) {
+				usage(use_v4command, argv[i]);
+			}
+			local_family_set = 1;
+			local_family = AF_INET;
+#endif
+			add_agent_options = 1;
+		} else if (!strcmp(argv[i], "-A")) {
+#ifdef DHCPv6
+			if (local_family_set && (local_family == AF_INET6)) {
+				usage(use_v4command, argv[i]);
+			}
+			local_family_set = 1;
+			local_family = AF_INET;
+#endif
+			if (++i == argc)
+				usage(use_noarg, argv[i-1]);
+
+			dhcp_max_agent_option_packet_length = atoi(argv[i]);
+
+			if (dhcp_max_agent_option_packet_length > DHCP_MTU_MAX)
+				log_fatal("%s: packet length exceeds "
+					  "longest possible MTU\n",
+					  argv[i]);
+		} else if (!strcmp(argv[i], "-m")) {
+#ifdef DHCPv6
+			if (local_family_set && (local_family == AF_INET6)) {
+				usage(use_v4command, argv[i]);
+			}
+			local_family_set = 1;
+			local_family = AF_INET;
+#endif
+			if (++i == argc)
+				usage(use_noarg, argv[i-1]);
+			if (!strcasecmp(argv[i], "append")) {
+				agent_relay_mode = forward_and_append;
+			} else if (!strcasecmp(argv[i], "replace")) {
+				agent_relay_mode = forward_and_replace;
+			} else if (!strcasecmp(argv[i], "forward")) {
+				agent_relay_mode = forward_untouched;
+			} else if (!strcasecmp(argv[i], "discard")) {
+				agent_relay_mode = discard;
+			} else
+				usage("Unknown argument to -m: %s", argv[i]);
+		} else if (!strcmp(argv [i], "-U")) {
+			if (++i == argc)
+				usage(use_noarg, argv[i-1]);
+
+			if (uplink) {
+				usage("more than one uplink (-U) specified: %s"
+				      ,argv[i]);
+			}
+
+			/* Allocate the uplink interface */
+			status = interface_allocate(&uplink, MDL);
+			if (status != ISC_R_SUCCESS) {
+				log_fatal("%s: uplink interface_allocate: %s",
+					 argv[i], isc_result_totext(status));
+			}
+
+			if (strlen(argv[i]) >= sizeof(uplink->name)) {
+				log_fatal("%s: uplink name too long,"
+					  " it cannot exceed: %ld characters",
+					  argv[i], (long)(sizeof(uplink->name) - 1));
+			}
+
+			uplink->name[sizeof(uplink->name) - 1] = 0x00;
+			strncpy(uplink->name, argv[i],
+				sizeof(uplink->name) - 1);
+			interface_snorf(uplink, (INTERFACE_REQUESTED |
+						INTERFACE_STREAMS));
+
+			/* Turn on -a, in case they don't do so explicitly */
+			add_agent_options = 1;
+			add_rfc3527_suboption = 1;
+		} else if (!strcmp(argv[i], "-D")) {
+#ifdef DHCPv6
+			if (local_family_set && (local_family == AF_INET6)) {
+				usage(use_v4command, argv[i]);
+			}
+			local_family_set = 1;
+			local_family = AF_INET;
+#endif
+			drop_agent_mismatches = 1;
+#ifdef DHCPv6
+		} else if (!strcmp(argv[i], "-I")) {
+			if (local_family_set && (local_family == AF_INET)) {
+				usage(use_v6command, argv[i]);
+			}
+			local_family_set = 1;
+			local_family = AF_INET6;
+			use_if_id = ISC_TRUE;
+		} else if (!strcmp(argv[i], "-l")) {
+			if (local_family_set && (local_family == AF_INET)) {
+				usage(use_v6command, argv[i]);
+			}
+			local_family_set = 1;
+			local_family = AF_INET6;
+			if (downstreams != NULL)
+				use_if_id = ISC_TRUE;
+			if (++i == argc)
+				usage(use_noarg, argv[i-1]);
+			sl = parse_downstream(argv[i]);
+			sl->next = downstreams;
+			downstreams = sl;
+		} else if (!strcmp(argv[i], "-u")) {
+			if (local_family_set && (local_family == AF_INET)) {
+				usage(use_v6command, argv[i]);
+			}
+			local_family_set = 1;
+			local_family = AF_INET6;
+			if (++i == argc)
+				usage(use_noarg, argv[i-1]);
+			sl = parse_upstream(argv[i]);
+			sl->next = upstreams;
+			upstreams = sl;
+		} else if (!strcmp(argv[i], "-s")) {
+			if (local_family_set && (local_family == AF_INET)) {
+				usage(use_v6command, argv[i]);
+			}
+			local_family_set = 1;
+			local_family = AF_INET6;
+			if (++i == argc)
+				usage(use_noarg, argv[i-1]);
+			dhcrelay_sub_id = argv[i];
+#endif
+		} else if (!strcmp(argv[i], "-pf")) {
+			if (++i == argc)
+				usage(use_noarg, argv[i-1]);
+			path_dhcrelay_pid = argv[i];
+			no_dhcrelay_pid = ISC_TRUE;
+		} else if (!strcmp(argv[i], "--no-pid")) {
+			no_pid_file = ISC_TRUE;
+ 		} else if (argv[i][0] == '-') {
+			usage("Unknown command: %s", argv[i]);
+ 		} else {
+			struct hostent *he;
+			struct in_addr ia, *iap = NULL;
+
+#ifdef DHCPv6
+			if (local_family_set && (local_family == AF_INET6)) {
+				usage(use_v4command, argv[i]);
+			}
+			local_family_set = 1;
+			local_family = AF_INET;
+#endif
+			if (inet_aton(argv[i], &ia)) {
+				iap = &ia;
+			} else {
+				he = gethostbyname(argv[i]);
+				if (!he) {
+					log_error("%s: host unknown", argv[i]);
+				} else {
+					iap = ((struct in_addr *)
+					       he->h_addr_list[0]);
+				}
+			}
+
+			if (iap) {
+				sp = ((struct server_list *)
+				      dmalloc(sizeof *sp, MDL));
+				if (!sp)
+					log_fatal("no memory for server.\n");
+				sp->next = servers;
+				servers = sp;
+				memcpy(&sp->to.sin_addr, iap, sizeof *iap);
+			}
+ 		}
+	}
+
+#if defined(RELAY_PORT) && \
+    !defined (USE_BPF_RECEIVE) && !defined (USE_LPF_RECEIVE)
+	if (relay_port && (local_family == AF_INET))
+		usage(bpf_sock_support, "-rp");
+#endif
+
+	/*
+	 * If the user didn't specify a pid file directly
+	 * find one from environment variables or defaults
+	 */
+	if (no_dhcrelay_pid == ISC_FALSE) {
+		if (local_family == AF_INET) {
+			path_dhcrelay_pid = getenv("PATH_DHCRELAY_PID");
+			if (path_dhcrelay_pid == NULL)
+				path_dhcrelay_pid = _PATH_DHCRELAY_PID;
+		}
+#ifdef DHCPv6
+		else {
+			path_dhcrelay_pid = getenv("PATH_DHCRELAY6_PID");
+			if (path_dhcrelay_pid == NULL)
+				path_dhcrelay_pid = _PATH_DHCRELAY6_PID;
+		}
+#endif
+	}
+
+	if (!quiet) {
+		log_info("%s %s", message, PACKAGE_VERSION);
+		log_info(copyright);
+		log_info(arr);
+		log_info(url);
+	} else
+		log_perror = 0;
+
+	/* Set default port */
+	if (local_family == AF_INET) {
+ 		service_local = "bootps";
+ 		service_remote = "bootpc";
+		port_local = htons(67);
+ 		port_remote = htons(68);
+	}
+#ifdef DHCPv6
+	else {
+		service_local = "dhcpv6-server";
+		service_remote = "dhcpv6-client";
+		port_local = htons(547);
+		port_remote = htons(546);
+	}
+#endif
+
+	if (!local_port) {
+		ent = getservbyname(service_local, "udp");
+		if (ent)
+			local_port = ent->s_port;
+		else
+			local_port = port_local;
+
+		ent = getservbyname(service_remote, "udp");
+		if (ent)
+			remote_port = ent->s_port;
+		else
+			remote_port = port_remote;
+
+		endservent();
+	}
+
+	if (local_family == AF_INET) {
+		/* We need at least one server */
+		if (servers == NULL) {
+			log_fatal("No servers specified.");
+		}
+
+
+		/* Set up the server sockaddrs. */
+		for (sp = servers; sp; sp = sp->next) {
+			sp->to.sin_port = local_port;
+			sp->to.sin_family = AF_INET;
+#ifdef HAVE_SA_LEN
+			sp->to.sin_len = sizeof sp->to;
+#endif
+		}
+	}
+#ifdef DHCPv6
+	else {
+		unsigned code;
+
+		/* We need at least one upstream and one downstream interface */
+		if (upstreams == NULL || downstreams == NULL) {
+			log_info("Must specify at least one lower "
+				 "and one upper interface.\n");
+			usage(NULL, NULL);
+		}
+
+		/* Set up the initial dhcp option universe. */
+		initialize_common_option_spaces();
+
+		/* Check requested options. */
+		code = D6O_RELAY_MSG;
+		if (!option_code_hash_lookup(&requested_opts[0],
+					     dhcpv6_universe.code_hash,
+					     &code, 0, MDL))
+			log_fatal("Unable to find the RELAY_MSG "
+				  "option definition.");
+		code = D6O_INTERFACE_ID;
+		if (!option_code_hash_lookup(&requested_opts[1],
+					     dhcpv6_universe.code_hash,
+					     &code, 0, MDL))
+			log_fatal("Unable to find the INTERFACE_ID "
+				  "option definition.");
+	}
+#endif
+
+	/* Get the current time... */
+	gettimeofday(&cur_tv, NULL);
+
+	/* Discover all the network interfaces. */
+	discover_interfaces(DISCOVER_RELAY);
+
+#ifdef DHCPv6
+	if (local_family == AF_INET6)
+		setup_streams();
+#endif
+
+	/* Become a daemon... */
+	if (!no_daemon) {
+		char buf = 0;
+		FILE *pf;
+		int pfdesc;
+
+		log_perror = 0;
+
+		/* Signal parent we started successfully. */
+		if (dfd[0] != -1 && dfd[1] != -1) {
+			if (write(dfd[1], &buf, 1) != 1)
+				log_fatal("write to parent: %m");
+			(void) close(dfd[1]);
+			dfd[0] = dfd[1] = -1;
+		}
+
+		/* Create the pid file. */
+		if (no_pid_file == ISC_FALSE) {
+			pfdesc = open(path_dhcrelay_pid,
+				      O_CREAT | O_TRUNC | O_WRONLY, 0644);
+
+			if (pfdesc < 0) {
+				log_error("Can't create %s: %m",
+					  path_dhcrelay_pid);
+			} else {
+				pf = fdopen(pfdesc, "w");
+				if (!pf)
+					log_error("Can't fdopen %s: %m",
+						  path_dhcrelay_pid);
+				else {
+					fprintf(pf, "%ld\n",(long)getpid());
+					fclose(pf);
+				}
+			}
+		}
+
+		(void) close(0);
+		(void) close(1);
+		(void) close(2);
+		(void) setsid();
+
+		IGNORE_RET (chdir("/"));
+	}
+
+	/* Set up the packet handler... */
+	if (local_family == AF_INET)
+		bootp_packet_handler = do_relay4;
+#ifdef DHCPv6
+	else
+		dhcpv6_packet_handler = do_packet6;
+#endif
+
+#if defined(ENABLE_GENTLE_SHUTDOWN)
+	/* no signal handlers until we deal with the side effects */
+        /* install signal handlers */
+	signal(SIGINT, dhcp_signal_handler);   /* control-c */
+	signal(SIGTERM, dhcp_signal_handler);  /* kill */
+#endif
+
+	/* Start dispatching packets and timeouts... */
+	dispatch();
+
+	/* In fact dispatch() never returns. */
+	return (0);
+}
+
+static void
+do_relay4(struct interface_info *ip, struct dhcp_packet *packet,
+	  unsigned int length, unsigned int from_port, struct iaddr from,
+	  struct hardware *hfrom) {
+	struct server_list *sp;
+	struct sockaddr_in to;
+	struct interface_info *out;
+	struct hardware hto, *htop;
+
+	if (packet->hlen > sizeof packet->chaddr) {
+		log_info("Discarding packet with invalid hlen, received on "
+			 "%s interface.", ip->name);
+		return;
+	}
+	if (ip->address_count < 1 || ip->addresses == NULL) {
+		log_info("Discarding packet received on %s interface that "
+			 "has no IPv4 address assigned.", ip->name);
+		return;
+	}
+
+	/* Find the interface that corresponds to the giaddr
+	   in the packet. */
+	if (packet->giaddr.s_addr) {
+		for (out = interfaces; out; out = out->next) {
+			int i;
+
+			for (i = 0 ; i < out->address_count ; i++ ) {
+				if (out->addresses[i].s_addr ==
+				    packet->giaddr.s_addr) {
+					i = -1;
+					break;
+				}
+			}
+
+			if (i == -1)
+				break;
+		}
+	} else {
+		out = NULL;
+	}
+
+	/* If it's a bootreply, forward it to the client. */
+	if (packet->op == BOOTREPLY) {
+		if (!(ip->flags & INTERFACE_UPSTREAM)) {
+			log_debug("Dropping reply received on %s", ip->name);
+			return;
+		}
+
+		if (!(packet->flags & htons(BOOTP_BROADCAST)) &&
+			can_unicast_without_arp(out)) {
+			to.sin_addr = packet->yiaddr;
+			to.sin_port = remote_port;
+
+			/* and hardware address is not broadcast */
+			htop = &hto;
+		} else {
+			to.sin_addr.s_addr = htonl(INADDR_BROADCAST);
+			to.sin_port = remote_port;
+
+			/* hardware address is broadcast */
+			htop = NULL;
+		}
+		to.sin_family = AF_INET;
+#ifdef HAVE_SA_LEN
+		to.sin_len = sizeof to;
+#endif
+
+		memcpy(&hto.hbuf[1], packet->chaddr, packet->hlen);
+		hto.hbuf[0] = packet->htype;
+		hto.hlen = packet->hlen + 1;
+
+		/* Wipe out the agent relay options and, if possible, figure
+		   out which interface to use based on the contents of the
+		   option that we put on the request to which the server is
+		   replying. */
+		if (!(length =
+		      strip_relay_agent_options(ip, &out, packet, length)))
+			return;
+
+		if (!out) {
+			log_error("Packet to bogus giaddr %s.\n",
+			      inet_ntoa(packet->giaddr));
+			++bogus_giaddr_drops;
+			return;
+		}
+
+		if (send_packet(out, NULL, packet, length, out->addresses[0],
+				&to, htop) < 0) {
+			++server_packet_errors;
+		} else {
+			log_debug("Forwarded BOOTREPLY for %s to %s",
+			       print_hw_addr(packet->htype, packet->hlen,
+					      packet->chaddr),
+			       inet_ntoa(to.sin_addr));
+
+			++server_packets_relayed;
+		}
+		return;
+	}
+
+	/* If giaddr matches one of our addresses, ignore the packet -
+	   we just sent it. */
+	if (out)
+		return;
+
+	if (!(ip->flags & INTERFACE_DOWNSTREAM)) {
+		log_debug("Dropping request received on %s", ip->name);
+		return;
+	}
+
+	/* Add relay agent options if indicated.   If something goes wrong,
+	 * drop the packet.  Note this may set packet->giaddr if RFC3527
+	 * is enabled. */
+	if (!(length = add_relay_agent_options(ip, packet, length,
+					       ip->addresses[0])))
+		return;
+
+	/* If giaddr is not already set, Set it so the server can
+	   figure out what net it's from and so that we can later
+	   forward the response to the correct net.    If it's already
+	   set, the response will be sent directly to the relay agent
+	   that set giaddr, so we won't see it. */
+	if (!packet->giaddr.s_addr)
+		packet->giaddr = ip->addresses[0];
+	if (packet->hops < max_hop_count)
+		packet->hops = packet->hops + 1;
+	else
+		return;
+
+	/* Otherwise, it's a BOOTREQUEST, so forward it to all the
+	   servers. */
+	for (sp = servers; sp; sp = sp->next) {
+		if (send_packet((fallback_interface
+				 ? fallback_interface : interfaces),
+				 NULL, packet, length, ip->addresses[0],
+				 &sp->to, NULL) < 0) {
+			++client_packet_errors;
+		} else {
+			log_debug("Forwarded BOOTREQUEST for %s to %s",
+			       print_hw_addr(packet->htype, packet->hlen,
+					      packet->chaddr),
+			       inet_ntoa(sp->to.sin_addr));
+			++client_packets_relayed;
+		}
+	}
+
+}
+
+#endif /* UNIT_TEST */
+
+/* Strip any Relay Agent Information options from the DHCP packet
+   option buffer.   If there is a circuit ID suboption, look up the
+   outgoing interface based upon it. */
+
+int
+strip_relay_agent_options(struct interface_info *in,
+			  struct interface_info **out,
+			  struct dhcp_packet *packet,
+			  unsigned length) {
+	int is_dhcp = 0;
+	u_int8_t *op, *nextop, *sp, *max;
+	int good_agent_option = 0;
+	int status;
+
+	/* If we're not adding agent options to packets, we're not taking
+	   them out either. */
+	if (!add_agent_options)
+		return (length);
+
+	/* If there's no cookie, it's a bootp packet, so we should just
+	   forward it unchanged. */
+	if (memcmp(packet->options, DHCP_OPTIONS_COOKIE, 4))
+		return (length);
+
+	max = ((u_int8_t *)packet) + length;
+	sp = op = &packet->options[4];
+
+	while (op < max) {
+		switch(*op) {
+			/* Skip padding... */
+		      case DHO_PAD:
+			if (sp != op)
+				*sp = *op;
+			++op;
+			++sp;
+			continue;
+
+			/* If we see a message type, it's a DHCP packet. */
+		      case DHO_DHCP_MESSAGE_TYPE:
+			is_dhcp = 1;
+			goto skip;
+			break;
+
+			/* Quit immediately if we hit an End option. */
+		      case DHO_END:
+			if (sp != op)
+				*sp++ = *op++;
+			goto out;
+
+		      case DHO_DHCP_AGENT_OPTIONS:
+			/* We shouldn't see a relay agent option in a
+			   packet before we've seen the DHCP packet type,
+			   but if we do, we have to leave it alone. */
+			if (!is_dhcp)
+				goto skip;
+
+			/* Do not process an agent option if it exceeds the
+			 * buffer.  Fail this packet.
+			 */
+			nextop = op + op[1] + 2;
+			if (nextop > max)
+				return (0);
+
+			status = find_interface_by_agent_option(packet,
+								out, op + 2,
+								op[1]);
+			if (status == -1 && drop_agent_mismatches)
+				return (0);
+			if (status)
+				good_agent_option = 1;
+			op = nextop;
+			break;
+
+		      skip:
+			/* Skip over other options. */
+		      default:
+			/* Fail if processing this option will exceed the
+			 * buffer(op[1] is malformed).
+			 */
+			nextop = op + op[1] + 2;
+			if (nextop > max)
+				return (0);
+
+			if (sp != op) {
+				size_t mlen = op[1] + 2;
+				memmove(sp, op, mlen);
+				sp += mlen;
+				if (sp > max) {
+					return (0);
+				}
+
+				op = nextop;
+			} else
+				op = sp = nextop;
+
+			break;
+		}
+	}
+      out:
+
+	/* If it's not a DHCP packet, we're not supposed to touch it. */
+	if (!is_dhcp)
+		return (length);
+
+	/* If none of the agent options we found matched, or if we didn't
+	   find any agent options, count this packet as not having any
+	   matching agent options, and if we're relying on agent options
+	   to determine the outgoing interface, drop the packet. */
+
+	if (!good_agent_option) {
+		++missing_agent_option;
+		if (drop_agent_mismatches)
+			return (0);
+	}
+
+	/* Adjust the length... */
+	if (sp != op) {
+		length = sp -((u_int8_t *)packet);
+
+		/* Make sure the packet isn't short(this is unlikely,
+		   but WTH) */
+		if (length < BOOTP_MIN_LEN) {
+			memset(sp, DHO_PAD, BOOTP_MIN_LEN - length);
+			length = BOOTP_MIN_LEN;
+		}
+	}
+	return (length);
+}
+
+
+/* Find an interface that matches the circuit ID specified in the
+   Relay Agent Information option.   If one is found, store it through
+   the pointer given; otherwise, leave the existing pointer alone.
+
+   We actually deviate somewhat from the current specification here:
+   if the option buffer is corrupt, we suggest that the caller not
+   respond to this packet.  If the circuit ID doesn't match any known
+   interface, we suggest that the caller to drop the packet.  Only if
+   we find a circuit ID that matches an existing interface do we tell
+   the caller to go ahead and process the packet. */
+
+int
+find_interface_by_agent_option(struct dhcp_packet *packet,
+			       struct interface_info **out,
+			       u_int8_t *buf, int len) {
+	int i = 0;
+	u_int8_t *circuit_id = 0;
+	unsigned circuit_id_len = 0;
+	struct interface_info *ip;
+
+	while (i < len) {
+		/* If the next agent option overflows the end of the
+		   packet, the agent option buffer is corrupt. */
+		if (i + 1 == len ||
+		    i + buf[i + 1] + 2 > len) {
+			++corrupt_agent_options;
+			return (-1);
+		}
+		switch(buf[i]) {
+			/* Remember where the circuit ID is... */
+		      case RAI_CIRCUIT_ID:
+			circuit_id = &buf[i + 2];
+			circuit_id_len = buf[i + 1];
+			i += circuit_id_len + 2;
+			continue;
+
+		      default:
+			i += buf[i + 1] + 2;
+			break;
+		}
+	}
+
+	/* If there's no circuit ID, it's not really ours, tell the caller
+	   it's no good. */
+	if (!circuit_id) {
+		++missing_circuit_id;
+		return (-1);
+	}
+
+	/* Scan the interface list looking for an interface whose
+	   name matches the one specified in circuit_id. */
+
+	for (ip = interfaces; ip; ip = ip->next) {
+		if (ip->circuit_id &&
+		    ip->circuit_id_len == circuit_id_len &&
+		    !memcmp(ip->circuit_id, circuit_id, circuit_id_len))
+			break;
+	}
+
+	/* If we got a match, use it. */
+	if (ip) {
+		*out = ip;
+		return (1);
+	}
+
+	/* If we didn't get a match, the circuit ID was bogus. */
+	++bad_circuit_id;
+	return (-1);
+}
+
+/*
+ * Examine a packet to see if it's a candidate to have a Relay
+ * Agent Information option tacked onto its tail.   If it is, tack
+ * the option on.
+ */
+int
+add_relay_agent_options(struct interface_info *ip, struct dhcp_packet *packet,
+			unsigned length, struct in_addr giaddr) {
+	int is_dhcp = 0, mms;
+	unsigned optlen;
+	u_int8_t *op, *nextop, *sp, *max, *end_pad = NULL;
+	int adding_link_select;
+
+	/* If we're not adding agent options to packets, we can skip
+	   this. */
+	if (!add_agent_options)
+		return (length);
+
+	/* If there's no cookie, it's a bootp packet, so we should just
+	   forward it unchanged. */
+	if (memcmp(packet->options, DHCP_OPTIONS_COOKIE, 4))
+		return (length);
+
+	max = ((u_int8_t *)packet) + dhcp_max_agent_option_packet_length;
+
+	/* Add link selection suboption if enabled and we're the first relay */
+	adding_link_select = (add_rfc3527_suboption
+			      && (packet->giaddr.s_addr == 0));
+
+	/* Commence processing after the cookie. */
+	sp = op = &packet->options[4];
+
+	while (op < max) {
+		switch(*op) {
+			/* Skip padding... */
+		      case DHO_PAD:
+			/* Remember the first pad byte so we can commandeer
+			 * padded space.
+			 *
+			 * XXX: Is this really a good idea?  Sure, we can
+			 * seemingly reduce the packet while we're looking,
+			 * but if the packet was signed by the client then
+			 * this padding is part of the checksum(RFC3118),
+			 * and its nonpresence would break authentication.
+			 */
+			if (end_pad == NULL)
+				end_pad = sp;
+
+			if (sp != op)
+				*sp++ = *op++;
+			else
+				sp = ++op;
+
+			continue;
+
+			/* If we see a message type, it's a DHCP packet. */
+		      case DHO_DHCP_MESSAGE_TYPE:
+			is_dhcp = 1;
+			goto skip;
+
+			/*
+			 * If there's a maximum message size option, we
+			 * should pay attention to it
+			 */
+		      case DHO_DHCP_MAX_MESSAGE_SIZE:
+			mms = ntohs(*(op + 2));
+			if (mms < dhcp_max_agent_option_packet_length &&
+			    mms >= DHCP_MTU_MIN)
+				max = ((u_int8_t *)packet) + mms;
+			goto skip;
+
+			/* Quit immediately if we hit an End option. */
+		      case DHO_END:
+			goto out;
+
+		      case DHO_DHCP_AGENT_OPTIONS:
+			/* We shouldn't see a relay agent option in a
+			   packet before we've seen the DHCP packet type,
+			   but if we do, we have to leave it alone. */
+			if (!is_dhcp)
+				goto skip;
+
+			end_pad = NULL;
+
+			/* There's already a Relay Agent Information option
+			   in this packet.   How embarrassing.   Decide what
+			   to do based on the mode the user specified. */
+
+			switch(agent_relay_mode) {
+			      case forward_and_append:
+				goto skip;
+			      case forward_untouched:
+				return (length);
+			      case discard:
+				return (0);
+			      case forward_and_replace:
+			      default:
+				break;
+			}
+
+			/* Skip over the agent option and start copying
+			   if we aren't copying already. */
+			op += op[1] + 2;
+			break;
+
+		      skip:
+			/* Skip over other options. */
+		      default:
+			/* Fail if processing this option will exceed the
+			 * buffer(op[1] is malformed).
+			 */
+			nextop = op + op[1] + 2;
+			if (nextop > max)
+				return (0);
+
+			end_pad = NULL;
+
+			if (sp != op) {
+				size_t mlen = op[1] + 2;
+				memmove(sp, op, mlen);
+				sp += mlen;
+				if (sp > max) {
+					return (0);
+				}
+
+				op = nextop;
+			} else
+				op = sp = nextop;
+
+			break;
+		}
+	}
+      out:
+
+	/* If it's not a DHCP packet, we're not supposed to touch it. */
+	if (!is_dhcp)
+		return (length);
+
+	/* If the packet was padded out, we can store the agent option
+	   at the beginning of the padding. */
+
+	if (end_pad != NULL)
+		sp = end_pad;
+
+#if 0
+	/* Remember where the end of the packet was after parsing
+	   it. */
+	op = sp;
+#endif
+
+	/* Sanity check.  Had better not ever happen. */
+	if ((ip->circuit_id_len > 255) ||(ip->circuit_id_len < 1))
+		log_fatal("Circuit ID length %d out of range [1-255] on "
+			  "%s\n", ip->circuit_id_len, ip->name);
+	optlen = ip->circuit_id_len + 2;            /* RAI_CIRCUIT_ID + len */
+
+	if (ip->remote_id) {
+		if (ip->remote_id_len > 255 || ip->remote_id_len < 1)
+			log_fatal("Remote ID length %d out of range [1-255] "
+				  "on %s\n", ip->remote_id_len, ip->name);
+		optlen += ip->remote_id_len + 2;    /* RAI_REMOTE_ID + len */
+	}
+
+	if (adding_link_select) {
+		optlen += 6;
+	}
+
+#ifdef RELAY_PORT
+	if (relay_port) {
+		optlen += 2;
+	}
+#endif
+
+	/* We do not support relay option fragmenting(multiple options to
+	 * support an option data exceeding 255 bytes).
+	 */
+	if ((optlen < 3) ||(optlen > 255))
+		log_fatal("Total agent option length(%u) out of range "
+			   "[3 - 255] on %s\n", optlen, ip->name);
+
+	/*
+	 * Is there room for the option, its code+len, and DHO_END?
+	 * If not, forward without adding the option.
+	 */
+	if (max - sp >= optlen + 3) {
+		log_debug("Adding %d-byte relay agent option", optlen + 3);
+
+		/* Okay, cons up *our* Relay Agent Information option. */
+		*sp++ = DHO_DHCP_AGENT_OPTIONS;
+		*sp++ = optlen;
+
+		/* Copy in the circuit id... */
+		*sp++ = RAI_CIRCUIT_ID;
+		*sp++ = ip->circuit_id_len;
+		memcpy(sp, ip->circuit_id, ip->circuit_id_len);
+		sp += ip->circuit_id_len;
+
+		/* Copy in remote ID... */
+		if (ip->remote_id) {
+			*sp++ = RAI_REMOTE_ID;
+			*sp++ = ip->remote_id_len;
+			memcpy(sp, ip->remote_id, ip->remote_id_len);
+			sp += ip->remote_id_len;
+		}
+
+		/* RFC3527: Use the inbound packet's interface address in
+		 * the link selection suboption and set the outbound giaddr
+		 * to the uplink address. */
+		if (adding_link_select) {
+			*sp++ = RAI_LINK_SELECT;
+			*sp++ = 4u;
+			memcpy(sp, &giaddr.s_addr, 4);
+			sp += 4;
+			packet->giaddr = uplink->addresses[0];
+			log_debug ("Adding link selection suboption"
+				   " with addr: %s", inet_ntoa(giaddr));
+		}
+
+#ifdef RELAY_PORT
+		/* draft-ietf-dhc-relay-port-10.txt section 5.1 */
+		if (relay_port) {
+			*sp++ = RAI_RELAY_PORT;
+			*sp++ = 0u;
+		}
+#endif
+	} else {
+		++agent_option_errors;
+		log_error("No room in packet (used %d of %d) "
+			  "for %d-byte relay agent option: omitted",
+			   (int) (sp - ((u_int8_t *) packet)),
+			   (int) (max - ((u_int8_t *) packet)),
+			   optlen + 3);
+	}
+
+	/*
+	 * Deposit an END option unless the packet is full (shouldn't
+	 * be possible).
+	 */
+	if (sp < max)
+		*sp++ = DHO_END;
+
+	/* Recalculate total packet length. */
+	length = sp -((u_int8_t *)packet);
+
+	/* Make sure the packet isn't short(this is unlikely, but WTH) */
+	if (length < BOOTP_MIN_LEN) {
+		memset(sp, DHO_PAD, BOOTP_MIN_LEN - length);
+		return (BOOTP_MIN_LEN);
+	}
+
+	return (length);
+}
+
+#ifdef DHCPv6
+#ifndef UNIT_TEST
+/*
+ * Parse a downstream argument: [address%]interface[#index].
+ */
+static struct stream_list *
+parse_downstream(char *arg) {
+	struct stream_list *dp, *up;
+	struct interface_info *ifp = NULL;
+	char *ifname, *addr, *iid;
+	isc_result_t status;
+
+	if (!supports_multiple_interfaces(ifp) &&
+	    (downstreams != NULL))
+		log_fatal("No support for multiple interfaces.");
+
+	/* Decode the argument. */
+	ifname = strchr(arg, '%');
+	if (ifname == NULL) {
+		ifname = arg;
+		addr = NULL;
+	} else {
+		*ifname++ = '\0';
+		addr = arg;
+	}
+	iid = strchr(ifname, '#');
+	if (iid != NULL) {
+		*iid++ = '\0';
+	}
+	if (strlen(ifname) >= sizeof(ifp->name)) {
+		usage("Interface name '%s' too long", ifname);
+	}
+
+	/* Don't declare twice. */
+	for (dp = downstreams; dp; dp = dp->next) {
+		if (strcmp(ifname, dp->ifp->name) == 0)
+			log_fatal("Down interface '%s' declared twice.",
+				  ifname);
+	}
+
+	/* Share with up side? */
+	for (up = upstreams; up; up = up->next) {
+		if (strcmp(ifname, up->ifp->name) == 0) {
+			log_info("parse_downstream: Interface '%s' is "
+				 "both down and up.", ifname);
+			ifp = up->ifp;
+			break;
+		}
+	}
+
+	/* New interface. */
+	if (ifp == NULL) {
+		status = interface_allocate(&ifp, MDL);
+		if (status != ISC_R_SUCCESS)
+			log_fatal("%s: interface_allocate: %s",
+				  arg, isc_result_totext(status));
+		strcpy(ifp->name, ifname);
+		if (interfaces) {
+			interface_reference(&ifp->next, interfaces, MDL);
+			interface_dereference(&interfaces, MDL);
+		}
+		interface_reference(&interfaces, ifp, MDL);
+	}
+	ifp->flags |= INTERFACE_REQUESTED | INTERFACE_DOWNSTREAM;
+
+	/* New downstream. */
+	dp = (struct stream_list *) dmalloc(sizeof(*dp), MDL);
+	if (!dp)
+		log_fatal("No memory for downstream.");
+	dp->ifp = ifp;
+	if (iid != NULL) {
+		dp->id = atoi(iid);
+	} else {
+		dp->id = -1;
+	}
+	/* !addr case handled by setup. */
+	if (addr && (inet_pton(AF_INET6, addr, &dp->link.sin6_addr) <= 0))
+		log_fatal("Bad link address '%s'", addr);
+
+	return dp;
+}
+
+/*
+ * Parse an upstream argument: [address]%interface.
+ */
+static struct stream_list *
+parse_upstream(char *arg) {
+	struct stream_list *up, *dp;
+	struct interface_info *ifp = NULL;
+	char *ifname, *addr;
+	isc_result_t status;
+
+	/* Decode the argument. */
+	ifname = strchr(arg, '%');
+	if (ifname == NULL) {
+		ifname = arg;
+		addr = All_DHCP_Servers;
+	} else {
+		*ifname++ = '\0';
+		addr = arg;
+	}
+	if (strlen(ifname) >= sizeof(ifp->name)) {
+		log_fatal("Interface name '%s' too long", ifname);
+	}
+
+	/* Shared up interface? */
+	for (up = upstreams; up; up = up->next) {
+		if (strcmp(ifname, up->ifp->name) == 0) {
+			ifp = up->ifp;
+			break;
+		}
+	}
+	for (dp = downstreams; dp; dp = dp->next) {
+		if (strcmp(ifname, dp->ifp->name) == 0) {
+			log_info("parse_upstream: Interface '%s' is "
+				 "both down and up.", ifname);
+			ifp = dp->ifp;
+			break;
+		}
+	}
+
+	/* New interface. */
+	if (ifp == NULL) {
+		status = interface_allocate(&ifp, MDL);
+		if (status != ISC_R_SUCCESS)
+			log_fatal("%s: interface_allocate: %s",
+				  arg, isc_result_totext(status));
+		strcpy(ifp->name, ifname);
+		if (interfaces) {
+			interface_reference(&ifp->next, interfaces, MDL);
+			interface_dereference(&interfaces, MDL);
+		}
+		interface_reference(&interfaces, ifp, MDL);
+	}
+	ifp->flags |= INTERFACE_REQUESTED | INTERFACE_UPSTREAM;
+
+	/* New upstream. */
+	up = (struct stream_list *) dmalloc(sizeof(*up), MDL);
+	if (up == NULL)
+		log_fatal("No memory for upstream.");
+
+	up->ifp = ifp;
+
+	if (inet_pton(AF_INET6, addr, &up->link.sin6_addr) <= 0)
+		log_fatal("Bad address %s", addr);
+
+	return up;
+}
+
+/*
+ * Setup downstream interfaces.
+ */
+static void
+setup_streams(void) {
+	struct stream_list *dp, *up;
+	int i;
+	isc_boolean_t link_is_set;
+
+	for (dp = downstreams; dp; dp = dp->next) {
+		/* Check interface */
+		if (dp->ifp->v6address_count == 0)
+			log_fatal("Interface '%s' has no IPv6 addresses.",
+				  dp->ifp->name);
+
+		/* Check/set link. */
+		if (IN6_IS_ADDR_UNSPECIFIED(&dp->link.sin6_addr))
+			link_is_set = ISC_FALSE;
+		else
+			link_is_set = ISC_TRUE;
+		for (i = 0; i < dp->ifp->v6address_count; i++) {
+			if (IN6_IS_ADDR_LINKLOCAL(&dp->ifp->v6addresses[i]))
+				continue;
+			if (!link_is_set)
+				break;
+			if (!memcmp(&dp->ifp->v6addresses[i],
+				    &dp->link.sin6_addr,
+				    sizeof(dp->link.sin6_addr)))
+				break;
+		}
+		if (i == dp->ifp->v6address_count)
+			log_fatal("Interface %s does not have global IPv6 "
+				  "address assigned.", dp->ifp->name);
+		if (!link_is_set)
+			memcpy(&dp->link.sin6_addr,
+			       &dp->ifp->v6addresses[i],
+			       sizeof(dp->link.sin6_addr));
+
+		/* Set interface-id. */
+		if (dp->id == -1)
+			dp->id = dp->ifp->index;
+	}
+
+	for (up = upstreams; up; up = up->next) {
+		up->link.sin6_port = local_port;
+		up->link.sin6_family = AF_INET6;
+#ifdef HAVE_SA_LEN
+		up->link.sin6_len = sizeof(up->link);
+#endif
+
+		if (up->ifp->v6address_count == 0)
+			log_fatal("Interface '%s' has no IPv6 addresses.",
+				  up->ifp->name);
+
+		/* RFC 3315 Sec 20 - "If the relay agent relays messages to
+		 * the All_DHCP_Servers address or other multicast addresses,
+		 * it sets the Hop Limit field to 32." */
+		if (IN6_IS_ADDR_MULTICAST(&up->link.sin6_addr)) {
+			set_multicast_hop_limit(up->ifp, HOP_COUNT_LIMIT);
+		}
+	}
+}
+
+/*
+ * Add DHCPv6 agent options here.
+ */
+static const int required_forw_opts[] = {
+	D6O_INTERFACE_ID,
+	D6O_SUBSCRIBER_ID,
+#if defined(RELAY_PORT)
+	D6O_RELAY_SOURCE_PORT,
+#endif
+	D6O_RELAY_MSG,
+	0
+};
+
+/*
+ * Process a packet upwards, i.e., from client to server.
+ */
+static void
+process_up6(struct packet *packet, struct stream_list *dp) {
+	char forw_data[65535];
+	unsigned cursor;
+	struct dhcpv6_relay_packet *relay;
+	struct option_state *opts;
+	struct stream_list *up;
+	u_int16_t relay_client_port = 0;
+
+	/* Check if the message should be relayed to the server. */
+	switch (packet->dhcpv6_msg_type) {
+	      case DHCPV6_SOLICIT:
+	      case DHCPV6_REQUEST:
+	      case DHCPV6_CONFIRM:
+	      case DHCPV6_RENEW:
+	      case DHCPV6_REBIND:
+	      case DHCPV6_RELEASE:
+	      case DHCPV6_DECLINE:
+	      case DHCPV6_INFORMATION_REQUEST:
+	      case DHCPV6_RELAY_FORW:
+	      case DHCPV6_LEASEQUERY:
+	      case DHCPV6_DHCPV4_QUERY:
+		log_info("Relaying %s from %s port %d going up.",
+			 dhcpv6_type_names[packet->dhcpv6_msg_type],
+			 piaddr(packet->client_addr),
+			 ntohs(packet->client_port));
+		break;
+
+	      case DHCPV6_ADVERTISE:
+	      case DHCPV6_REPLY:
+	      case DHCPV6_RECONFIGURE:
+	      case DHCPV6_RELAY_REPL:
+	      case DHCPV6_LEASEQUERY_REPLY:
+	      case DHCPV6_DHCPV4_RESPONSE:
+		log_info("Discarding %s from %s port %d going up.",
+			 dhcpv6_type_names[packet->dhcpv6_msg_type],
+			 piaddr(packet->client_addr),
+			 ntohs(packet->client_port));
+		return;
+
+	      default:
+		log_info("Unknown %d type from %s port %d going up.",
+			 packet->dhcpv6_msg_type,
+			 piaddr(packet->client_addr),
+			 ntohs(packet->client_port));
+		return;
+	}
+
+	/* Build the relay-forward header. */
+	relay = (struct dhcpv6_relay_packet *) forw_data;
+	cursor = offsetof(struct dhcpv6_relay_packet, options);
+	relay->msg_type = DHCPV6_RELAY_FORW;
+	if (packet->dhcpv6_msg_type == DHCPV6_RELAY_FORW) {
+		if (packet->dhcpv6_hop_count >= max_hop_count) {
+			log_info("Hop count exceeded,");
+			return;
+		}
+		relay->hop_count = packet->dhcpv6_hop_count + 1;
+		if (dp) {
+			memcpy(&relay->link_address, &dp->link.sin6_addr, 16);
+		} else {
+			/* On smart relay add: && !global. */
+			if (!use_if_id && downstreams->next) {
+				log_info("Shan't get back the interface.");
+				return;
+			}
+			memset(&relay->link_address, 0, 16);
+		}
+
+		if (packet->client_port != htons(547)) {
+			relay_client_port = packet->client_port;
+		}
+	} else {
+		relay->hop_count = 0;
+		if (!dp)
+			return;
+		memcpy(&relay->link_address, &dp->link.sin6_addr, 16);
+	}
+	memcpy(&relay->peer_address, packet->client_addr.iabuf, 16);
+
+	/* Get an option state. */
+	opts = NULL;
+	if (!option_state_allocate(&opts, MDL)) {
+		log_fatal("No memory for upwards options.");
+	}
+
+	/* Add an interface-id (if used). */
+	if (use_if_id) {
+		int if_id;
+
+		if (dp) {
+			if_id = dp->id;
+		} else if (!downstreams->next) {
+			if_id = downstreams->id;
+		} else {
+			log_info("Don't know the interface.");
+			option_state_dereference(&opts, MDL);
+			return;
+		}
+
+		if (!save_option_buffer(&dhcpv6_universe, opts,
+					NULL, (unsigned char *) &if_id,
+					sizeof(int),
+					D6O_INTERFACE_ID, 0)) {
+			log_error("Can't save interface-id.");
+			option_state_dereference(&opts, MDL);
+			return;
+		}
+	}
+
+	/* Add a subscriber-id if desired. */
+	/* This is for testing rather than general use */
+	if (dhcrelay_sub_id != NULL) {
+		if (!save_option_buffer(&dhcpv6_universe, opts, NULL,
+					(unsigned char *) dhcrelay_sub_id,
+					strlen(dhcrelay_sub_id),
+					D6O_SUBSCRIBER_ID, 0)) {
+			log_error("Can't save subsriber-id.");
+			option_state_dereference(&opts, MDL);
+			return;
+		}
+	}
+
+
+#if defined(RELAY_PORT)
+	/*
+	 * If we use a non-547 UDP source port or if we have received
+	 * from a downstream relay agent uses a non-547 port, we need
+	 * to include the RELAY-SOURCE-PORT option. The "Downstream
+	 * UDP Port" field value in the option allow us to send
+	 * relay-reply message back to the downstream relay agent
+	 * with the correct UDP source port.
+        */
+	if (relay_port || relay_client_port) {
+		if (!save_option_buffer(&dhcpv6_universe, opts, NULL,
+					(unsigned char *) &relay_client_port,
+					sizeof(u_int16_t),
+					D6O_RELAY_SOURCE_PORT, 0)) {
+			log_error("Can't save relay-source-port.");
+			option_state_dereference(&opts, MDL);
+			return;
+		}
+	}
+#else
+	/* Avoid unused but set warning, */
+	(void)(relay_client_port);
+#endif
+
+	/* Add the relay-msg carrying the packet. */
+	if (!save_option_buffer(&dhcpv6_universe, opts,
+				NULL, (unsigned char *) packet->raw,
+				packet->packet_length,
+				D6O_RELAY_MSG, 0)) {
+		log_error("Can't save relay-msg.");
+		option_state_dereference(&opts, MDL);
+		return;
+	}
+
+	/* Finish the relay-forward message. */
+	cursor += store_options6(forw_data + cursor,
+				 sizeof(forw_data) - cursor,
+				 opts, packet,
+				 required_forw_opts, NULL);
+	option_state_dereference(&opts, MDL);
+
+	/* Send it to all upstreams. */
+	for (up = upstreams; up; up = up->next) {
+		send_packet6(up->ifp, (unsigned char *) forw_data,
+			     (size_t) cursor, &up->link);
+	}
+}
+
+/*
+ * Process a packet downwards, i.e., from server to client.
+ */
+static void
+process_down6(struct packet *packet) {
+	struct stream_list *dp;
+	struct option_cache *oc;
+	struct data_string relay_msg;
+	const struct dhcpv6_packet *msg;
+	struct data_string if_id;
+#if defined(RELAY_PORT)
+	struct data_string down_port;
+#endif
+	struct sockaddr_in6 to;
+	struct iaddr peer;
+
+	/* The packet must be a relay-reply message. */
+	if (packet->dhcpv6_msg_type != DHCPV6_RELAY_REPL) {
+		if (packet->dhcpv6_msg_type < dhcpv6_type_name_max)
+			log_info("Discarding %s from %s port %d going down.",
+				 dhcpv6_type_names[packet->dhcpv6_msg_type],
+				 piaddr(packet->client_addr),
+				 ntohs(packet->client_port));
+		else
+			log_info("Unknown %d type from %s port %d going down.",
+				 packet->dhcpv6_msg_type,
+				 piaddr(packet->client_addr),
+				 ntohs(packet->client_port));
+		return;
+	}
+
+	/* Inits. */
+	memset(&relay_msg, 0, sizeof(relay_msg));
+	memset(&if_id, 0, sizeof(if_id));
+#if defined(RELAY_PORT)
+	memset(&down_port, 0, sizeof(down_port));
+#endif
+	memset(&to, 0, sizeof(to));
+	to.sin6_family = AF_INET6;
+#ifdef HAVE_SA_LEN
+	to.sin6_len = sizeof(to);
+#endif
+	to.sin6_port = remote_port;
+	peer.len = 16;
+
+	/* Get the relay-msg option (carrying the message to relay). */
+	oc = lookup_option(&dhcpv6_universe, packet->options, D6O_RELAY_MSG);
+	if (oc == NULL) {
+		log_info("No relay-msg.");
+		return;
+	}
+	if (!evaluate_option_cache(&relay_msg, packet, NULL, NULL,
+				   packet->options, NULL,
+				   &global_scope, oc, MDL) ||
+	    (relay_msg.len < offsetof(struct dhcpv6_packet, options))) {
+		log_error("Can't evaluate relay-msg.");
+		goto cleanup;
+	}
+	msg = (const struct dhcpv6_packet *) relay_msg.data;
+
+	/* Get the interface-id (if exists) and the downstream. */
+	oc = lookup_option(&dhcpv6_universe, packet->options,
+			   D6O_INTERFACE_ID);
+	if (oc != NULL) {
+		int if_index;
+
+		if (!evaluate_option_cache(&if_id, packet, NULL, NULL,
+					   packet->options, NULL,
+					   &global_scope, oc, MDL) ||
+		    (if_id.len != sizeof(int))) {
+			log_info("Can't evaluate interface-id.");
+			goto cleanup;
+		}
+		memcpy(&if_index, if_id.data, sizeof(int));
+		for (dp = downstreams; dp; dp = dp->next) {
+			if (dp->id == if_index)
+				break;
+		}
+	} else {
+		if (use_if_id) {
+			/* Require an interface-id. */
+			log_info("No interface-id.");
+			goto cleanup;
+		}
+		for (dp = downstreams; dp; dp = dp->next) {
+			/* Get the first matching one. */
+			if (!memcmp(&dp->link.sin6_addr,
+				    &packet->dhcpv6_link_address,
+				    sizeof(struct in6_addr)))
+				break;
+		}
+	}
+	/* Why bother when there is no choice. */
+	if (!dp && downstreams && !downstreams->next)
+		dp = downstreams;
+	if (!dp) {
+		log_info("Can't find the down interface.");
+		goto cleanup;
+	}
+	memcpy(peer.iabuf, &packet->dhcpv6_peer_address, peer.len);
+	to.sin6_addr = packet->dhcpv6_peer_address;
+
+	/* Check if we should relay the carried message. */
+	switch (msg->msg_type) {
+		/* Relay-Reply of for another relay, not a client. */
+	      case DHCPV6_RELAY_REPL:
+		to.sin6_port = local_port;
+
+#if defined(RELAY_PORT)
+		oc = lookup_option(&dhcpv6_universe, packet->options,
+				   D6O_RELAY_SOURCE_PORT);
+		if (oc != NULL) {
+			u_int16_t down_relay_port;
+
+			memset(&down_port, 0, sizeof(down_port));
+			if (!evaluate_option_cache(&down_port, packet, NULL,
+						   NULL, packet->options, NULL,
+						   &global_scope, oc, MDL) ||
+			    (down_port.len != sizeof(u_int16_t))) {
+				log_info("Can't evaluate down "
+					 "relay-source-port.");
+				goto cleanup;
+			}
+			memcpy(&down_relay_port, down_port.data,
+			       sizeof(u_int16_t));
+			/*
+			 * If the down_relay_port value is non-zero,
+			 * that means our downstream relay agent uses
+			 * a non-547 UDP source port sending
+			 * relay-forw message to us. We need to use
+			 * the same UDP port sending reply back.
+			 */
+			if (down_relay_port) {
+				to.sin6_port = down_relay_port;
+			}
+		}
+#endif
+
+		/* Fall into: */
+
+	      case DHCPV6_ADVERTISE:
+	      case DHCPV6_REPLY:
+	      case DHCPV6_RECONFIGURE:
+	      case DHCPV6_RELAY_FORW:
+	      case DHCPV6_LEASEQUERY_REPLY:
+	      case DHCPV6_DHCPV4_RESPONSE:
+		log_info("Relaying %s to %s port %d down.",
+			 dhcpv6_type_names[msg->msg_type],
+			 piaddr(peer),
+			 ntohs(to.sin6_port));
+		break;
+
+	      case DHCPV6_SOLICIT:
+	      case DHCPV6_REQUEST:
+	      case DHCPV6_CONFIRM:
+	      case DHCPV6_RENEW:
+	      case DHCPV6_REBIND:
+	      case DHCPV6_RELEASE:
+	      case DHCPV6_DECLINE:
+	      case DHCPV6_INFORMATION_REQUEST:
+	      case DHCPV6_LEASEQUERY:
+	      case DHCPV6_DHCPV4_QUERY:
+		log_info("Discarding %s to %s port %d down.",
+			 dhcpv6_type_names[msg->msg_type],
+			 piaddr(peer),
+			 ntohs(to.sin6_port));
+		goto cleanup;
+
+	      default:
+		log_info("Unknown %d type to %s port %d down.",
+			 msg->msg_type,
+			 piaddr(peer),
+			 ntohs(to.sin6_port));
+		goto cleanup;
+	}
+
+	/* Send the message to the downstream. */
+	send_packet6(dp->ifp, (unsigned char *) relay_msg.data,
+		     (size_t) relay_msg.len, &to);
+
+      cleanup:
+	if (relay_msg.data != NULL)
+		data_string_forget(&relay_msg, MDL);
+	if (if_id.data != NULL)
+		data_string_forget(&if_id, MDL);
+}
+#endif /* UNIT_TEST */
+
+/*
+ * Called by the dispatch packet handler with a decoded packet.
+ */
+void
+dhcpv6(struct packet *packet) {
+#ifndef UNIT_TEST
+	struct stream_list *dp;
+
+	/* Try all relay-replies downwards. */
+	if (packet->dhcpv6_msg_type == DHCPV6_RELAY_REPL) {
+		process_down6(packet);
+		return;
+	}
+	/* Others are candidates to go up if they come from down. */
+	for (dp = downstreams; dp; dp = dp->next) {
+		if (packet->interface != dp->ifp)
+			continue;
+		process_up6(packet, dp);
+		return;
+	}
+	/* Relay-forward could work from an unknown interface. */
+	if (packet->dhcpv6_msg_type == DHCPV6_RELAY_FORW) {
+		process_up6(packet, NULL);
+		return;
+	}
+
+	log_info("Can't process packet from interface '%s'.",
+		 packet->interface->name);
+#endif /* UNIT_TEST */
+}
+#endif /* DHCPv6 */
+
+/* Stub routines needed for linking with DHCP libraries. */
+void
+bootp(struct packet *packet) {
+	return;
+}
+
+void
+dhcp(struct packet *packet) {
+	return;
+}
+
+#if defined(DHCPv6) && defined(DHCP4o6)
+isc_result_t dhcpv4o6_handler(omapi_object_t *h)
+{
+	return ISC_R_NOTIMPLEMENTED;
+}
+#endif
+
+void
+classify(struct packet *p, struct class *c) {
+	return;
+}
+
+int
+check_collection(struct packet *p, struct lease *l, struct collection *c) {
+	return 0;
+}
+
+isc_result_t
+find_class(struct class **class, const char *c1, const char *c2, int i) {
+	return ISC_R_NOTFOUND;
+}
+
+int
+parse_allow_deny(struct option_cache **oc, struct parse *p, int i) {
+	return 0;
+}
+
+isc_result_t
+dhcp_set_control_state(control_object_state_t oldstate,
+		       control_object_state_t newstate) {
+	char buf = 0;
+
+	if (newstate != server_shutdown)
+		return ISC_R_SUCCESS;
+
+	/* Log shutdown on signal. */
+	log_info("Received signal %d, initiating shutdown.", shutdown_signal);
+
+	if (no_pid_file == ISC_FALSE)
+		(void) unlink(path_dhcrelay_pid);
+
+	if (!no_daemon && dfd[0] != -1 && dfd[1] != -1) {
+		IGNORE_RET(write(dfd[1], &buf, 1));
+		(void) close(dfd[1]);
+		dfd[0] = dfd[1] = -1;
+	}
+	exit(0);
+}
+
+/*!
+ *
+ * \brief Allocate an interface as requested with a given set of flags
+ *
+ * The requested interface is allocated, its flags field is set to
+ * INTERFACE_REQUESTED OR'd with the given flags,  and then added to
+ * the list of interfaces.
+ *
+ * \param name - name of the requested interface
+ * \param flags - additional flags for the interface
+ *
+ * \return Nothing
+ */
+void request_v4_interface(const char* name, int flags) {
+        struct interface_info *tmp = NULL;
+        int len = strlen(name);
+        isc_result_t status;
+
+        if (len >= sizeof(tmp->name)) {
+                log_fatal("%s: interface name too long (is %d)", name, len);
+        }
+
+        status = interface_allocate(&tmp, MDL);
+        if (status != ISC_R_SUCCESS) {
+                log_fatal("%s: interface_allocate: %s", name,
+                          isc_result_totext(status));
+        }
+
+	log_debug("Requesting: %s as upstream: %c downstream: %c", name,
+		  (flags & INTERFACE_UPSTREAM ? 'Y' : 'N'),
+		  (flags & INTERFACE_DOWNSTREAM ? 'Y' : 'N'));
+
+        memcpy(tmp->name, name, len);
+        interface_snorf(tmp, (INTERFACE_REQUESTED | flags));
+        interface_dereference(&tmp, MDL);
+}
Index: relay
===================================================================
--- relay	(nonexistent)
+++ relay	(revision 5)

Property changes on: relay
___________________________________________________________________
Added: svn:ignore
## -0,0 +1,73 ##
+
+# install dir
+dist
+
+# Target build dirs
+.a1x-newlib
+.a2x-newlib
+.at91sam7s-newlib
+
+.build-machine
+
+.a1x-glibc
+.a2x-glibc
+.h3-glibc
+.h5-glibc
+.i586-glibc
+.i686-glibc
+.imx6-glibc
+.jz47xx-glibc
+.makefile
+.am335x-glibc
+.omap543x-glibc
+.p5600-glibc
+.power8-glibc
+.power8le-glibc
+.power9-glibc
+.power9le-glibc
+.m1000-glibc
+.riscv64-glibc
+.rk328x-glibc
+.rk33xx-glibc
+.rk339x-glibc
+.s8xx-glibc
+.s9xx-glibc
+.x86_64-glibc
+
+# Hidden files (each file)
+.makefile
+.dist
+.rootfs
+
+# src & hw requires
+.src_requires
+.src_requires_depend
+.requires
+.requires_depend
+
+# Tarballs
+*.gz
+*.bz2
+*.lz
+*.xz
+*.tgz
+*.txz
+
+# Signatures
+*.asc
+*.sig
+*.sign
+*.sha1sum
+
+# Patches
+*.patch
+
+# Descriptions
+*.dsc
+*.txt
+
+# Default linux config files
+*.defconfig
+
+# backup copies
+*~
Index: server/mdb.c
===================================================================
--- server/mdb.c	(nonexistent)
+++ server/mdb.c	(revision 5)
@@ -0,0 +1,3279 @@
+/* mdb.c
+
+   Server-specific in-memory database support. */
+
+/*
+ * Copyright (c) 2004-2019 by 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.
+ *   950 Charter Street
+ *   Redwood City, CA 94063
+ *   <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 */
Index: server
===================================================================
--- server	(nonexistent)
+++ server	(revision 5)

Property changes on: server
___________________________________________________________________
Added: svn:ignore
## -0,0 +1,73 ##
+
+# install dir
+dist
+
+# Target build dirs
+.a1x-newlib
+.a2x-newlib
+.at91sam7s-newlib
+
+.build-machine
+
+.a1x-glibc
+.a2x-glibc
+.h3-glibc
+.h5-glibc
+.i586-glibc
+.i686-glibc
+.imx6-glibc
+.jz47xx-glibc
+.makefile
+.am335x-glibc
+.omap543x-glibc
+.p5600-glibc
+.power8-glibc
+.power8le-glibc
+.power9-glibc
+.power9le-glibc
+.m1000-glibc
+.riscv64-glibc
+.rk328x-glibc
+.rk33xx-glibc
+.rk339x-glibc
+.s8xx-glibc
+.s9xx-glibc
+.x86_64-glibc
+
+# Hidden files (each file)
+.makefile
+.dist
+.rootfs
+
+# src & hw requires
+.src_requires
+.src_requires_depend
+.requires
+.requires_depend
+
+# Tarballs
+*.gz
+*.bz2
+*.lz
+*.xz
+*.tgz
+*.txz
+
+# Signatures
+*.asc
+*.sig
+*.sign
+*.sha1sum
+
+# Patches
+*.patch
+
+# Descriptions
+*.dsc
+*.txt
+
+# Default linux config files
+*.defconfig
+
+# backup copies
+*~
Index: .
===================================================================
--- .	(nonexistent)
+++ .	(revision 5)

Property changes on: .
___________________________________________________________________
Added: svn:ignore
## -0,0 +1,73 ##
+
+# install dir
+dist
+
+# Target build dirs
+.a1x-newlib
+.a2x-newlib
+.at91sam7s-newlib
+
+.build-machine
+
+.a1x-glibc
+.a2x-glibc
+.h3-glibc
+.h5-glibc
+.i586-glibc
+.i686-glibc
+.imx6-glibc
+.jz47xx-glibc
+.makefile
+.am335x-glibc
+.omap543x-glibc
+.p5600-glibc
+.power8-glibc
+.power8le-glibc
+.power9-glibc
+.power9le-glibc
+.m1000-glibc
+.riscv64-glibc
+.rk328x-glibc
+.rk33xx-glibc
+.rk339x-glibc
+.s8xx-glibc
+.s9xx-glibc
+.x86_64-glibc
+
+# Hidden files (each file)
+.makefile
+.dist
+.rootfs
+
+# src & hw requires
+.src_requires
+.src_requires_depend
+.requires
+.requires_depend
+
+# Tarballs
+*.gz
+*.bz2
+*.lz
+*.xz
+*.tgz
+*.txz
+
+# Signatures
+*.asc
+*.sig
+*.sign
+*.sha1sum
+
+# Patches
+*.patch
+
+# Descriptions
+*.dsc
+*.txt
+
+# Default linux config files
+*.defconfig
+
+# backup copies
+*~