[BACK]Return to dhclient.c CVS log [TXT][DIR] Up to [local] / src / sbin / dhclient

File: [local] / src / sbin / dhclient / dhclient.c (download)

Revision 1.728, Sun Apr 28 16:43:42 2024 UTC (5 weeks, 6 days ago) by florian
Branch: MAIN
CVS Tags: HEAD
Changes since 1.727: +11 -4 lines

gmtime(3) / locatime(3) can fail when timestamps are way off.

Add missing error checks to all calls under sbin/

Input & OK millert

/*	$OpenBSD: dhclient.c,v 1.728 2024/04/28 16:43:42 florian Exp $	*/

/*
 * Copyright 2004 Henning Brauer <henning@openbsd.org>
 * Copyright (c) 1995, 1996, 1997, 1998, 1999
 * The Internet Software Consortium.    All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. Neither the name of The Internet Software Consortium nor the names
 *    of its contributors may be used to endorse or promote products derived
 *    from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE INTERNET SOFTWARE CONSORTIUM AND
 * CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
 * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED.  IN NO EVENT SHALL THE INTERNET SOFTWARE CONSORTIUM OR
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
 * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
 * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 *
 * This software has been written for the Internet Software Consortium
 * by Ted Lemon <mellon@fugue.com> in cooperation with Vixie
 * Enterprises.  To learn more about the Internet Software Consortium,
 * see ``http://www.vix.com/isc''.  To learn more about Vixie
 * Enterprises, see ``http://www.vix.com''.
 *
 * This client was substantially modified and enhanced by Elliot Poger
 * for use on Linux while he was working on the MosquitoNet project at
 * Stanford.
 *
 * The current version owes much to Elliot's Linux enhancements, but
 * was substantially reorganized and partially rewritten by Ted Lemon
 * so as to use the same networking framework that the Internet Software
 * Consortium DHCP server uses.   Much system-specific configuration code
 * was moved into a shell script so that as support for more operating
 * systems is added, it will not be necessary to port and maintain
 * system-specific configuration code to these operating systems - instead,
 * the shell script can invoke the native tools to accomplish the same
 * purpose.
 */

#include <sys/types.h>
#include <sys/socket.h>
#include <sys/stat.h>
#include <sys/ioctl.h>
#include <sys/uio.h>
#include <sys/queue.h>

#include <net/if.h>
#include <net/if_types.h>
#include <net/if_dl.h>
#include <net/route.h>

#include <netinet/in.h>
#include <netinet/if_ether.h>

#include <net80211/ieee80211.h>
#include <net80211/ieee80211_ioctl.h>

#include <arpa/inet.h>

#include <ctype.h>
#include <errno.h>
#include <fcntl.h>
#include <ifaddrs.h>
#include <imsg.h>
#include <limits.h>
#include <paths.h>
#include <poll.h>
#include <pwd.h>
#include <resolv.h>
#include <signal.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <syslog.h>
#include <unistd.h>

#include "dhcp.h"
#include "dhcpd.h"
#include "log.h"
#include "privsep.h"

char *path_dhclient_conf;
char *path_lease_db;
char *log_procname;

int nullfd = -1;
int cmd_opts;
int quit;

const struct in_addr inaddr_any = { INADDR_ANY };
const struct in_addr inaddr_broadcast = { INADDR_BROADCAST };

struct client_config *config;
struct imsgbuf *unpriv_ibuf;

void		 usage(void);
int		 res_hnok_list(const char *);
int		 addressinuse(char *, struct in_addr, char *);

void		 fork_privchld(struct interface_info *, int, int);
void		 get_name(struct interface_info *, int, char *);
void		 get_ssid(struct interface_info *, int);
void		 get_sockets(struct interface_info *);
int		 get_routefd(int);
void		 set_iff_up(struct interface_info *, int);
void		 set_user(char *);
int		 get_ifa_family(char *, int);
struct ifaddrs	*get_link_ifa(const char *, struct ifaddrs *);
void		 interface_state(struct interface_info *);
struct interface_info *initialize_interface(char *, int);
void		 tick_msg(const char *, int);
void		 rtm_dispatch(struct interface_info *, struct rt_msghdr *);

struct client_lease *apply_defaults(struct client_lease *);
struct client_lease *clone_lease(struct client_lease *);

void state_reboot(struct interface_info *);
void state_init(struct interface_info *);
void state_selecting(struct interface_info *);
void state_bound(struct interface_info *);
void state_panic(struct interface_info *);

void set_interval(struct interface_info *, struct timespec *);
void set_resend_timeout(struct interface_info *, struct timespec *,
    void (*where)(struct interface_info *));
void set_secs(struct interface_info *, struct timespec *);

void send_discover(struct interface_info *);
void send_request(struct interface_info *);
void send_decline(struct interface_info *);
void send_release(struct interface_info *);

void process_offer(struct interface_info *, struct option_data *,
    const char *);
void bind_lease(struct interface_info *);

void make_discover(struct interface_info *, struct client_lease *);
void make_request(struct interface_info *, struct client_lease *);
void make_decline(struct interface_info *, struct client_lease *);
void make_release(struct interface_info *, struct client_lease *);

void release_lease(struct interface_info *);
void propose_release(struct interface_info *);

void write_lease_db(struct interface_info *);
char *lease_as_string(char *, struct client_lease *);
struct proposal *lease_as_proposal(struct client_lease *);
struct unwind_info *lease_as_unwind_info(struct client_lease *);
void append_statement(char *, size_t, char *, char *);
time_t lease_expiry(struct client_lease *);
time_t lease_renewal(struct client_lease *);
time_t lease_rebind(struct client_lease *);
void   get_lease_timeouts(struct interface_info *, struct client_lease *);

struct client_lease *packet_to_lease(struct interface_info *,
    struct option_data *);
void go_daemon(void);
int rdaemon(int);
int take_charge(struct interface_info *, int, char *);
int autoconf(struct interface_info *);
struct client_lease *get_recorded_lease(struct interface_info *);

#define ROUNDUP(a)	\
	((a) > 0 ? (1 + (((a) - 1) | (sizeof(long) - 1))) : sizeof(long))
#define	ADVANCE(x, n) (x += ROUNDUP((n)->sa_len))

#define	TICK_WAIT	0
#define	TICK_SUCCESS	1
#define	TICK_DAEMON	2

static FILE *leaseFile;

int
get_ifa_family(char *cp, int n)
{
	struct sockaddr		*sa;
	unsigned int		 i;

	for (i = 1; i; i <<= 1) {
		if ((i & n) != 0) {
			sa = (struct sockaddr *)cp;
			if (i == RTA_IFA)
				return sa->sa_family;
			ADVANCE(cp, sa);
		}
	}

	return AF_UNSPEC;
}

struct ifaddrs *
get_link_ifa(const char *name, struct ifaddrs *ifap)
{
	struct ifaddrs		*ifa;
	struct sockaddr_dl	*sdl;

	for (ifa = ifap; ifa != NULL; ifa = ifa->ifa_next) {
		if (strcmp(name, ifa->ifa_name) == 0 &&
		    (ifa->ifa_flags & IFF_LOOPBACK) == 0 &&
		    (ifa->ifa_flags & IFF_POINTOPOINT) == 0 &&
		    ifa->ifa_data != NULL && /* NULL shouldn't be possible. */
		    ifa->ifa_addr != NULL &&
		    ifa->ifa_addr->sa_family == AF_LINK) {
			sdl = (struct sockaddr_dl *)ifa->ifa_addr;
			if (sdl->sdl_alen == ETHER_ADDR_LEN &&
			    (sdl->sdl_type == IFT_ETHER ||
			    sdl->sdl_type == IFT_CARP))
				break;
		}
	}

	if (ifa == NULL)
		fatal("get_link_ifa()");

	return ifa;
}

void
interface_state(struct interface_info *ifi)
{
	struct ifaddrs			*ifap, *ifa;
	struct if_data			*ifd;
	struct sockaddr_dl		*sdl;
	char				*oldlladdr;
	int				 newlinkup, oldlinkup;

	oldlinkup = LINK_STATE_IS_UP(ifi->link_state);

	if (getifaddrs(&ifap) == -1)
		fatal("getifaddrs");

	ifa = get_link_ifa(ifi->name, ifap);
	sdl = (struct sockaddr_dl *)ifa->ifa_addr;
	ifd = (struct if_data *)ifa->ifa_data;

	if ((ifa->ifa_flags & IFF_UP) == 0 ||
	    (ifa->ifa_flags & IFF_RUNNING) == 0) {
		ifi->link_state = LINK_STATE_DOWN;
	} else {
		ifi->link_state = ifd->ifi_link_state;
		ifi->mtu = ifd->ifi_mtu;
	}

	newlinkup = LINK_STATE_IS_UP(ifi->link_state);
	if (newlinkup != oldlinkup) {
		log_debug("%s: link %s -> %s", log_procname,
		    (oldlinkup != 0) ? "up" : "down",
		    (newlinkup != 0) ? "up" : "down");
		tick_msg("link", newlinkup ? TICK_SUCCESS : TICK_WAIT);
	}

	if (memcmp(&ifi->hw_address, LLADDR(sdl), ETHER_ADDR_LEN) != 0) {
		if (log_getverbose()) {
			oldlladdr = strdup(ether_ntoa(&ifi->hw_address));
			if (oldlladdr == NULL)
				fatal("oldlladdr");
			log_debug("%s: LLADDR %s -> %s", log_procname,
			    oldlladdr,
			    ether_ntoa((struct ether_addr *)LLADDR(sdl)));
			free(oldlladdr);
		}
		memcpy(&ifi->hw_address, LLADDR(sdl), ETHER_ADDR_LEN);
		quit = RESTART;	/* Even if MTU has changed. */
	}

	freeifaddrs(ifap);
}

struct interface_info *
initialize_interface(char *name, int noaction)
{
	struct interface_info		*ifi;
	struct ifaddrs			*ifap, *ifa;
	struct if_data			*ifd;
	struct sockaddr_dl		*sdl;
	int				 ioctlfd;

	ifi = calloc(1, sizeof(*ifi));
	if (ifi == NULL)
		fatal("ifi");

	ifi->rbuf_max = RT_BUF_SIZE;
	ifi->rbuf = malloc(ifi->rbuf_max);
	if (ifi->rbuf == NULL)
		fatal("rbuf");

	if ((ioctlfd = socket(AF_INET, SOCK_DGRAM, 0)) == -1)
		fatal("socket(AF_INET, SOCK_DGRAM)");

	get_name(ifi, ioctlfd, name);
	ifi->index = if_nametoindex(ifi->name);
	if (ifi->index == 0)
		fatalx("if_nametoindex(%s) == 0", ifi->name);

	if (getifaddrs(&ifap) == -1)
		fatal("getifaddrs()");

	ifa = get_link_ifa(ifi->name, ifap);
	sdl = (struct sockaddr_dl *)ifa->ifa_addr;
	ifd = (struct if_data *)ifa->ifa_data;

	if ((ifa->ifa_flags & IFF_UP) == 0 ||
	    (ifa->ifa_flags & IFF_RUNNING) == 0)
		ifi->link_state = LINK_STATE_DOWN;
	else
		ifi->link_state = ifd->ifi_link_state;

	memcpy(ifi->hw_address.ether_addr_octet, LLADDR(sdl), ETHER_ADDR_LEN);
	ifi->rdomain = ifd->ifi_rdomain;

	get_sockets(ifi);
	get_ssid(ifi, ioctlfd);

	if (noaction == 0 && !LINK_STATE_IS_UP(ifi->link_state))
		set_iff_up(ifi, ioctlfd);

	close(ioctlfd);
	freeifaddrs(ifap);

	return ifi;
}

