aboutsummaryrefslogtreecommitdiff
path: root/client/dhclient.c
diff options
context:
space:
mode:
Diffstat (limited to 'client/dhclient.c')
-rw-r--r--client/dhclient.c4282
1 files changed, 4282 insertions, 0 deletions
diff --git a/client/dhclient.c b/client/dhclient.c
new file mode 100644
index 0000000..48707d1
--- /dev/null
+++ b/client/dhclient.c
@@ -0,0 +1,4282 @@
+/* dhclient.c
+
+ DHCP Client. */
+
+/*
+ * Copyright (c) 2004-2011 by Internet Systems Consortium, Inc. ("ISC")
+ * Copyright (c) 1995-2003 by Internet Software Consortium
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
+ * OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ *
+ * Internet Systems Consortium, Inc.
+ * 950 Charter Street
+ * Redwood City, CA 94063
+ * <info@isc.org>
+ * 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 <syslog.h>
+#include <signal.h>
+#include <errno.h>
+#include <sys/time.h>
+#include <sys/wait.h>
+#include <limits.h>
+#include <dns/result.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;
+
+/* 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;
+
+/* ASSERT_STATE() does nothing now; it used to be
+ assert (state_is == state_shouldbe). */
+#define ASSERT_STATE(state_is, state_shouldbe) {}
+
+static const char copyright[] =
+"Copyright 2004-2011 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/";
+
+u_int16_t local_port = 0;
+u_int16_t remote_port = 0;
+int no_daemon = 0;
+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;
+char *mockup_relay = NULL;
+
+void run_stateless(int exit_mode);
+
+static void usage(void);
+
+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);
+
+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;
+#endif /* DHCPv6 */
+ char *s;
+
+ /* Initialize client globals. */
+ memset(&default_duid, 0, sizeof(default_duid));
+
+ /* 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("dhclient", LOG_NDELAY, LOG_DAEMON);
+
+#if !(defined(DEBUG) || defined(__CYGWIN32__))
+ setlogmask(LOG_UPTO(LOG_INFO));
+#endif
+
+ /* Set up the isc and dns library managers */
+ status = dhcp_context_create();
+ 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;
+#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();
+ 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();
+ 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();
+ path_dhclient_conf = argv[i];
+ no_dhclient_conf = 1;
+ } else if (!strcmp(argv[i], "-lf")) {
+ if (++i == argc)
+ usage();
+ path_dhclient_db = argv[i];
+ no_dhclient_db = 1;
+ } else if (!strcmp(argv[i], "-sf")) {
+ if (++i == argc)
+ usage();
+ 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();
+ server = argv[i];
+ } else if (!strcmp(argv[i], "-g")) {
+ if (++i == argc)
+ usage();
+ 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();
+ 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();
+ }
+ 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();
+ }
+ 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();
+ }
+ 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();
+ }
+ 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], "-D")) {
+ if (local_family_set && (local_family == AF_INET)) {
+ usage();
+ }
+ local_family_set = 1;
+ local_family = AF_INET6;
+ if (++i == argc)
+ usage();
+ if (!strcasecmp(argv[i], "LL")) {
+ duid_type = DUID_LL;
+ } else if (!strcasecmp(argv[i], "LLT")) {
+ duid_type = DUID_LLT;
+ } else {
+ usage();
+ }
+#endif /* DHCPv6 */
+ } else if (!strcmp(argv[i], "-v")) {
+ quiet = 0;
+ } else if (!strcmp(argv[i], "--version")) {
+ log_info("isc-dhclient-%s", PACKAGE_VERSION);
+ exit(0);
+ } else if (argv[i][0] == '-') {
+ usage();
+ } else if (interfaces_requested < 0) {
+ usage();
+ } 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();
+ }
+
+ 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();
+
+ /* 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] != '/') {
+ char *path = dmalloc(PATH_MAX, MDL);
+ if (path == NULL)
+ log_fatal("No memory for filename\n");
+ path_dhclient_db = realpath(path_dhclient_db, path);
+ if (path_dhclient_db == NULL)
+ log_fatal("%s: %s", path, strerror(errno));
+ }
+
+ if (path_dhclient_script[0] != '/') {
+ char *path = dmalloc(PATH_MAX, MDL);
+ if (path == NULL)
+ log_fatal("No memory for filename\n");
+ path_dhclient_script = realpath(path_dhclient_script, path);
+ if (path_dhclient_script == NULL)
+ log_fatal("%s: %s", path, strerror(errno));
+ }
+
+ /*
+ * 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;
+
+ oldpid = 0;
+ if ((pidfd = fopen(path_dhclient_pid, "r")) != NULL) {
+ e = fscanf(pidfd, "%ld\n", &temp);
+ oldpid = (pid_t)temp;
+
+ if (e != 0 && e != EOF) {
+ if (oldpid)
+ kill(oldpid, SIGTERM);
+ }
+ 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();
+ }
+ run_stateless(exit_mode);
+ return 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();
+
+ /* 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.");
+ exit(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());
+
+ /* Start a configuration state machine for each interface. */
+#ifdef DHCPv6
+ if (local_family == AF_INET6) {
+ /* Establish a default DUID. This may be moved to the
+ * DHCPv4 area later.
+ */
+ 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);
+ }
+
+ 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);
+ else 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)
+ return 0;
+ if (release_mode) {
+#ifndef DHCPv6
+ return 0;
+#else
+ if (local_family == AF_INET6) {
+ if (onetry)
+ return 0;
+ } else
+ return 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 we're not supposed to wait before getting the address,
+ don't. */
+ if (nowait)
+ go_daemon();
+
+ /* 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();
+
+ /*NOTREACHED*/
+ return 0;
+}
+
+static void usage()
+{
+ log_info("%s %s", message, PACKAGE_VERSION);
+ log_info(copyright);
+ log_info(arr);
+ log_info(url);
+
+
+ log_fatal("Usage: dhclient "
+#ifdef DHCPv6
+ "[-4|-6] [-SNTP1dvrx] [-nw] [-p <port>] [-D LL|LLT]\n"
+#else /* DHCPv6 */
+ "[-1dvrx] [-nw] [-p <port>]\n"
+#endif /* DHCPv6 */
+ " [-s server-addr] [-cf config-file] "
+ "[-lf lease-file]\n"
+ " [-pf pid-file] [--no-pid] [-e VAR=val]\n"
+ " [-sf script-file] [interface]");
+}
+
+void run_stateless(int exit_mode)
+{
+#ifdef DHCPv6
+ struct client_state *client;
+ omapi_object_t *listener;
+ isc_result_t result;
+
+ /* Discover the network interface. */
+ discover_interfaces(DISCOVER_REQUESTED);
+
+ if (!interfaces)
+ usage();
+
+ /* Parse the dhclient.conf file. */
+ read_client_conf();
+
+ /* Parse the lease database. */
+ read_client_leases();
+
+ /* 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);
+ }
+
+ /* 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)
+ go_daemon();
+
+ /* 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();
+
+ /*NOTREACHED*/
+#endif /* DHCPv6 */
+ return;
+}
+
+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;
+{
+}
+
+int unbill_class (lease, class)
+ struct lease *lease;
+ struct class *class;
+{
+ return 0;
+}
+
+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 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 from %s", 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);
+ 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 = (struct client_lease *)0;
+ state_init (client);
+ 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, 0);
+
+ /* Replace the old active lease with the new one. */
+ if (client -> active)
+ destroy_client_lease (client -> active);
+ client -> active = client -> new;
+ client -> new = (struct client_lease *)0;
+
+ /* 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 ();
+ go_daemon ();
+#if defined (NSUPDATE)
+ if (client->config->do_forward_update)
+ dhclient_schedule_updates(client, &client->active->address, 1);
+#endif
+}
+
+/* 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;
+
+ /* 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);
+ 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) {
+ 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.");
+}
+#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 from %s", name, 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;
+ }
+
+ /* 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);
+ }
+ log_info("%s", obuf);
+}
+
+/* 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 (struct client_lease *)0;
+ }
+
+ 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);
+
+ 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 ((struct binding_value **)0,
+ (struct packet *)packet,
+ (struct lease *)0, client,
+ lease -> options, lease -> options,
+ &global_scope,
+ client -> config -> on_receipt,
+ (struct group *)0);
+
+ 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);
+ 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;
+
+ 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. */
+ result = send_packet (client -> interface, (struct packet *)0,
+ &client -> packet,
+ client -> packet_length,
+ inaddr_any, &sockaddr_broadcast,
+ (struct hardware *)0);
+
+ /*
+ * 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);
+}
+
+/* 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 recorded lease %s",
+ 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);
+ 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 ();
+ go_daemon ();
+ 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.. */
+ for (lp = client -> leases; lp -> next; lp = lp -> next)
+ ;
+ lp -> next = client -> active;
+ if (lp -> next) {
+ lp -> next -> next = (struct client_lease *)0;
+ }
+ 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.");
+ exit (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);
+ go_daemon ();
+}
+
+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;
+
+ /* 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);
+ 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);
+ }
+
+ log_info ("DHCPREQUEST on %s to %s port %d",
+ client -> name ? client -> name : client -> interface -> name,
+ inet_ntoa (destination.sin_addr),
+ ntohs (destination.sin_port));
+
+ if (destination.sin_addr.s_addr != INADDR_BROADCAST &&
+ fallback_interface)
+ result = send_packet (fallback_interface,
+ (struct packet *)0,
+ &client -> packet,
+ client -> packet_length,
+ from, &destination,
+ (struct hardware *)0);
+ else
+ /* Send out a packet. */
+ result = send_packet (client -> interface, (struct packet *)0,
+ &client -> packet,
+ client -> packet_length,
+ from, &destination,
+ (struct hardware *)0);
+
+ 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;
+
+ log_info ("DHCPDECLINE on %s to %s port %d",
+ client -> name ? client -> name : client -> interface -> name,
+ inet_ntoa (sockaddr_broadcast.sin_addr),
+ ntohs (sockaddr_broadcast.sin_port));
+
+ /* Send out a packet. */
+ result = send_packet (client -> interface, (struct packet *)0,
+ &client -> packet,
+ client -> packet_length,
+ inaddr_any, &sockaddr_broadcast,
+ (struct hardware *)0);
+}
+
+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;
+ }
+
+ log_info ("DHCPRELEASE on %s to %s port %d",
+ client -> name ? client -> name : client -> interface -> name,
+ inet_ntoa (destination.sin_addr),
+ ntohs (destination.sin_port));
+
+ if (fallback_interface)
+ result = send_packet (fallback_interface,
+ (struct packet *)0,
+ &client -> packet,
+ client -> packet_length,
+ from, &destination,
+ (struct hardware *)0);
+ else
+ /* Send out a packet. */
+ result = send_packet (client -> interface, (struct packet *)0,
+ &client -> packet,
+ client -> packet_length,
+ from, &destination,
+ (struct hardware *)0);
+}
+
+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 = (struct buffer *)0;
+
+ /* 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 = (struct option_cache *)0;
+
+ /* 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)))
+ log_error ("can't make option 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
+ ((struct binding_value **)0,
+ (struct packet *)0, (struct lease *)0, client,
+ (lease ? lease -> options : (struct option_state *)0),
+ *op, &global_scope,
+ client -> config -> on_transmission,
+ (struct group *)0);
+}
+
+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];
+ 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];
+ 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];
+ 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];
+ 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)) {
+ fprintf(leaseFile, "%soption %s%s%s %s;\n", preamble,
+ name, dot, oc->option->name,
+ pretty_print_option(oc->option, ds.data, ds.len,
+ 1, 1));
+ 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);
+ }
+}
+
+/* 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;
+ }
+ }
+
+ /* It would make more sense to write this as a hex string,
+ * but our function to do that (print_hex_n) uses a fixed
+ * length buffer...and we can't guarantee a duid would be
+ * less than the fixed length.
+ */
+ str = quotify_buf(duid->data, duid->len, 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;
+ }
+ stat = fprintf(leaseFile, " %s %s {\n",
+ ianame, print_hex_1(4, ia->iaid, 12));
+ 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;
+
+void script_init (client, reason, medium)
+ 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 ());
+ }
+}
+
+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);
+ }
+ }
+ }
+}
+
+void script_write_params (client, prefix, lease)
+ 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));
+
+ /* 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", "%d", (int)(lease -> expiry));
+}
+
+int script_go (client)
+ 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)
+ 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 go_daemon ()
+{
+ static int state = 0;
+ int pid;
+
+ /* Don't become a daemon if the user requested otherwise. */
+ if (no_daemon) {
+ write_client_pid_file ();
+ return;
+ }
+
+ /* Only do it once. */
+ if (state)
+ return;
+ state = 1;
+
+ /* Stop logging to stderr... */
+ log_perror = 0;
+
+ /* Become a daemon... */
+ if ((pid = fork ()) < 0)
+ log_fatal ("Can't fork daemon: %m");
+ else if (pid)
+ exit (0);
+ /* Become session leader and get pid... */
+ pid = setsid ();
+
+ /* Close standard I/O descriptors. */
+ close(0);
+ close(1);
+ close(2);
+
+ /* Reopen them on /dev/null. */
+ open("/dev/null", O_RDWR);
+ open("/dev/null", O_RDWR);
+ 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:
+ break;
+ }
+ client -> state = S_INIT;
+ state_reboot (client);
+ }
+ }
+}
+
+void do_release(client)
+ struct client_state *client;
+{
+ struct data_string ds;
+ struct option_cache *oc;
+
+ /* 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_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;
+}
+
+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)
+{
+ exit (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);
+ if (result == ISC_R_SUCCESS) {
+ return;
+ }
+ }
+
+ /* If we are done or have an error clean up */
+ 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);
+ 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) {
+ ddns_cb_free(ddns_cb, MDL);
+ }
+ }
+}
+#endif
+
+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;
+
+ /* 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
+ 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) {
+ if (client != NULL) {
+ client->ddns_cb = NULL;
+ }
+ 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);
+ 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;
+ }
+
+ 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;
+ 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;
+
+ /* If this is a DHCPv6 client update, make a dhcid string out of
+ * the DUID. If this is a DHCPv4 client update, choose either
+ * the client identifier, if there is one, or the interface's
+ * MAC address.
+ */
+ result = 0;
+ memset(&client_identifier, 0, sizeof(client_identifier));
+ if (client->active_lease != NULL) {
+ 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)) {
+ /* RFC4701 defines type '2' as being for the DUID
+ * field. We aren't using RFC4701 DHCID RR's yet,
+ * but this is as good a value as any.
+ */
+ result = get_dhcid(&ddns_cb->dhcid, 2,
+ client_identifier.data,
+ client_identifier.len);
+ data_string_forget(&client_identifier, MDL);
+ } else
+ log_fatal("Impossible condition at %s:%d.", MDL);
+ } else {
+ 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)) {
+ result = get_dhcid(&ddns_cb->dhcid,
+ DHO_DHCP_CLIENT_IDENTIFIER,
+ client_identifier.data,
+ client_identifier.len);
+ data_string_forget(&client_identifier, MDL);
+ } else
+ result = get_dhcid(&ddns_cb->dhcid, 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);
+ } 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);
+ 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
+
+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)
+ 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.");
+}