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 (<, lp -> ip_addr, MDL)) {
+ if (lt -> pool) {
+ parse_warn (cfile,
+ "lease %s is declared twice!",
+ piaddr (lp -> ip_addr));
+ } else
+ pool_reference (< -> pool, pool, MDL);
+ lease_dereference (<, 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 (<, 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 (<, 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 (<, MDL);
+ return 0;
+ }
+ strcpy (lt -> client_hostname, lease -> client_hostname);
+ }
+ if (lease -> scope)
+ binding_scope_reference (< -> scope, lease -> scope, MDL);
+ if (lease -> agent_options)
+ option_chain_head_reference (< -> agent_options,
+ lease -> agent_options, MDL);
+ host_reference (< -> host, lease -> host, file, line);
+ subnet_reference (< -> subnet, lease -> subnet, file, line);
+ pool_reference (< -> pool, lease -> pool, file, line);
+ class_reference (< -> billing_class,
+ lease -> billing_class, file, line);
+ lt -> hardware_addr = lease -> hardware_addr;
+ if (lease->on_star.on_expiry)
+ executable_statement_reference (<->on_star.on_expiry,
+ lease->on_star.on_expiry,
+ file, line);
+ if (lease->on_star.on_commit)
+ executable_statement_reference (<->on_star.on_commit,
+ lease->on_star.on_commit,
+ file, line);
+ if (lease->on_star.on_release)
+ executable_statement_reference (<->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(<, 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(<, lease, MDL)) {
+ return;
+ }
+
+ if (lt->scope) {
+ binding_scope_dereference(<->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(<, 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 (<, 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 (<, 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
+*~