void
get_name(struct interface_info *ifi, int ioctlfd, char *arg)
{
	struct ifgroupreq	 ifgr;
	size_t			 len;

	if (strcmp(arg, "egress") == 0) {
		memset(&ifgr, 0, sizeof(ifgr));
		strlcpy(ifgr.ifgr_name, "egress", sizeof(ifgr.ifgr_name));
		if (ioctl(ioctlfd, SIOCGIFGMEMB, (caddr_t)&ifgr) == -1)
			fatal("SIOCGIFGMEMB");
		if (ifgr.ifgr_len > sizeof(struct ifg_req))
			fatalx("too many interfaces in group egress");
		if ((ifgr.ifgr_groups = calloc(1, ifgr.ifgr_len)) == NULL)
			fatalx("ifgr_groups");
		if (ioctl(ioctlfd, SIOCGIFGMEMB, (caddr_t)&ifgr) == -1)
			fatal("SIOCGIFGMEMB");
		len = strlcpy(ifi->name, ifgr.ifgr_groups->ifgrq_member,
		    IFNAMSIZ);
		free(ifgr.ifgr_groups);
	} else
		len = strlcpy(ifi->name, arg, IFNAMSIZ);

	if (len >= IFNAMSIZ)
		fatalx("interface name too long");
}

void
get_ssid(struct interface_info *ifi, int ioctlfd)
{
	struct ieee80211_nwid		nwid;
	struct ifreq			ifr;

	memset(&ifr, 0, sizeof(ifr));
	memset(&nwid, 0, sizeof(nwid));

	ifr.ifr_data = (caddr_t)&nwid;
	strlcpy(ifr.ifr_name, ifi->name, sizeof(ifr.ifr_name));

	if (ioctl(ioctlfd, SIOCG80211NWID, (caddr_t)&ifr) == 0) {
		memset(ifi->ssid, 0, sizeof(ifi->ssid));
		memcpy(ifi->ssid, nwid.i_nwid, nwid.i_len);
		ifi->ssid_len = nwid.i_len;
	}
}

void
set_iff_up(struct interface_info *ifi, int ioctlfd)
{
	struct ifreq	 ifr;

	memset(&ifr, 0, sizeof(ifr));
	strlcpy(ifr.ifr_name, ifi->name, sizeof(ifr.ifr_name));

	if (ioctl(ioctlfd, SIOCGIFFLAGS, (caddr_t)&ifr) == -1)
		fatal("%s: SIOCGIFFLAGS", ifi->name);

	if ((ifr.ifr_flags & IFF_UP) == 0) {
		ifi->link_state = LINK_STATE_DOWN;
		ifr.ifr_flags |= IFF_UP;
		if (ioctl(ioctlfd, SIOCSIFFLAGS, (caddr_t)&ifr) == -1)
			fatal("%s: SIOCSIFFLAGS", ifi->name);
	}
}

void
set_user(char *user)
{
	struct passwd		*pw;

	pw = getpwnam(user);
	if (pw == NULL)
		fatalx("no such user: %s", user);

	if (chroot(pw->pw_dir) == -1)
		fatal("chroot(%s)", pw->pw_dir);
	if (chdir("/") == -1)
		fatal("chdir(\"/\")");
	if (setresgid(pw->pw_gid, pw->pw_gid, pw->pw_gid) == -1)
		fatal("setresgid");
	if (setgroups(1, &pw->pw_gid) == -1)
		fatal("setgroups");
	if (setresuid(pw->pw_uid, pw->pw_uid, pw->pw_uid) == -1)
		fatal("setresuid");

	endpwent();
}

void
get_sockets(struct interface_info *ifi)
{
	unsigned char		*newp;
	size_t			 newsize;

	ifi->udpfd = get_udp_sock(ifi->rdomain);
	ifi->bpffd = get_bpf_sock(ifi->name);

	newsize = configure_bpf_sock(ifi->bpffd);
	if (newsize > ifi->rbuf_max) {
		if ((newp = realloc(ifi->rbuf, newsize)) == NULL)
			fatal("rbuf");
		ifi->rbuf = newp;
		ifi->rbuf_max = newsize;
	}
}

int
get_routefd(int rdomain)
{
	int		routefd, rtfilter;

	if ((routefd = socket(AF_ROUTE, SOCK_RAW, AF_INET)) == -1)
		fatal("socket(AF_ROUTE, SOCK_RAW)");

	rtfilter = ROUTE_FILTER(RTM_PROPOSAL) | ROUTE_FILTER(RTM_IFINFO) |
	    ROUTE_FILTER(RTM_NEWADDR) | ROUTE_FILTER(RTM_DELADDR) |
	    ROUTE_FILTER(RTM_IFANNOUNCE) | ROUTE_FILTER(RTM_80211INFO);

	if (setsockopt(routefd, AF_ROUTE, ROUTE_MSGFILTER,
	    &rtfilter, sizeof(rtfilter)) == -1)
		fatal("setsockopt(ROUTE_MSGFILTER)");
	if (setsockopt(routefd, AF_ROUTE, ROUTE_TABLEFILTER, &rdomain,
	    sizeof(rdomain)) == -1)
		fatal("setsockopt(ROUTE_TABLEFILTER)");

	return routefd;
}

void
routefd_handler(struct interface_info *ifi, int routefd)
{
	struct rt_msghdr		*rtm;
	unsigned char			*buf = ifi->rbuf;
	unsigned char			*lim, *next;
	ssize_t				 n;

	do {
		n = read(routefd, buf, RT_BUF_SIZE);
	} while (n == -1 && errno == EINTR);
	if (n == -1) {
		log_warn("%s: routing socket", log_procname);
		return;
	}
	if (n == 0)
		fatalx("%s: routing socket closed", log_procname);

	lim = buf + n;
	for (next = buf; next < lim && quit == 0; next += rtm->rtm_msglen) {
		rtm = (struct rt_msghdr *)next;
		if (lim < next + sizeof(rtm->rtm_msglen) ||
		    lim < next + rtm->rtm_msglen)
			fatalx("%s: partial rtm in buffer", log_procname);

		if (rtm->rtm_version != RTM_VERSION)
			continue;

		rtm_dispatch(ifi, rtm);
	}
}

void
rtm_dispatch(struct interface_info *ifi, struct rt_msghdr *rtm)
{
	struct if_msghdr		*ifm;
	struct if_announcemsghdr	*ifan;
	struct ifa_msghdr		*ifam;
	struct if_ieee80211_data	*ifie;
	char				*oldssid;
	uint32_t			 oldmtu;

	switch (rtm->rtm_type) {
	case RTM_PROPOSAL:
		if (rtm->rtm_priority == RTP_PROPOSAL_SOLICIT) {
			if (quit == 0 && ifi->active != NULL)
				tell_unwind(ifi->unwind_info, ifi->flags);
			return;
		}
		if (rtm->rtm_index != ifi->index ||
		    rtm->rtm_priority != RTP_PROPOSAL_DHCLIENT)
			return;
		if ((rtm->rtm_flags & RTF_PROTO3) != 0) {
			if (rtm->rtm_seq == (int32_t)ifi->xid) {
				ifi->flags |= IFI_IN_CHARGE;
				return;
			} else if ((ifi->flags & IFI_IN_CHARGE) != 0) {
				log_debug("%s: yielding responsibility",
				    log_procname);
				quit = TERMINATE;
			}
		} else if ((rtm->rtm_flags & RTF_PROTO2) != 0) {
			release_lease(ifi); /* OK even if we sent it. */
			quit = TERMINATE;
		} else
			return; /* Ignore tell_unwind() proposals. */
		break;

	case RTM_DESYNC:
		log_warnx("%s: RTM_DESYNC", log_procname);
		break;

	case RTM_IFINFO:
		ifm = (struct if_msghdr *)rtm;
		if (ifm->ifm_index != ifi->index)
			break;
		if ((rtm->rtm_flags & RTF_UP) == 0)
			fatalx("down");

		oldmtu = ifi->mtu;
		interface_state(ifi);
		if (oldmtu == ifi->mtu)
			quit = RESTART;
		else
			log_debug("%s: MTU %u -> %u",
			    log_procname, oldmtu, ifi->mtu);
		break;

	case RTM_80211INFO:
		if (rtm->rtm_index != ifi->index)
			break;
		ifie = &((struct if_ieee80211_msghdr *)rtm)->ifim_ifie;
		if (ifi->ssid_len != ifie->ifie_nwid_len || memcmp(ifi->ssid,
		    ifie->ifie_nwid, ifie->ifie_nwid_len) != 0) {
			if (log_getverbose()) {
				oldssid = strdup(pretty_print_string(ifi->ssid,
				    ifi->ssid_len, 1));
				if (oldssid == NULL)
					fatal("oldssid");
				log_debug("%s: SSID %s -> %s", log_procname,
				    oldssid, pretty_print_string(ifie->ifie_nwid,
				    ifie->ifie_nwid_len, 1));
				free(oldssid);
			}
			quit = RESTART;
		}
		break;

	case RTM_IFANNOUNCE:
		ifan = (struct if_announcemsghdr *)rtm;
		if (ifan->ifan_what == IFAN_DEPARTURE && ifan->ifan_index ==
		    ifi->index)
			fatalx("departed");
		break;

	case RTM_NEWADDR:
	case RTM_DELADDR:
		/* Need to check if it is time to write resolv.conf. */
		ifam = (struct ifa_msghdr *)rtm;
		if (get_ifa_family((char *)ifam + ifam->ifam_hdrlen,
		    ifam->ifam_addrs) != AF_INET)
			return;
		break;

	default:
		break;
	}

	/*
	 * Responsibility for resolv.conf may have changed hands.
	 */
	if (quit == 0 && ifi->active != NULL &&
	    (ifi->flags & IFI_IN_CHARGE) != 0 &&
	    ifi->state == S_BOUND)
		write_resolv_conf();
}

int
main(int argc, char *argv[])
{
	uint8_t			 actions[DHO_END];
	struct stat		 sb;
	struct interface_info	*ifi;
	char			*ignore_list, *p;
	int			 fd, socket_fd[2];
	int			 routefd;
	int			 ch, i;

	if (isatty(STDERR_FILENO) != 0)
		log_init(1, LOG_DEBUG); /* log to stderr until daemonized */
	else
		log_init(0, LOG_DEBUG); /* can't log to stderr */

	log_setverbose(0);	/* Don't show log_debug() messages. */

	if (lstat(_PATH_DHCLIENT_CONF, &sb) == 0)
		path_dhclient_conf = _PATH_DHCLIENT_CONF;
	memset(actions, ACTION_USELEASE, sizeof(actions));

	while ((ch = getopt(argc, argv, "c:di:nrv")) != -1) {
		syslog(LOG_ALERT | LOG_CONS,
		    "dhclient will go away, so -%c option will not exist", ch);
		switch (ch) {
		case 'c':
			if (strlen(optarg) == 0)
				path_dhclient_conf = NULL;
			else if (lstat(optarg, &sb) == 0)
				path_dhclient_conf = optarg;
			else
				fatal("lstat(%s)", optarg);
			break;
		case 'd':
			cmd_opts |= OPT_FOREGROUND;
			break;
		case 'i':
			syslog(LOG_ALERT | LOG_CONS,
			    "dhclient will go away, for -i learn dhcpleased.conf");
			if (strlen(optarg) == 0)
				break;
			ignore_list = strdup(optarg);
			if (ignore_list == NULL)
				fatal("ignore_list");
			for (p = strsep(&ignore_list, ", "); p != NULL;
			     p = strsep(&ignore_list, ", ")) {
				if (*p == '\0')
					continue;
				i = name_to_code(p);
				if (i == DHO_END)
					fatalx("invalid option name: '%s'", p);
				actions[i] = ACTION_IGNORE;
			}
			free(ignore_list);
			break;
		case 'n':
			cmd_opts |= OPT_NOACTION;
			break;
		case 'r':
			cmd_opts |= OPT_RELEASE;
			break;
		case 'v':
			cmd_opts |= OPT_VERBOSE;
			break;
		default:
			usage();
		}
	}

	argc -= optind;
	argv += optind;

	if (argc != 1)
		usage();

	syslog(LOG_ALERT | LOG_CONS,
	    "dhclient will go away, stop using it");

	execl("/sbin/ifconfig", "ifconfig", argv[0], "inet", "autoconf", NULL);

	if ((cmd_opts & (OPT_FOREGROUND | OPT_NOACTION)) != 0)
		cmd_opts |= OPT_VERBOSE;

	if ((cmd_opts & OPT_VERBOSE) != 0)
		log_setverbose(1);	/* Show log_debug() messages. */

	ifi = initialize_interface(argv[0], cmd_opts & OPT_NOACTION);

	log_procname = strdup(ifi->name);
	if (log_procname == NULL)
		fatal("log_procname");
	setproctitle("%s", log_procname);
	log_procinit(log_procname);

	tzset();

	if (setrtable(ifi->rdomain) == -1)
		fatal("setrtable(%u)", ifi->rdomain);

	if ((cmd_opts & OPT_RELEASE) != 0) {
		if ((cmd_opts & OPT_NOACTION) == 0)
			propose_release(ifi);
		exit(0);
	}

	signal(SIGPIPE, SIG_IGN);	/* Don't wait for go_daemon()! */

	if (socketpair(AF_UNIX, SOCK_STREAM | SOCK_NONBLOCK | SOCK_CLOEXEC, 0,
	    socket_fd) == -1)
		fatal("socketpair");

	if ((nullfd = open(_PATH_DEVNULL, O_RDWR)) == -1)
		fatal("open(%s)", _PATH_DEVNULL);

	fork_privchld(ifi, socket_fd[0], socket_fd[1]);

	close(socket_fd[0]);
	if ((unpriv_ibuf = malloc(sizeof(*unpriv_ibuf))) == NULL)
		fatal("unpriv_ibuf");
	imsg_init(unpriv_ibuf, socket_fd[1]);

	read_conf(ifi->name, actions, &ifi->hw_address);
	if ((cmd_opts & OPT_NOACTION) != 0)
		return 0;

	if (asprintf(&path_lease_db, "%s.%s", _PATH_LEASE_DB, ifi->name) == -1)
		fatal("path_lease_db");

	routefd = get_routefd(ifi->rdomain);
	fd = take_charge(ifi, routefd, path_lease_db);	/* Kill other dhclients. */
	if (autoconf(ifi)) {
		/* dhcpleased has been notified to request a new lease. */
		return 0;
	}
	if (fd != -1)
		read_lease_db(&ifi->lease_db);

	if ((leaseFile = fopen(path_lease_db, "w")) == NULL)
		log_warn("%s: fopen(%s)", log_procname, path_lease_db);
	write_lease_db(ifi);

	set_user("_dhcp");

	if ((cmd_opts & OPT_FOREGROUND) == 0) {
		if (pledge("stdio inet dns route proc", NULL) == -1)
			fatal("pledge");
	} else {
		if (pledge("stdio inet dns route", NULL) == -1)
			fatal("pledge");
	}

	tick_msg("link", LINK_STATE_IS_UP(ifi->link_state) ? TICK_SUCCESS :
	    TICK_WAIT);
	quit = RESTART;
	dispatch(ifi, routefd);

	return 0;
}

void
usage(void)
{
	extern char	*__progname;

	fprintf(stderr,
	    "usage: %s [-dnrv] [-c file] [-i options] "
	    "interface\n", __progname);
	exit(1);
}

void
state_preboot(struct interface_info *ifi)
{
	interface_state(ifi);
	if (quit != 0)
		return;

	if (LINK_STATE_IS_UP(ifi->link_state)) {
		tick_msg("link", TICK_SUCCESS);
		ifi->state = S_REBOOTING;
		state_reboot(ifi);
	} else {
		tick_msg("link", TICK_WAIT);
		set_timeout(ifi, 1, state_preboot);
	}
}

/*
 * Called when the interface link becomes active.
 */
void
state_reboot(struct interface_info *ifi)
{
	const struct timespec	 reboot_intvl = {config->reboot_interval, 0};
	struct client_lease	*lease;

	cancel_timeout(ifi);

	/*
	 * If there is no recorded lease or the lease is BOOTP then
	 * go straight to INIT and try to DISCOVER a new lease.
	 */
	ifi->active = get_recorded_lease(ifi);
	if (ifi->active == NULL || BOOTP_LEASE(ifi->active)) {
		ifi->state = S_INIT;
		state_init(ifi);
		return;
	}
	lease = apply_defaults(ifi->active);
	get_lease_timeouts(ifi, lease);
	free_client_lease(lease);

	ifi->xid = arc4random();
	make_request(ifi, ifi->active);

	ifi->destination.s_addr = INADDR_BROADCAST;
	clock_gettime(CLOCK_MONOTONIC, &ifi->first_sending);
	timespecadd(&ifi->first_sending, &reboot_intvl, &ifi->reboot_timeout);
	ifi->interval = 0;

	send_request(ifi);
}

/*
 * Called when a lease has completely expired and we've been unable to
 * renew it.
 */
void
state_init(struct interface_info *ifi)
{
	const struct timespec	offer_intvl = {config->offer_interval, 0};

	ifi->xid = arc4random();
	make_discover(ifi, ifi->active);

	ifi->destination.s_addr = INADDR_BROADCAST;
	ifi->state = S_SELECTING;
	clock_gettime(CLOCK_MONOTONIC, &ifi->first_sending);
	timespecadd(&ifi->first_sending, &offer_intvl, &ifi->offer_timeout);
	ifi->select_timeout = ifi->offer_timeout;
	ifi->interval = 0;

	send_discover(ifi);
}

/*
 * Called when one or more DHCPOFFER packets have been received and a
 * configurable period of time has passed.
 */
void
state_selecting(struct interface_info *ifi)
{
	cancel_timeout(ifi);

	if (ifi->offer == NULL) {
		state_panic(ifi);
		return;
	}

	ifi->state = S_REQUESTING;

	/* If it was a BOOTREPLY, we can just take the lease right now. */
	if (BOOTP_LEASE(ifi->offer)) {
		bind_lease(ifi);
		return;
	}

	ifi->destination.s_addr = INADDR_BROADCAST;
	clock_gettime(CLOCK_MONOTONIC, &ifi->first_sending);
	ifi->interval = 0;

	/*
	 * Make a DHCPREQUEST packet from the lease we picked. Keep
	 * the current xid, as all offers should have had the same
	 * one.
	 */
	make_request(ifi, ifi->offer);

	/* Toss the lease we picked - we'll get it back in a DHCPACK. */
	free_client_lease(ifi->offer);
	ifi->offer = NULL;
	free(ifi->offer_src);
	ifi->offer_src = NULL;

	send_request(ifi);
}

void
dhcpoffer(struct interface_info *ifi, struct option_data *options,
    const char *src)
{
	if (ifi->state != S_SELECTING) {
		log_debug("%s: unexpected DHCPOFFER from %s - state #%d",
		    log_procname, src, ifi->state);
		return;
	}

	log_debug("%s: DHCPOFFER from %s", log_procname, src);
	process_offer(ifi, options, src);
}

void
bootreply(struct interface_info *ifi, struct option_data *options,
    const char *src)
{
	if (ifi->state != S_SELECTING) {
		log_debug("%s: unexpected BOOTREPLY from %s - state #%d",
		    log_procname, src, ifi->state);
		return;
	}

	log_debug("%s: BOOTREPLY from %s", log_procname, src);
	process_offer(ifi, options, src);
}

void
process_offer(struct interface_info *ifi, struct option_data *options,
    const char *src)
{
	const struct timespec	 select_intvl = {config->select_interval, 0};
	struct timespec		 now;
	struct client_lease	*lease;

	clock_gettime(CLOCK_MONOTONIC, &now);

	lease = packet_to_lease(ifi, options);
	if (lease != NULL) {
		if (ifi->offer == NULL) {
			ifi->offer = lease;
			free(ifi->offer_src);
			ifi->offer_src = strdup(src);	/* NULL is OK */
			timespecadd(&now, &select_intvl, &ifi->select_timeout);
			if (timespeccmp(&ifi->select_timeout,
			    &ifi->offer_timeout, >))
				ifi->select_timeout = ifi->offer_timeout;
		} else if (lease->address.s_addr ==
		    ifi->offer->address.s_addr) {
			/* Decline duplicate offers. */
		} else if (lease->address.s_addr ==
		    ifi->requested_address.s_addr) {
			free_client_lease(ifi->offer);
			ifi->offer = lease;
			free(ifi->offer_src);
			ifi->offer_src = strdup(src);	/* NULL is OK */
		}

		if (ifi->offer != lease) {
			make_decline(ifi, lease);
			send_decline(ifi);
			free_client_lease(lease);
		} else if (ifi->offer->address.s_addr ==
		    ifi->requested_address.s_addr) {
			ifi->select_timeout = now;
		}
	}

	if (timespeccmp(&now, &ifi->select_timeout, >=))
		state_selecting(ifi);
	else {
		ifi->timeout = ifi->select_timeout;
		ifi->timeout_func = state_selecting;
	}
}

void
dhcpack(struct interface_info *ifi, struct option_data *options,
    const char *src)
{
	struct client_lease	*lease;

	if (ifi->state != S_REBOOTING &&
	    ifi->state != S_REQUESTING &&
	    ifi->state != S_RENEWING) {
		log_debug("%s: unexpected DHCPACK from %s - state #%d",
		    log_procname, src, ifi->state);
		return;
	}

	log_debug("%s: DHCPACK", log_procname);

	lease = packet_to_lease(ifi, options);
	if (lease == NULL) {
		ifi->state = S_INIT;
		state_init(ifi);
		return;
	}

	ifi->offer = lease;
	ifi->offer_src = strdup(src);	/* NULL is OK */
	memcpy(ifi->offer->ssid, ifi->ssid, sizeof(ifi->offer->ssid));
	ifi->offer->ssid_len = ifi->ssid_len;

	/* Stop resending DHCPREQUEST. */
	cancel_timeout(ifi);

	bind_lease(ifi);
}

void
dhcpnak(struct interface_info *ifi, const char *src)
{
	struct client_lease		*ll, *pl;

	if (ifi->state != S_REBOOTING &&
	    ifi->state != S_REQUESTING &&
	    ifi->state != S_RENEWING) {
		log_debug("%s: unexpected DHCPNAK from %s - state #%d",
		    log_procname, src, ifi->state);
		return;
	}

	log_debug("%s: DHCPNAK", log_procname);

	/* Remove the NAK'd address from the database. */
	TAILQ_FOREACH_SAFE(ll, &ifi->lease_db, next, pl) {
		if (ifi->ssid_len == ll->ssid_len &&
		    memcmp(ifi->ssid, ll->ssid, ll->ssid_len) == 0 &&
		    ll->address.s_addr == ifi->requested_address.s_addr) {
			if (ll == ifi->active) {
				tell_unwind(NULL, ifi->flags);
				free(ifi->unwind_info);
				ifi->unwind_info = NULL;
				revoke_proposal(ifi->configured);
				free(ifi->configured);
				ifi->configured = NULL;
				ifi->active = NULL;
			}
			TAILQ_REMOVE(&ifi->lease_db, ll, next);
			free_client_lease(ll);
			write_lease_db(ifi);
		}
	}

	/* Stop sending DHCPREQUEST packets. */
	cancel_timeout(ifi);

	ifi->state = S_INIT;
	state_init(ifi);
}

void
bind_lease(struct interface_info *ifi)
{
	struct timespec		 now;
	struct client_lease	*lease, *pl, *ll;
	struct proposal		*effective_proposal = NULL;
	struct unwind_info	*unwind_info;
	char			*msg = NULL;
	int			 rslt, seen;

	tick_msg("lease", TICK_SUCCESS);
	clock_gettime(CLOCK_MONOTONIC, &now);

	lease = apply_defaults(ifi->offer);
	get_lease_timeouts(ifi, lease);

	/* Replace the old active lease with the accepted offer. */
	ifi->active = ifi->offer;
	ifi->offer = NULL;

	/*
	 * Supply unwind with updated info.
	 */
	unwind_info = lease_as_unwind_info(ifi->active);
	if (ifi->unwind_info == NULL && unwind_info != NULL) {
		ifi->unwind_info = unwind_info;
		tell_unwind(ifi->unwind_info, ifi->flags);
	} else if (ifi->unwind_info != NULL && unwind_info == NULL) {
		tell_unwind(NULL, ifi->flags);
		free(ifi->unwind_info);
		ifi->unwind_info = NULL;
	} else if (ifi->unwind_info != NULL && unwind_info != NULL) {
		if (memcmp(ifi->unwind_info, unwind_info,
		    sizeof(*ifi->unwind_info)) != 0) {
			tell_unwind(NULL, ifi->flags);
			free(ifi->unwind_info);
			ifi->unwind_info = unwind_info;
			tell_unwind(ifi->unwind_info, ifi->flags);
		}
	}

	effective_proposal = lease_as_proposal(lease);
	if (ifi->configured != NULL) {
		if (memcmp(ifi->configured, effective_proposal,
		    sizeof(*ifi->configured)) == 0)
			goto newlease;
	}
	free(ifi->configured);
	ifi->configured = effective_proposal;
	effective_proposal = NULL;

	propose(ifi->configured);
	rslt = asprintf(&msg, "%s lease accepted from %s",
	    inet_ntoa(ifi->active->address),
	    (ifi->offer_src == NULL) ? "<unknown>" : ifi->offer_src);
	if (rslt == -1)
		fatal("bind msg");

newlease:
	seen = 0;
	TAILQ_FOREACH_SAFE(ll, &ifi->lease_db, next, pl) {
		if (ifi->ssid_len != ll->ssid_len ||
		    memcmp(ifi->ssid, ll->ssid, ll->ssid_len) != 0)
			continue;
		if (ifi->active == ll)
			seen = 1;
		else if (ll->address.s_addr == ifi->active->address.s_addr) {
			TAILQ_REMOVE(&ifi->lease_db, ll, next);
			free_client_lease(ll);
		}
	}
	if (seen == 0) {
		if (ifi->active->epoch == 0)
			time(&ifi->active->epoch);
		TAILQ_INSERT_HEAD(&ifi->lease_db, ifi->active,  next);
	}

	/*
	 * Write out updated information before going daemon.
	 *
	 * Some scripts (e.g. the installer in autoinstall mode) assume that
	 * the bind process is complete and all related information is in
	 * place when dhclient(8) goes daemon.
	 */
	write_lease_db(ifi);

	free_client_lease(lease);
	free(effective_proposal);
	free(ifi->offer_src);
	ifi->offer_src = NULL;

	if (msg != NULL) {
		if ((cmd_opts & OPT_FOREGROUND) != 0) {
			/* log msg on console only. */
			;
		} else if (isatty(STDERR_FILENO) != 0) {
			/*
			 * log msg to console and then go_daemon() so it is
			 * logged again, this time to /var/log/daemon.
			 */
			log_info("%s: %s", log_procname, msg);
			go_daemon();
		}
		log_info("%s: %s", log_procname, msg);
		free(msg);
	}

	ifi->state = S_BOUND;
	go_daemon();

	/*
	 * Set timeout to start the renewal process.
	 *
	 * If the renewal time is in the past, the lease is from the
	 * leaseDB. Rather than immediately trying to contact a server,
	 * pause the configured time between attempts.
	 */
	if (timespeccmp(&now, &ifi->renew, >=))
		set_timeout(ifi, config->retry_interval, state_bound);
	else {
		ifi->timeout = ifi->renew;
		ifi->timeout_func = state_bound;
	}
}

/*
 * 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(struct interface_info *ifi)
{
	struct option_data	*opt;
	struct in_addr		*dest;

	ifi->xid = arc4random();
	make_request(ifi, ifi->active);

	dest = &ifi->destination;
	opt = &ifi->active->options[DHO_DHCP_SERVER_IDENTIFIER];

	if (opt->len == sizeof(*dest))
		dest->s_addr = ((struct in_addr *)opt->data)->s_addr;
	else
		dest->s_addr = INADDR_BROADCAST;

	clock_gettime(CLOCK_MONOTONIC, &ifi->first_sending);
	ifi->interval = 0;
	ifi->state = S_RENEWING;

	send_request(ifi);
}

int
addressinuse(char *name, struct in_addr address, char *ifname)
{
	struct ifaddrs		*ifap, *ifa;
	struct sockaddr_in	*sin;
	int			 used = 0;

	if (getifaddrs(&ifap) != 0) {
		log_warn("%s: getifaddrs", log_procname);
		return 0;
	}

	for (ifa = ifap; ifa; ifa = ifa->ifa_next) {
		if (ifa->ifa_addr == NULL ||
		    ifa->ifa_addr->sa_family != AF_INET)
			continue;

		sin = (struct sockaddr_in *)ifa->ifa_addr;
		if (memcmp(&address, &sin->sin_addr, sizeof(address)) == 0) {
			strlcpy(ifname, ifa->ifa_name, IF_NAMESIZE);
			used = 1;
			if (strncmp(ifname, name, IF_NAMESIZE) != 0)
				break;
		}
	}

	freeifaddrs(ifap);
	return used;
}

/*
 * Allocate a client_lease structure and initialize it from the
 * parameters in the received packet.
 *
 * Return NULL and decline the lease if a valid lease cannot be
 * constructed.
 */
struct client_lease *
packet_to_lease(struct interface_info *ifi, struct option_data *options)
{
	char			 ifname[IF_NAMESIZE];
	struct dhcp_packet	*packet = &ifi->recv_packet;
	struct client_lease	*lease;
	char			*pretty, *name;
	int			 i;

	lease = calloc(1, sizeof(*lease));
	if (lease == NULL) {
		log_warn("%s: lease", log_procname);
		return NULL;	/* Can't even DECLINE. */
	}

	/*  Copy the lease addresses. */
	lease->address.s_addr = packet->yiaddr.s_addr;
	lease->next_server.s_addr = packet->siaddr.s_addr;

	/* Copy the lease options. */
	for (i = 0; i < DHO_COUNT; i++) {
		if (options[i].len == 0)
			continue;
		name = code_to_name(i);
		if (i == DHO_DOMAIN_SEARCH) {
			/* Replace RFC 1035 data with a string. */
			pretty = rfc1035_as_string(options[i].data,
			    options[i].len);
			free(options[i].data);
			options[i].data = strdup(pretty);
			if (options[i].data == NULL)
				fatal("RFC1035 string");
			options[i].len = strlen(options[i].data);
		} else
			pretty = pretty_print_option(i, &options[i], 0);
		if (strlen(pretty) == 0)
			continue;
		switch (i) {
		case DHO_DOMAIN_SEARCH:
		case DHO_DOMAIN_NAME:
			/*
			 * Allow deviant but historically blessed
			 * practice of supplying multiple domain names
			 * with DHO_DOMAIN_NAME. Thus allowing multiple
			 * entries in the resolv.conf 'search' statement.
			 */
			if (res_hnok_list(pretty) == 0) {
				log_debug("%s: invalid host name in %s",
				    log_procname, name);
				continue;
			}
			break;
		case DHO_HOST_NAME:
		case DHO_NIS_DOMAIN:
			if (res_hnok(pretty) == 0) {
				log_debug("%s: invalid host name in %s",
				    log_procname, name);
				continue;
			}
			break;
		default:
			break;
		}
		lease->options[i] = options[i];
		options[i].data = NULL;
		options[i].len = 0;
	}

	/*
	 * If this lease doesn't supply a required parameter, decline it.
	 */
	for (i = 0; i < config->required_option_count; i++) {
		if (lease->options[config->required_options[i]].len == 0) {
			name = code_to_name(config->required_options[i]);
			log_warnx("%s: %s required but missing", log_procname,
			    name);
			goto decline;
		}
	}

	/*
	 * If this lease is trying to sell us an address we are already
	 * using, decline it.
	 */
	memset(ifname, 0, sizeof(ifname));
	if (addressinuse(ifi->name, lease->address, ifname) != 0 &&
	    strncmp(ifname, ifi->name, IF_NAMESIZE) != 0) {
		log_warnx("%s: %s already configured on %s", log_procname,
		    inet_ntoa(lease->address), ifname);
		goto decline;
	}

	/* If the server name was filled out, copy it. */
	if ((lease->options[DHO_DHCP_OPTION_OVERLOAD].len == 0 ||
	    (lease->options[DHO_DHCP_OPTION_OVERLOAD].data[0] & 2) == 0) &&
	    packet->sname[0]) {
		lease->server_name = calloc(1, DHCP_SNAME_LEN + 1);
		if (lease->server_name == NULL) {
			log_warn("%s: SNAME", log_procname);
			goto decline;
		}
		memcpy(lease->server_name, packet->sname, DHCP_SNAME_LEN);
		if (res_hnok(lease->server_name) == 0) {
			log_debug("%s: invalid host name in SNAME ignored",
			    log_procname);
			free(lease->server_name);
			lease->server_name = NULL;
		}
	}

	/* If the file name was filled out, copy it. */
	if ((lease->options[DHO_DHCP_OPTION_OVERLOAD].len == 0 ||
	    (lease->options[DHO_DHCP_OPTION_OVERLOAD].data[0] & 1) == 0) &&
	    packet->file[0]) {
		/* Don't count on the NUL terminator. */
		lease->filename = malloc(DHCP_FILE_LEN + 1);
		if (lease->filename == NULL) {
			log_warn("%s: filename", log_procname);
			goto decline;
		}
		memcpy(lease->filename, packet->file, DHCP_FILE_LEN);
		lease->filename[DHCP_FILE_LEN] = '\0';
	}

	/*
	 * Record the client identifier used to obtain the lease.  We already
	 * checked that the packet client identifier is absent (RFC 2131) or
	 * matches what we sent (RFC 6842),
	 */
	i = DHO_DHCP_CLIENT_IDENTIFIER;
	if (lease->options[i].len == 0 && config->send_options[i].len != 0) {
		lease->options[i].len = config->send_options[i].len;
		lease->options[i].data = malloc(lease->options[i].len);
		if (lease->options[i].data == NULL)
			fatal("lease client-identifier");
		memcpy(lease->options[i].data, config->send_options[i].data,
		    lease->options[i].len);
	}

	time(&lease->epoch);
	return lease;

decline:
	make_decline(ifi, lease);
	send_decline(ifi);
	free_client_lease(lease);
	return NULL;
}

void
set_interval(struct interface_info *ifi, struct timespec *now)
{
	struct timespec		interval;

	if (timespeccmp(now, &ifi->timeout, >))
		ifi->interval = 1;
	else {
		timespecsub(&ifi->timeout, now, &interval);
		if (interval.tv_sec == 0 || interval.tv_nsec > 500000000LL)
			interval.tv_sec++;
		ifi->interval = interval.tv_sec;
	}
}

void
set_resend_timeout(struct interface_info *ifi, struct timespec *now,
    void (*where)(struct interface_info *))
{
	const struct timespec	reboot_intvl = {config->reboot_interval, 0};
	const struct timespec	initial_intvl = {config->initial_interval, 0};
	const struct timespec	cutoff_intvl = {config->backoff_cutoff, 0};
	const struct timespec	onesecond = {1, 0};
	struct timespec		interval, when;

	if (timespeccmp(now, &ifi->link_timeout, <))
		interval = onesecond;
	else if (ifi->interval == 0) {
		if (ifi->state == S_REBOOTING)
			interval = reboot_intvl;
		else
			interval = initial_intvl;
	} else {
		timespecclear(&interval);
		interval.tv_sec = ifi->interval + arc4random_uniform(2 *
		    ifi->interval);
	}
	if (timespeccmp(&interval, &onesecond, <))
		interval = onesecond;
	else if (timespeccmp(&interval, &cutoff_intvl, >))
		interval = cutoff_intvl;

	timespecadd(now, &interval, &when);
	switch (ifi->state) {
	case S_REBOOTING:
	case S_RENEWING:
		if (timespeccmp(&when, &ifi->expiry, >))
			when = ifi->expiry;
		break;
	case S_SELECTING:
		if (timespeccmp(&when, &ifi->select_timeout, >))
			when = ifi->select_timeout;
		break;
	case S_REQUESTING:
		if (timespeccmp(&when, &ifi->offer_timeout, >))
			when = ifi->offer_timeout;
		break;
	default:
		break;
	}

	ifi->timeout = when;
	ifi->timeout_func = where;
}

void
set_secs(struct interface_info *ifi, struct timespec *now)
{
	struct timespec interval;

	if (ifi->state != S_REQUESTING) {
		/* Update the number of seconds since we started sending. */
		timespecsub(now, &ifi->first_sending, &interval);
		if (interval.tv_nsec > 500000000LL)
			interval.tv_sec++;
		if (interval.tv_sec > UINT16_MAX)
			ifi->secs = UINT16_MAX;
		else
			ifi->secs = interval.tv_sec;
	}

	ifi->sent_packet.secs = htons(ifi->secs);
}

/*
 * 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(struct interface_info *ifi)
{
	struct timespec		 now;
	ssize_t			 rslt;

	clock_gettime(CLOCK_MONOTONIC, &now);
	if (timespeccmp(&now, &ifi->offer_timeout, >=)) {
		state_panic(ifi);
		return;
	}

	set_resend_timeout(ifi, &now, send_discover);
	set_interval(ifi, &now);
	set_secs(ifi, &now);

	rslt = send_packet(ifi, inaddr_any, inaddr_broadcast, "DHCPDISCOVER");
	if (rslt != -1)
		log_debug("%s: DHCPDISCOVER %s", log_procname,
		    (ifi->requested_address.s_addr == INADDR_ANY) ? "" :
		    inet_ntoa(ifi->requested_address));

	tick_msg("lease", TICK_WAIT);
}

/*
 * 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.
 *
 * If LINK_STATE_UNKNOWN, do NOT use recorded leases.
 */
void
state_panic(struct interface_info *ifi)
{
	log_debug("%s: no acceptable DHCPOFFERS received", log_procname);

	if (ifi->link_state >= LINK_STATE_UP) {
		ifi->offer = get_recorded_lease(ifi);
		if (ifi->offer != NULL) {
			ifi->state = S_REQUESTING;
			ifi->offer_src = strdup(path_lease_db); /* NULL is OK. */
			bind_lease(ifi);
			return;
		}
	}

	/*
	 * No leases were available, or what was available didn't work
	 */
	log_debug("%s: no working leases in persistent database - sleeping",
	    log_procname);
	ifi->state = S_INIT;
	set_timeout(ifi, config->retry_interval, state_init);
	tick_msg("lease", TICK_DAEMON);
}

void
send_request(struct interface_info *ifi)
{
	struct sockaddr_in	 destination;
	struct in_addr		 from;
	struct timespec		 now;
	ssize_t			 rslt;
	char			*addr;

	cancel_timeout(ifi);
	clock_gettime(CLOCK_MONOTONIC, &now);

	switch (ifi->state) {
	case S_REBOOTING:
		if (timespeccmp(&now, &ifi->reboot_timeout, >=))
			ifi->state = S_INIT;
		else {
			destination.sin_addr.s_addr = INADDR_BROADCAST;
			if (ifi->active == NULL)
				from.s_addr = INADDR_ANY;
			else
				from.s_addr = ifi->active->address.s_addr;
		}
		break;
	case S_RENEWING:
		if (timespeccmp(&now, &ifi->expiry, >=))
			ifi->state = S_INIT;
		else {
			if (timespeccmp(&now, &ifi->rebind, >=))
				destination.sin_addr.s_addr = INADDR_BROADCAST;
			else
				destination.sin_addr.s_addr = ifi->destination.s_addr;
			if (ifi->active == NULL)
				from.s_addr = INADDR_ANY;
			else
				from.s_addr = ifi->active->address.s_addr;
		}
		break;
	case S_REQUESTING:
		if (timespeccmp(&now, &ifi->offer_timeout, >=))
			ifi->state = S_INIT;
		else {
			destination.sin_addr.s_addr = INADDR_BROADCAST;
			from.s_addr = INADDR_ANY;
		}
		break;
	default:
		ifi->state = S_INIT;
		break;
	}

	if (ifi->state == S_INIT) {
		/* Something has gone wrong. Start over. */
		state_init(ifi);
		return;
	}

	set_resend_timeout(ifi, &now, send_request);
	set_interval(ifi, &now);
	set_secs(ifi, &now);

	rslt = send_packet(ifi, from, destination.sin_addr, "DHCPREQUEST");
	if (rslt != -1 && log_getverbose()) {
		addr = strdup(inet_ntoa(ifi->requested_address));
		if (addr == NULL)
			fatal("strdup(ifi->requested_address)");
		if (destination.sin_addr.s_addr == INADDR_BROADCAST)
			log_debug("%s: DHCPREQUEST %s", log_procname, addr);
		else
			log_debug("%s: DHCPREQUEST %s from %s", log_procname,
			    addr, inet_ntoa(destination.sin_addr));
		free(addr);
	}

	tick_msg("lease", TICK_WAIT);
}

void
send_decline(struct interface_info *ifi)
{
	ssize_t		rslt;

	rslt = send_packet(ifi, inaddr_any, inaddr_broadcast, "DHCPDECLINE");
	if (rslt != -1)
		log_debug("%s: DHCPDECLINE", log_procname);
}

void
send_release(struct interface_info *ifi)
{
	ssize_t		rslt;

	rslt = send_packet(ifi, ifi->configured->address, ifi->destination,
	    "DHCPRELEASE");
	if (rslt != -1)
		log_debug("%s: DHCPRELEASE", log_procname);
}

void
make_discover(struct interface_info *ifi, struct client_lease *lease)
{
	struct option_data	 options[DHO_COUNT];
	struct dhcp_packet	*packet = &ifi->sent_packet;
	unsigned char		 discover = DHCPDISCOVER;
	int			 i;

	memset(options, 0, sizeof(options));
	memset(packet, 0, sizeof(*packet));

	/* Set DHCP_MESSAGE_TYPE to DHCPDISCOVER */
	i = DHO_DHCP_MESSAGE_TYPE;
	options[i].data = &discover;
	options[i].len = sizeof(discover);

	/* Request the options we want */
	i  = DHO_DHCP_PARAMETER_REQUEST_LIST;
	options[i].data = config->requested_options;
	options[i].len = config->requested_option_count;

	/* If we had an address, try to get it again. */
	if (lease != NULL) {
		ifi->requested_address = lease->address;
		i = DHO_DHCP_REQUESTED_ADDRESS;
		options[i].data = (char *)&lease->address;
		options[i].len = sizeof(lease->address);
	} else
		ifi->requested_address.s_addr = INADDR_ANY;

	/* Send any options requested in the config file. */
	for (i = 0; i < DHO_COUNT; i++)
		if (options[i].data == NULL &&
		    config->send_options[i].data != NULL) {
			options[i].data = config->send_options[i].data;
			options[i].len = config->send_options[i].len;
		}

	/*
	 * Set up the option buffer to fit in a 576-byte UDP packet, which
	 * RFC 791 says is the largest packet that *MUST* be accepted
	 * by any host.
	 */
	i = pack_options(ifi->sent_packet.options, 576 - DHCP_FIXED_LEN,
	    options);
	if (i == -1 || packet->options[i] != DHO_END)
		fatalx("options do not fit in DHCPDISCOVER packet");
	ifi->sent_packet_length = DHCP_FIXED_NON_UDP+i+1;
	if (ifi->sent_packet_length < BOOTP_MIN_LEN)
		ifi->sent_packet_length = BOOTP_MIN_LEN;

	packet->op = BOOTREQUEST;
	packet->htype = HTYPE_ETHER;
	packet->hlen = ETHER_ADDR_LEN;
	packet->hops = 0;
	packet->xid = ifi->xid;
	packet->secs = 0; /* filled in by send_discover. */
	packet->flags = 0;

	packet->ciaddr.s_addr = INADDR_ANY;
	packet->yiaddr.s_addr = INADDR_ANY;
	packet->siaddr.s_addr = INADDR_ANY;
	packet->giaddr.s_addr = INADDR_ANY;

	memcpy(&packet->chaddr, ifi->hw_address.ether_addr_octet,
	    ETHER_ADDR_LEN);
}

void
make_request(struct interface_info *ifi, struct client_lease *lease)
{
	struct option_data	 options[DHO_COUNT];
	struct dhcp_packet	*packet = &ifi->sent_packet;
	unsigned char		 request = DHCPREQUEST;
	int			 i;

	memset(options, 0, sizeof(options));
	memset(packet, 0, sizeof(*packet));

	/* Set DHCP_MESSAGE_TYPE to DHCPREQUEST */
	i = DHO_DHCP_MESSAGE_TYPE;
	options[i].data = &request;
	options[i].len = sizeof(request);

	/* Request the options we want */
	i = DHO_DHCP_PARAMETER_REQUEST_LIST;
	options[i].data = config->requested_options;
	options[i].len = config->requested_option_count;

	/*
	 * If we are requesting an address that hasn't yet been assigned
	 * to us, use the DHCP Requested Address option.
	 */
	if (ifi->state == S_REQUESTING) {
		/* Send back the server identifier. */
		i = DHO_DHCP_SERVER_IDENTIFIER;
		options[i].data = lease->options[i].data;
		options[i].len = lease->options[i].len;
	}
	if (ifi->state == S_REQUESTING ||
	    ifi->state == S_REBOOTING) {
		i = DHO_DHCP_REQUESTED_ADDRESS;
		options[i].data = (char *)&lease->address.s_addr;
		options[i].len = sizeof(lease->address.s_addr);
	}

	/* Send any options requested in the config file. */
	for (i = 0; i < DHO_COUNT; i++)
		if (options[i].data == NULL &&
		    config->send_options[i].data != NULL) {
			options[i].data = config->send_options[i].data;
			options[i].len = config->send_options[i].len;
		}

	/*
	 * Set up the option buffer to fit in a 576-byte UDP packet, which
	 * RFC 791 says is the largest packet that *MUST* be accepted
	 * by any host.
	 */
	i = pack_options(ifi->sent_packet.options, 576 - DHCP_FIXED_LEN,
	    options);
	if (i == -1 || packet->options[i] != DHO_END)
		fatalx("options do not fit in DHCPREQUEST packet");
	ifi->sent_packet_length = DHCP_FIXED_NON_UDP+i+1;
	if (ifi->sent_packet_length < BOOTP_MIN_LEN)
		ifi->sent_packet_length = BOOTP_MIN_LEN;

	packet->op = BOOTREQUEST;
	packet->htype = HTYPE_ETHER;
	packet->hlen = ETHER_ADDR_LEN;
	packet->hops = 0;
	packet->xid = ifi->xid;
	packet->secs = 0; /* Filled in by send_request. */
	packet->flags = 0;

	/*
	 * If we own the address we're requesting, put it in ciaddr. Otherwise
	 * set ciaddr to zero.
	 */
	ifi->requested_address = lease->address;
	if (ifi->state == S_BOUND ||
	    ifi->state == S_RENEWING)
		packet->ciaddr.s_addr = lease->address.s_addr;
	else
		packet->ciaddr.s_addr = INADDR_ANY;

	packet->yiaddr.s_addr = INADDR_ANY;
	packet->siaddr.s_addr = INADDR_ANY;
	packet->giaddr.s_addr = INADDR_ANY;

	memcpy(&packet->chaddr, ifi->hw_address.ether_addr_octet,
	    ETHER_ADDR_LEN);
}

void
make_decline(struct interface_info *ifi, struct client_lease *lease)
{
	struct option_data	 options[DHO_COUNT];
	struct dhcp_packet	*packet = &ifi->sent_packet;
	unsigned char		 decline = DHCPDECLINE;
	int			 i;

	memset(options, 0, sizeof(options));
	memset(packet, 0, sizeof(*packet));

	/* Set DHCP_MESSAGE_TYPE to DHCPDECLINE */
	i = DHO_DHCP_MESSAGE_TYPE;
	options[i].data = &decline;
	options[i].len = sizeof(decline);

	/* Send back the server identifier. */
	i = DHO_DHCP_SERVER_IDENTIFIER;
	options[i].data = lease->options[i].data;
	options[i].len = lease->options[i].len;

	/* Send back the address we're declining. */
	i = DHO_DHCP_REQUESTED_ADDRESS;
	options[i].data = (char *)&lease->address.s_addr;
	options[i].len = sizeof(lease->address.s_addr);

	/* Send the uid if the user supplied one. */
	i = DHO_DHCP_CLIENT_IDENTIFIER;
	if (config->send_options[i].len != 0) {
		options[i].data = config->send_options[i].data;
		options[i].len = config->send_options[i].len;
	}

	/*
	 * Set up the option buffer to fit in a 576-byte UDP packet, which
	 * RFC 791 says is the largest packet that *MUST* be accepted
	 * by any host.
	 */
	i = pack_options(ifi->sent_packet.options, 576 - DHCP_FIXED_LEN,
	    options);
	if (i == -1 || packet->options[i] != DHO_END)
		fatalx("options do not fit in DHCPDECLINE packet");
	ifi->sent_packet_length = DHCP_FIXED_NON_UDP+i+1;
	if (ifi->sent_packet_length < BOOTP_MIN_LEN)
		ifi->sent_packet_length = BOOTP_MIN_LEN;

	packet->op = BOOTREQUEST;
	packet->htype = HTYPE_ETHER;
	packet->hlen = ETHER_ADDR_LEN;
	packet->hops = 0;
	packet->xid = ifi->xid;
	packet->secs = 0;
	packet->flags = 0;

	/* ciaddr must always be zero. */
	packet->ciaddr.s_addr = INADDR_ANY;
	packet->yiaddr.s_addr = INADDR_ANY;
	packet->siaddr.s_addr = INADDR_ANY;
	packet->giaddr.s_addr = INADDR_ANY;

	memcpy(&packet->chaddr, ifi->hw_address.ether_addr_octet,
	    ETHER_ADDR_LEN);
}

void
make_release(struct interface_info *ifi, struct client_lease *lease)
{
	struct option_data	 options[DHO_COUNT];
	struct dhcp_packet	*packet = &ifi->sent_packet;
	unsigned char		 release = DHCPRELEASE;
	int			 i;

	memset(options, 0, sizeof(options));
	memset(packet, 0, sizeof(*packet));

	/* Set DHCP_MESSAGE_TYPE to DHCPRELEASE */
	i = DHO_DHCP_MESSAGE_TYPE;
	options[i].data = &release;
	options[i].len = sizeof(release);

	/* Send back the server identifier. */
	i = DHO_DHCP_SERVER_IDENTIFIER;
	options[i].data = lease->options[i].data;
	options[i].len = lease->options[i].len;

	i = pack_options(ifi->sent_packet.options, 576 - DHCP_FIXED_LEN,
	    options);
	if (i == -1 || packet->options[i] != DHO_END)
		fatalx("options do not fit in DHCPRELEASE packet");
	ifi->sent_packet_length = DHCP_FIXED_NON_UDP+i+1;
	if (ifi->sent_packet_length < BOOTP_MIN_LEN)
		ifi->sent_packet_length = BOOTP_MIN_LEN;

	packet->op = BOOTREQUEST;
	packet->htype = HTYPE_ETHER;
	packet->hlen = ETHER_ADDR_LEN;
	packet->hops = 0;
	packet->xid = ifi->xid;
	packet->secs = 0;
	packet->flags = 0;

	/*
	 * Note we return the *offered* address. NOT the configured address
	 * which could have been changed via dhclient.conf. But the packet
	 * is sent from the *configured* address.
	 *
	 * This might easily confuse a server, but if you play with fire
	 * by modifying the address you are on your own!
	 */
	packet->ciaddr.s_addr = ifi->active->address.s_addr;
	packet->yiaddr.s_addr = INADDR_ANY;
	packet->siaddr.s_addr = INADDR_ANY;
	packet->giaddr.s_addr = INADDR_ANY;

	memcpy(&packet->chaddr, ifi->hw_address.ether_addr_octet,
	    ETHER_ADDR_LEN);
}

void
free_client_lease(struct client_lease *lease)
{
	int	 i;

	if (lease == NULL)
		return;

	free(lease->server_name);
	free(lease->filename);
	for (i = 0; i < DHO_COUNT; i++)
		free(lease->options[i].data);

	free(lease);
}

void
write_lease_db(struct interface_info *ifi)
{
	struct client_lease_tq *lease_db = &ifi->lease_db;
	struct client_lease	*lp, *pl;
	char			*leasestr;

	TAILQ_FOREACH_SAFE(lp, lease_db, next, pl) {
		if (lp != ifi->active && lease_expiry(lp) == 0) {
			TAILQ_REMOVE(lease_db, lp, next);
			free_client_lease(lp);
		}
	}

	if (leaseFile == NULL)
		return;

	rewind(leaseFile);

	/*
	 * The leases file is kept in chronological order, with the
	 * most recently bound lease last. When the file was read
	 * leases that were not expired were added to the head of the
	 * TAILQ ifi->leases as they were read. Therefore write out
	 * the leases in ifi->leases in reverse order to recreate
	 * the chonological order required.
	 */
	TAILQ_FOREACH_REVERSE(lp, lease_db, client_lease_tq, next) {
		leasestr = lease_as_string("lease", lp);
		if (leasestr != NULL)
			fprintf(leaseFile, "%s", leasestr);
		else
			log_warnx("%s: cannot make lease into string",
			    log_procname);
	}

	fflush(leaseFile);
	ftruncate(fileno(leaseFile), ftello(leaseFile));
	fsync(fileno(leaseFile));
}

void
append_statement(char *string, size_t sz, char *s1, char *s2)
{
	strlcat(string, s1, sz);
	strlcat(string, s2, sz);
	strlcat(string, ";\n", sz);
}

struct unwind_info *
lease_as_unwind_info(struct client_lease *lease)
{
	struct unwind_info	*unwind_info;
	struct option_data	*opt;
	unsigned int		 servers;

	unwind_info = calloc(1, sizeof(*unwind_info));
	if (unwind_info == NULL)
		fatal("unwind_info");

	opt = &lease->options[DHO_DOMAIN_NAME_SERVERS];
	if (opt->len != 0) {
		servers = opt->len / sizeof(in_addr_t);
		if (servers > MAXNS)
			servers = MAXNS;
		if (servers > 0) {
			unwind_info->count = servers;
			memcpy(unwind_info->ns, opt->data, servers *
			    sizeof(in_addr_t));
		}
	}

	if (unwind_info->count == 0) {
		free(unwind_info);
		unwind_info = NULL;
	}

	return unwind_info;
}

struct proposal *
lease_as_proposal(struct client_lease *lease)
{
	uint8_t			 defroute[5];	/* 1 + sizeof(in_addr_t) */
	struct option_data	 fake;
	struct option_data	*opt;
	struct proposal		*proposal;
	uint8_t			*ns, *p, *routes, *domains;
	unsigned int		 routes_len = 0, domains_len = 0, ns_len = 0;
	uint16_t		 mtu;

	/* Determine sizes of variable length data. */
	opt = NULL;
	if (lease->options[DHO_CLASSLESS_STATIC_ROUTES].len != 0) {
		opt = &lease->options[DHO_CLASSLESS_STATIC_ROUTES];
	} else if (lease->options[DHO_CLASSLESS_MS_STATIC_ROUTES].len != 0) {
		opt = &lease->options[DHO_CLASSLESS_MS_STATIC_ROUTES];
	} else if (lease->options[DHO_ROUTERS].len != 0) {
		/* Fake a classless static default route. */
		opt = &lease->options[DHO_ROUTERS];
		fake.len = sizeof(defroute);
		fake.data = defroute;
		fake.data[0] = 0;
		memcpy(&fake.data[1], opt->data, sizeof(defroute) - 1);
		opt = &fake;
	}
	if (opt != NULL) {
		routes_len = opt->len;
		routes = opt->data;
	}

	opt = NULL;
	if (lease->options[DHO_DOMAIN_SEARCH].len != 0)
		opt = &lease->options[DHO_DOMAIN_SEARCH];
	else if (lease->options[DHO_DOMAIN_NAME].len != 0)
		opt = &lease->options[DHO_DOMAIN_NAME];
	if (opt != NULL) {
		domains_len = opt->len;
		domains = opt->data;
	}

	if (lease->options[DHO_DOMAIN_NAME_SERVERS].len != 0) {
		opt = &lease->options[DHO_DOMAIN_NAME_SERVERS];
		ns_len = opt->len;
		ns = opt->data;
	}

	/* Allocate proposal. */
	proposal = calloc(1, sizeof(*proposal) + routes_len + domains_len +
	    ns_len);
	if (proposal == NULL)
		fatal("proposal");

	/* Fill in proposal. */
	proposal->address = lease->address;

	opt = &lease->options[DHO_INTERFACE_MTU];
	if (opt->len == sizeof(mtu)) {
		memcpy(&mtu, opt->data, sizeof(mtu));
		proposal->mtu = ntohs(mtu);
	}

	opt = &lease->options[DHO_SUBNET_MASK];
	if (opt->len == sizeof(proposal->netmask))
		memcpy(&proposal->netmask, opt->data, opt->len);

	/* Append variable length uint8_t data. */
	p = (uint8_t *)proposal + sizeof(struct proposal);
	memcpy(p, routes, routes_len);
	p += routes_len;
	proposal->routes_len = routes_len;
	memcpy(p, domains, domains_len);
	p += domains_len;
	proposal->domains_len = domains_len;
	memcpy(p, ns, ns_len);
	proposal->ns_len = ns_len;

	return proposal;
}

char *
lease_as_string(char *type, struct client_lease *lease)
{
	static char		 string[8192];
	char			 timebuf[27];	/* 6 2017/04/08 05:47:50 UTC; */
	struct option_data	*opt;
	struct tm		*tm;
	char			*buf, *name;
	time_t			 t;
	size_t			 rslt;
	int			 i;

	memset(string, 0, sizeof(string));

	strlcat(string, type, sizeof(string));
	strlcat(string, " {\n", sizeof(string));
	strlcat(string, BOOTP_LEASE(lease) ? "  bootp;\n" : "", sizeof(string));

	append_statement(string, sizeof(string), "  fixed-address ",
	    inet_ntoa(lease->address));
	append_statement(string, sizeof(string), "  next-server ",
	    inet_ntoa(lease->next_server));

	if (lease->filename != NULL) {
		buf = pretty_print_string(lease->filename,
		    strlen(lease->filename), 1);
		if (buf == NULL)
			return NULL;
		append_statement(string, sizeof(string), "  filename ", buf);
	}
	if (lease->server_name != NULL) {
		buf = pretty_print_string(lease->server_name,
		    strlen(lease->server_name), 1);
		if (buf == NULL)
			return NULL;
		append_statement(string, sizeof(string), "  server-name ",
		    buf);
	}
	if (lease->ssid_len != 0) {
		buf = pretty_print_string(lease->ssid, lease->ssid_len, 1);
		if (buf == NULL)
			return NULL;
		append_statement(string, sizeof(string), "  ssid ", buf);
	}

	for (i = 0; i < DHO_COUNT; i++) {
		opt = &lease->options[i];
		if (opt->len == 0)
			continue;
		name = code_to_name(i);

		buf = pretty_print_option(i, opt, 1);
		if (strlen(buf) == 0)
			continue;
		strlcat(string, "  option ", sizeof(string));
		strlcat(string, name, sizeof(string));
		append_statement(string, sizeof(string), " ", buf);
	}

	i = asprintf(&buf, "%lld", (long long)lease->epoch);
	if (i == -1)
		return NULL;
	append_statement(string, sizeof(string), "  epoch ", buf);
	free(buf);

	t = lease->epoch + lease_renewal(lease);
	if ((tm = gmtime(&t)) == NULL)
		return NULL;
	rslt = strftime(timebuf, sizeof(timebuf), DB_TIMEFMT, tm);
	if (rslt == 0)
		return NULL;
	append_statement(string, sizeof(string), "  renew ", timebuf);

	t = lease->epoch + lease_rebind(lease);
	if ((tm = gmtime(&t)) == NULL)
		return NULL;
	rslt = strftime(timebuf, sizeof(timebuf), DB_TIMEFMT, tm);
	if (rslt == 0)
		return NULL;
	append_statement(string, sizeof(string), "  rebind ", timebuf);

	t = lease->epoch + lease_expiry(lease);
	if ((tm = gmtime(&t)) == NULL)
		return NULL;
	rslt = strftime(timebuf, sizeof(timebuf), DB_TIMEFMT, tm);
	if (rslt == 0)
		return NULL;
	append_statement(string, sizeof(string), "  expire ", timebuf);

	rslt = strlcat(string, "}\n", sizeof(string));
	if (rslt >= sizeof(string))
		return NULL;

	return string;
}

void
go_daemon(void)
{
	static int	 daemonized = 0;

	if ((cmd_opts & OPT_FOREGROUND) != 0 || daemonized != 0)
		return;

	daemonized = 1;

	if (rdaemon(nullfd) == -1)
		fatal("daemonize");

	/* Stop logging to stderr. */
	log_init(0, LOG_DAEMON);
	if ((cmd_opts & OPT_VERBOSE) != 0)
		log_setverbose(1);	/* Show log_debug() messages. */
	log_procinit(log_procname);

	setproctitle("%s", log_procname);
	signal(SIGHUP, SIG_IGN);
	signal(SIGPIPE, SIG_IGN);
}

int
rdaemon(int devnull)
{
	if (devnull == -1) {
		errno = EBADF;
		return -1;
	}
	if (fcntl(devnull, F_GETFL) == -1)
		return -1;

	switch (fork()) {
	case -1:
		return -1;
	case 0:
		break;
	default:
		_exit(0);
	}

	if (setsid() == -1)
		return -1;

	(void)dup2(devnull, STDIN_FILENO);
	(void)dup2(devnull, STDOUT_FILENO);
	(void)dup2(devnull, STDERR_FILENO);
	if (devnull > 2)
		(void)close(devnull);

	return 0;
}

/*
 * resolv_conf(5) says a max of DHCP_DOMAIN_SEARCH_CNT domains and total
 * length of DHCP_DOMAIN_SEARCH_LEN bytes are acceptable for the 'search'
 * statement.
 */
int
res_hnok_list(const char *names)
{
	char	*dupnames, *hn, *inputstring;
	int	 count;

	if (strlen(names) >= DHCP_DOMAIN_SEARCH_LEN)
		return 0;

	dupnames = inputstring = strdup(names);
	if (inputstring == NULL)
		fatal("domain name list");

	count = 0;
	while ((hn = strsep(&inputstring, " \t")) != NULL) {
		if (strlen(hn) == 0)
			continue;
		if (res_hnok(hn) == 0)
			break;
		count++;
		if (count > DHCP_DOMAIN_SEARCH_CNT)
			break;
	}

	free(dupnames);

	return count > 0 && count < 7 && hn == NULL;
}

/*
 * Decode a byte string encoding a list of domain names as specified in RFC1035
 * section 4.1.4.
 *
 * The result is a string consisting of a blank separated list of domain names.
 *
 * e.g.
 *
 * 3:65:6e:67:5:61:70:70:6c:65:3:63:6f:6d:0:9:6d:61:72:6b:65:74:69:6e:67:c0:04
 *
 * which represents
 *
 *    3 |'e'|'n'|'g'| 5 |'a'|'p'|'p'|'l'|
 *   'e'| 3 |'c'|'o'|'m'| 0 | 9 |'m'|'a'|
 *   'r'|'k'|'e'|'t'|'i'|'n'|'g'|xC0|x04|
 *
 * will be translated to
 *
 * "eng.apple.com. marketing.apple.com."
 */
char *
rfc1035_as_string(unsigned char *src, size_t srclen)
{
	static char		 search[DHCP_DOMAIN_SEARCH_LEN];
	unsigned char		 name[DHCP_DOMAIN_SEARCH_LEN];
	unsigned char		*endsrc, *cp;
	int			 len, domains;

	memset(search, 0, sizeof(search));

	/* Compute expanded length. */
	domains = 0;
	cp = src;
	endsrc = src + srclen;

	while (cp < endsrc && domains < DHCP_DOMAIN_SEARCH_CNT) {
		len = dn_expand(src, endsrc, cp, name, sizeof(name));
		if (len == -1)
			goto bad;
		cp += len;
		if (domains > 0)
			strlcat(search, " ", sizeof(search));
		strlcat(search, name, sizeof(search));
		if (strlcat(search, ".", sizeof(search)) >= sizeof(search))
			goto bad;
		domains++;
	}

	return search;

bad:
	memset(search, 0, sizeof(search));
	return search;
}

void
fork_privchld(struct interface_info *ifi, int fd, int fd2)
{
	struct pollfd	 pfd[1];
	struct imsgbuf	*priv_ibuf;
	ssize_t		 n;
	int		 ioctlfd, routefd, nfds, rslt;

	switch (fork()) {
	case -1:
		fatal("fork");
		break;
	case 0:
		break;
	default:
		return;
	}

	if (chdir("/") == -1)
		fatal("chdir(\"/\")");

	go_daemon();

	free(log_procname);
	rslt = asprintf(&log_procname, "%s [priv]", ifi->name);
	if (rslt == -1)
		fatal("log_procname");
	setproctitle("%s", log_procname);
	log_procinit(log_procname);

	close(fd2);

	if ((priv_ibuf = malloc(sizeof(*priv_ibuf))) == NULL)
		fatal("priv_ibuf");

	imsg_init(priv_ibuf, fd);

	if ((ioctlfd = socket(AF_INET, SOCK_DGRAM, 0)) == -1)
		fatal("socket(AF_INET, SOCK_DGRAM)");
	if ((routefd = socket(AF_ROUTE, SOCK_RAW, 0)) == -1)
		fatal("socket(AF_ROUTE, SOCK_RAW)");

	if (unveil(_PATH_RESCONF, "wc") == -1)
		fatal("unveil %s", _PATH_RESCONF);
	if (unveil("/etc/resolv.conf.tail", "r") == -1)
		fatal("unveil /etc/resolve.conf.tail");
	if (unveil(NULL, NULL) == -1)
		fatal("unveil");

	while (quit == 0) {
		pfd[0].fd = priv_ibuf->fd;
		pfd[0].events = POLLIN;

		nfds = ppoll(pfd, 1, NULL, NULL);
		if (nfds == -1) {
			if (errno == EINTR)
				continue;
			log_warn("%s: ppoll(priv_ibuf)", log_procname);
			break;
		}
		if ((pfd[0].revents & (POLLERR | POLLHUP | POLLNVAL)) != 0)
			break;
		if (nfds == 0 || (pfd[0].revents & POLLIN) == 0)
			continue;

		if ((n = imsg_read(priv_ibuf)) == -1 && errno != EAGAIN) {
			log_warn("%s: imsg_read(priv_ibuf)", log_procname);
			break;
		}
		if (n == 0) {
			/* Connection closed - other end should log message. */
			break;
		}

		dispatch_imsg(ifi->name, ifi->rdomain, ioctlfd, routefd,
		    priv_ibuf);
	}
	close(routefd);
	close(ioctlfd);

	imsg_clear(priv_ibuf);
	close(fd);

	exit(1);
}

struct client_lease *
apply_defaults(struct client_lease *lease)
{
	struct option_data	 emptyopt = {0, NULL};
	struct client_lease	*newlease;
	char			*fmt;
	int			 i;

	newlease = clone_lease(lease);
	if (newlease == NULL)
		fatalx("unable to clone lease");

	if (config->filename != NULL) {
		free(newlease->filename);
		newlease->filename = strdup(config->filename);
		if (newlease->filename == NULL)
			fatal("strdup(config->filename)");
	}
	if (config->server_name != NULL) {
		free(newlease->server_name);
		newlease->server_name = strdup(config->server_name);
		if (newlease->server_name == NULL)
			fatal("strdup(config->server_name)");
	}
	if (config->address.s_addr != INADDR_ANY)
		newlease->address.s_addr = config->address.s_addr;
	if (config->next_server.s_addr != INADDR_ANY)
		newlease->next_server.s_addr = config->next_server.s_addr;

	for (i = 0; i < DHO_COUNT; i++) {
		fmt = code_to_format(i);
		switch (config->default_actions[i]) {
		case ACTION_IGNORE:
			merge_option_data(fmt, &emptyopt, &emptyopt,
			    &newlease->options[i]);
			break;

		case ACTION_SUPERSEDE:
			merge_option_data(fmt, &config->defaults[i], &emptyopt,
			    &newlease->options[i]);
			break;

		case ACTION_PREPEND:
			merge_option_data(fmt, &config->defaults[i],
			    &lease->options[i], &newlease->options[i]);
			break;

		case ACTION_APPEND:
			merge_option_data(fmt, &lease->options[i],
			    &config->defaults[i], &newlease->options[i]);
			break;

		case ACTION_DEFAULT:
			if (newlease->options[i].len == 0)
				merge_option_data(fmt, &config->defaults[i],
				    &emptyopt, &newlease->options[i]);
			break;

		default:
			break;
		}
	}

	if (newlease->options[DHO_STATIC_ROUTES].len != 0) {
		log_debug("%s: DHO_STATIC_ROUTES (option 33) not supported",
		    log_procname);
		free(newlease->options[DHO_STATIC_ROUTES].data);
		newlease->options[DHO_STATIC_ROUTES].data = NULL;
		newlease->options[DHO_STATIC_ROUTES].len = 0;
	}

	/*
	 * RFC 3442 says client *MUST* ignore DHO_ROUTERS
	 * when DHO_CLASSLESS_[MS_]_ROUTES present.
	 */
	if ((newlease->options[DHO_CLASSLESS_MS_STATIC_ROUTES].len != 0) ||
	    (newlease->options[DHO_CLASSLESS_STATIC_ROUTES].len != 0)) {
		free(newlease->options[DHO_ROUTERS].data);
		newlease->options[DHO_ROUTERS].data = NULL;
		newlease->options[DHO_ROUTERS].len = 0;
	}

	return newlease;
}

struct client_lease *
clone_lease(struct client_lease *oldlease)
{
	struct client_lease	*newlease;
	int			 i;

	newlease = calloc(1, sizeof(*newlease));
	if (newlease == NULL)
		goto cleanup;

	newlease->epoch = oldlease->epoch;
	newlease->address = oldlease->address;
	newlease->next_server = oldlease->next_server;
	memcpy(newlease->ssid, oldlease->ssid, sizeof(newlease->ssid));
	newlease->ssid_len = oldlease->ssid_len;

	if (oldlease->server_name != NULL) {
		newlease->server_name = strdup(oldlease->server_name);
		if (newlease->server_name == NULL)
			goto cleanup;
	}
	if (oldlease->filename != NULL) {
		newlease->filename = strdup(oldlease->filename);
		if (newlease->filename == NULL)
			goto cleanup;
	}

	for (i = 0; i < DHO_COUNT; i++) {
		if (oldlease->options[i].len == 0)
			continue;
		newlease->options[i].len = oldlease->options[i].len;
		newlease->options[i].data = calloc(1,
		    newlease->options[i].len);
		if (newlease->options[i].data == NULL)
			goto cleanup;
		memcpy(newlease->options[i].data, oldlease->options[i].data,
		    newlease->options[i].len);
	}

	return newlease;

cleanup:
	free_client_lease(newlease);

	return NULL;
}

int
autoconf(struct interface_info *ifi)
{
	struct ifreq            ifr;
	int			ioctlfd;

	if ((ioctlfd = socket(AF_INET, SOCK_DGRAM, 0)) == -1)
		fatal("socket(AF_INET, SOCK_DGRAM)");

	memset(&ifr, 0, sizeof(ifr));
	strlcpy(ifr.ifr_name, ifi->name, sizeof(ifr.ifr_name));

	if (ioctl(ioctlfd, SIOCGIFXFLAGS, (caddr_t)&ifr) < 0)
		fatal("SIOGIFXFLAGS");

	close(ioctlfd);

	return ifr.ifr_flags & IFXF_AUTOCONF4;
}

int
take_charge(struct interface_info *ifi, int routefd, char *leasespath)
{
	const struct timespec	 max_timeout = { 9, 0 };
	const struct timespec	 resend_intvl = { 3, 0 };
	const struct timespec	 leasefile_intvl = { 0, 3000000 };
	struct timespec		 now, resend, stop, timeout;
	struct pollfd		 fds[1];
	struct rt_msghdr	 rtm;
	int			 fd, nfds;

	clock_gettime(CLOCK_MONOTONIC, &now);
	resend = now;
	timespecadd(&now, &max_timeout, &stop);

	/*
	 * Send RTM_PROPOSAL with RTF_PROTO3 set.
	 *
	 * When it comes back, we're in charge and other dhclients are
	 * dead processes walking.
	 */
	memset(&rtm, 0, sizeof(rtm));

	rtm.rtm_version = RTM_VERSION;
	rtm.rtm_type = RTM_PROPOSAL;
	rtm.rtm_msglen = sizeof(rtm);
	rtm.rtm_tableid = ifi->rdomain;
	rtm.rtm_index = ifi->index;
	rtm.rtm_priority = RTP_PROPOSAL_DHCLIENT;
	rtm.rtm_addrs = 0;
	rtm.rtm_flags = RTF_UP | RTF_PROTO3;

	for (fd = -1; fd == -1 && quit != TERMINATE;) {
		clock_gettime(CLOCK_MONOTONIC, &now);
		if (timespeccmp(&now, &stop, >=))
			fatalx("failed to take charge");

		if ((ifi->flags & IFI_IN_CHARGE) == 0) {
			if (timespeccmp(&now, &resend, >=)) {
				timespecadd(&resend, &resend_intvl, &resend);
				rtm.rtm_seq = ifi->xid = arc4random();
				if (write(routefd, &rtm, sizeof(rtm)) == -1)
					fatal("write(routefd)");
			}
			timespecsub(&resend, &now, &timeout);
		} else {
			/*
			 * Keep trying to open leasefile in 3ms intervals
			 * while continuing to process any RTM_* messages
			 * that come in.
			 */
			 timeout = leasefile_intvl;
		}

		fds[0].fd = routefd;
		fds[0].events = POLLIN;
		nfds = ppoll(fds, 1, &timeout, NULL);
		if (nfds == -1) {
			if (errno == EINTR)
				continue;
			fatal("ppoll(routefd)");
		}

		if ((fds[0].revents & (POLLERR | POLLHUP | POLLNVAL)) != 0)
			fatalx("routefd: ERR|HUP|NVAL");
		if (nfds == 1 && (fds[0].revents & POLLIN) == POLLIN)
			routefd_handler(ifi, routefd);

		if (quit != TERMINATE && (ifi->flags & IFI_IN_CHARGE) == IFI_IN_CHARGE) {
			fd = open(leasespath, O_NONBLOCK |
			    O_RDONLY|O_EXLOCK|O_CREAT|O_NOFOLLOW, 0640);
			if (fd == -1 && errno != EWOULDBLOCK)
				break;
		}
	}

	return fd;
}

struct client_lease *
get_recorded_lease(struct interface_info *ifi)
{
	char			 ifname[IF_NAMESIZE];
	struct client_lease	*lp;
	int			 i;

	/* Update on-disk db, which clears out expired leases. */
	ifi->active = NULL;
	write_lease_db(ifi);

	/* Run through the list of leases and see if one can be used. */
	i = DHO_DHCP_CLIENT_IDENTIFIER;
	TAILQ_FOREACH(lp, &ifi->lease_db, next) {
		if (lp->ssid_len != ifi->ssid_len)
			continue;
		if (memcmp(lp->ssid, ifi->ssid, lp->ssid_len) != 0)
			continue;
		if ((lp->options[i].len != 0) && ((lp->options[i].len !=
		    config->send_options[i].len) ||
		    memcmp(lp->options[i].data, config->send_options[i].data,
		    lp->options[i].len) != 0))
			continue;
		if (addressinuse(ifi->name, lp->address, ifname) != 0 &&
		    strncmp(ifname, ifi->name, IF_NAMESIZE) != 0)
			continue;
		break;
	}

	return lp;
}

time_t
lease_expiry(struct client_lease *lease)
{
	time_t		cur_time;
	uint32_t	expiry;

	time(&cur_time);
	expiry = 0;
	if (lease->options[DHO_DHCP_LEASE_TIME].len == sizeof(expiry)) {
		memcpy(&expiry, lease->options[DHO_DHCP_LEASE_TIME].data,
		    sizeof(expiry));
		expiry = ntohl(expiry);
		if (expiry < 60)
			expiry = 60;
	}
	expiry = lease->epoch + expiry - cur_time;

	return (expiry > 0) ? expiry : 0;
}

time_t
lease_renewal(struct client_lease *lease)
{
	time_t		 cur_time, expiry;
	uint32_t	 renewal;

	time(&cur_time);
	expiry = lease_expiry(lease);

	renewal = expiry / 2;
	if (lease->options[DHO_DHCP_RENEWAL_TIME].len == sizeof(renewal)) {
		memcpy(&renewal, lease->options[DHO_DHCP_RENEWAL_TIME].data,
		    sizeof(renewal));
		renewal = ntohl(renewal);
	}
	renewal = lease->epoch + renewal - cur_time;

	return (renewal > 0) ? renewal : 0;
}

time_t
lease_rebind(struct client_lease *lease)
{
	time_t		cur_time, expiry;
	uint32_t	rebind;

	time(&cur_time);
	expiry = lease_expiry(lease);

	rebind = (expiry / 8) * 7;
	if (lease->options[DHO_DHCP_REBINDING_TIME].len == sizeof(rebind)) {
		memcpy(&rebind, lease->options[DHO_DHCP_REBINDING_TIME].data,
		    sizeof(rebind));
		rebind = ntohl(rebind);
	}
	rebind = lease->epoch + rebind - cur_time;

	return (rebind > 0) ? rebind : 0;
}

void
get_lease_timeouts(struct interface_info *ifi, struct client_lease *lease)
{
	struct timespec	now, interval;

	clock_gettime(CLOCK_MONOTONIC, &now);
	timespecclear(&interval);

	interval.tv_sec = lease_expiry(lease);
	timespecadd(&now, &interval, &ifi->expiry);

	interval.tv_sec = lease_rebind(lease);
	timespecadd(&now, &interval, &ifi->rebind);

	interval.tv_sec = lease_renewal(lease);
	timespecadd(&now, &interval, &ifi->renew);

	if (timespeccmp(&ifi->rebind, &ifi->expiry, >))
		ifi->rebind = ifi->expiry;
	if (timespeccmp(&ifi->renew, &ifi->rebind, >))
		ifi->renew = ifi->rebind;
}

void
tick_msg(const char *preamble, int action)
{
	const struct timespec		grace_intvl = {3, 0};
	const struct timespec		link_intvl = {config->link_interval, 0};
	static struct timespec		grace, stop;
	struct timespec			now;
	static int			linkup, preamble_sent, sleeping;
	int				printmsg;

	clock_gettime(CLOCK_MONOTONIC, &now);

	if (!timespecisset(&stop)) {
		preamble_sent = 0;
		timespecadd(&now, &link_intvl, &stop);
		timespecadd(&now, &grace_intvl, &grace);
		return;
	}

	if (isatty(STDERR_FILENO) == 0 || sleeping == 1)
		printmsg = 0;	/* Already in the background. */
	else if (timespeccmp(&now, &grace, <))
		printmsg = 0;	/* Wait a bit before speaking. */
	else if (linkup && strcmp("link", preamble) == 0)
		printmsg = 0;	/* One 'got link' is enough for anyone. */
	else if (log_getverbose())
		printmsg = 0;	/* Verbose has sufficent verbiage. */
	else
		printmsg = 1;

	if (timespeccmp(&now, &stop, >=)) {
		if (action == TICK_WAIT)
			action = TICK_DAEMON;
		if (linkup == 0) {
			log_debug("%s: link timeout (%lld seconds) expired",
			    log_procname, (long long)link_intvl.tv_sec);
			linkup = 1;
		}
	}

	if (printmsg && preamble_sent == 0) {
		fprintf(stderr, "%s: no %s...", log_procname, preamble);
		preamble_sent = 1;
	}

	switch (action) {
	case TICK_SUCCESS:
		if (printmsg)
			fprintf(stderr, "got %s\n", preamble);
		preamble_sent = 0;
		if (strcmp("link", preamble) == 0) {
			linkup = 1;
			/* New silent period for "no lease ... got lease". */
			timespecadd(&now, &grace_intvl, &grace);
		}
		break;
	case TICK_WAIT:
		if (printmsg)
			fprintf(stderr, ".");
		break;
	case TICK_DAEMON:
		if (printmsg)
			fprintf(stderr, "sleeping\n");
		go_daemon();
		sleeping = 1;	/* OPT_FOREGROUND means isatty() == 1! */
		break;
	default:
		break;
	}

	if (printmsg)
		fflush(stderr);
}

/*
 * Release the lease used to configure the interface.
 *
 * 1) Send DHCPRELEASE.
 * 2) Unconfigure address/routes/etc.
 * 3) Remove lease from database & write updated DB.
 */
void
release_lease(struct interface_info *ifi)
{
	char			 buf[INET_ADDRSTRLEN];
	struct option_data	*opt;

	if (ifi->configured == NULL || ifi->active == NULL)
		return;	/* Nothing to release. */
	strlcpy(buf, inet_ntoa(ifi->configured->address), sizeof(buf));

	opt = &ifi->active->options[DHO_DHCP_SERVER_IDENTIFIER];
	if (opt->len == sizeof(in_addr_t))
		ifi->destination.s_addr = *(in_addr_t *)opt->data;
	else
		ifi->destination.s_addr = INADDR_BROADCAST;

	ifi->xid = arc4random();
	make_release(ifi, ifi->active);
	send_release(ifi);

	tell_unwind(NULL, ifi->flags);

	revoke_proposal(ifi->configured);
	imsg_flush(unpriv_ibuf);

	TAILQ_REMOVE(&ifi->lease_db, ifi->active, next);
	free_client_lease(ifi->active);
	ifi->active = NULL;
	write_lease_db(ifi);

	free(ifi->configured);
	ifi->configured = NULL;
	free(ifi->unwind_info);
	ifi->unwind_info = NULL;

	log_warnx("%s: %s RELEASED to %s", log_procname, buf,
	    inet_ntoa(ifi->destination));
}

void
propose_release(struct interface_info *ifi)
{
	const struct timespec	 max_timeout = { 3, 0 };
	struct timespec		 now, stop, timeout;
	struct pollfd		 fds[1];
	struct rt_msghdr	 rtm;
	int			 nfds, routefd, rtfilter;

	clock_gettime(CLOCK_MONOTONIC, &now);
	timespecadd(&now, &max_timeout, &stop);

	if ((routefd = socket(AF_ROUTE, SOCK_RAW, AF_INET)) == -1)
		fatal("socket(AF_ROUTE, SOCK_RAW)");

	rtfilter = ROUTE_FILTER(RTM_PROPOSAL);

	if (setsockopt(routefd, AF_ROUTE, ROUTE_MSGFILTER,
	    &rtfilter, sizeof(rtfilter)) == -1)
		fatal("setsockopt(ROUTE_MSGFILTER)");
	if (setsockopt(routefd, AF_ROUTE, ROUTE_TABLEFILTER, &ifi->rdomain,
	    sizeof(ifi->rdomain)) == -1)
		fatal("setsockopt(ROUTE_TABLEFILTER)");

	memset(&rtm, 0, sizeof(rtm));
	rtm.rtm_version = RTM_VERSION;
	rtm.rtm_type = RTM_PROPOSAL;
	rtm.rtm_msglen = sizeof(rtm);
	rtm.rtm_tableid = ifi->rdomain;
	rtm.rtm_index = ifi->index;
	rtm.rtm_priority = RTP_PROPOSAL_DHCLIENT;
	rtm.rtm_addrs = 0;
	rtm.rtm_flags = RTF_UP;
	rtm.rtm_flags |= RTF_PROTO2;
	rtm.rtm_seq = ifi->xid = arc4random();

	if (write(routefd, &rtm, sizeof(rtm)) == -1)
		fatal("write(routefd)");
	log_debug("%s: sent RTM_PROPOSAL to release lease", log_procname);

	while (quit == 0) {
		clock_gettime(CLOCK_MONOTONIC, &now);
		if (timespeccmp(&now, &stop, >=))
			break;
		timespecsub(&stop, &now, &timeout);
		fds[0].fd = routefd;
		fds[0].events = POLLIN;
		nfds = ppoll(fds, 1, &timeout, NULL);
		if (nfds == -1) {
			if (errno == EINTR)
				continue;
			fatal("ppoll(routefd)");
		}
		if ((fds[0].revents & (POLLERR | POLLHUP | POLLNVAL)) != 0)
			fatalx("routefd: ERR|HUP|NVAL");
		if (nfds == 0 || (fds[0].revents & POLLIN) == 0)
			continue;
		routefd_handler(ifi, routefd);
	}
}