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

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

Revision 1.123, Tue Jul 7 19:48:31 2020 UTC (3 years, 11 months ago) by krw
Branch: MAIN
CVS Tags: OPENBSD_7_5_BASE, OPENBSD_7_5, OPENBSD_7_4_BASE, OPENBSD_7_4, OPENBSD_7_3_BASE, OPENBSD_7_3, OPENBSD_7_2_BASE, OPENBSD_7_2, OPENBSD_7_1_BASE, OPENBSD_7_1, OPENBSD_7_0_BASE, OPENBSD_7_0, OPENBSD_6_9_BASE, OPENBSD_6_9, OPENBSD_6_8_BASE, OPENBSD_6_8, HEAD
Changes since 1.122: +28 -29 lines

Revert r1.121 and rewrite merge_option_data() to achieve same effect
w/o using string functions on data that *MIGHT NOT* be NUL
terminated. Fiddle parse_domain_name_list() to avoid string functions
for the same reason.

Problem encountered by Jesper Wallin when running with
vm.malloc_conf=CFGJUR, although he later proved 'J' (more junking) was
the actual trouble maker.

/*	$OpenBSD: options.c,v 1.123 2020/07/07 19:48:31 krw Exp $	*/

/* DHCP options parsing and reassembly. */

/*
 * Copyright (c) 1995, 1996, 1997, 1998 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''.
 */

#include <sys/queue.h>
#include <sys/socket.h>

#include <arpa/inet.h>

#include <net/if.h>

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

#include <ctype.h>
#include <resolv.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <vis.h>

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

int	parse_option_buffer(struct option_data *, unsigned char *, int);
void	pretty_print_classless_routes(unsigned char *, size_t, unsigned char *,
    size_t);
void	pretty_print_domain_list(unsigned char *, size_t, unsigned char *,
    size_t);

/*
 * DHCP Option names, formats and codes, from RFC1533.
 *
 * Format codes:
 *
 * e - end of data
 * I - IP address
 * l - 32-bit signed integer
 * L - 32-bit unsigned integer
 * S - 16-bit unsigned integer
 * B - 8-bit unsigned integer
 * t - ASCII text
 * f - flag (true or false)
 * A - array of whatever precedes (e.g., IA means array of IP addresses)
 * C - CIDR description
 * X - hex octets
 * D - domain name list, comma separated list of domain names.
 */

static const struct {
	char *name;
	char *format;
} dhcp_options[DHO_COUNT] = {
	/*   0 */ { "pad", "" },
	/*   1 */ { "subnet-mask", "I" },
	/*   2 */ { "time-offset", "l" },
	/*   3 */ { "routers", "IA" },
	/*   4 */ { "time-servers", "IA" },
	/*   5 */ { "ien116-name-servers", "IA" },
	/*   6 */ { "domain-name-servers", "IA" },
	/*   7 */ { "log-servers", "IA" },
	/*   8 */ { "cookie-servers", "IA" },
	/*   9 */ { "lpr-servers", "IA" },
	/*  10 */ { "impress-servers", "IA" },
	/*  11 */ { "resource-location-servers", "IA" },
	/*  12 */ { "host-name", "t" },
	/*  13 */ { "boot-size", "S" },
	/*  14 */ { "merit-dump", "t" },
	/*  15 */ { "domain-name", "t" },
	/*  16 */ { "swap-server", "I" },
	/*  17 */ { "root-path", "t" },
	/*  18 */ { "extensions-path", "t" },
	/*  19 */ { "ip-forwarding", "f" },
	/*  20 */ { "non-local-source-routing", "f" },
	/*  21 */ { "policy-filter", "IIA" },
	/*  22 */ { "max-dgram-reassembly", "S" },
	/*  23 */ { "default-ip-ttl", "B" },
	/*  24 */ { "path-mtu-aging-timeout", "L" },
	/*  25 */ { "path-mtu-plateau-table", "SA" },
	/*  26 */ { "interface-mtu", "S" },
	/*  27 */ { "all-subnets-local", "f" },
	/*  28 */ { "broadcast-address", "I" },
	/*  29 */ { "perform-mask-discovery", "f" },
	/*  30 */ { "mask-supplier", "f" },
	/*  31 */ { "router-discovery", "f" },
	/*  32 */ { "router-solicitation-address", "I" },
	/*  33 */ { "static-routes", "IIA" },
	/*  34 */ { "trailer-encapsulation", "f" },
	/*  35 */ { "arp-cache-timeout", "L" },
	/*  36 */ { "ieee802-3-encapsulation", "f" },
	/*  37 */ { "default-tcp-ttl", "B" },
	/*  38 */ { "tcp-keepalive-interval", "L" },
	/*  39 */ { "tcp-keepalive-garbage", "f" },
	/*  40 */ { "nis-domain", "t" },
	/*  41 */ { "nis-servers", "IA" },
	/*  42 */ { "ntp-servers", "IA" },
	/*  43 */ { "vendor-encapsulated-options", "X" },
	/*  44 */ { "netbios-name-servers", "IA" },
	/*  45 */ { "netbios-dd-server", "IA" },
	/*  46 */ { "netbios-node-type", "B" },
	/*  47 */ { "netbios-scope", "t" },
	/*  48 */ { "font-servers", "IA" },
	/*  49 */ { "x-display-manager", "IA" },
	/*  50 */ { "dhcp-requested-address", "I" },
	/*  51 */ { "dhcp-lease-time", "L" },
	/*  52 */ { "dhcp-option-overload", "B" },
	/*  53 */ { "dhcp-message-type", "B" },
	/*  54 */ { "dhcp-server-identifier", "I" },
	/*  55 */ { "dhcp-parameter-request-list", "BA" },
	/*  56 */ { "dhcp-message", "t" },
	/*  57 */ { "dhcp-max-message-size", "S" },
	/*  58 */ { "dhcp-renewal-time", "L" },
	/*  59 */ { "dhcp-rebinding-time", "L" },
	/*  60 */ { "dhcp-class-identifier", "t" },
	/*  61 */ { "dhcp-client-identifier", "X" },
	/*  62 */ { NULL, NULL },
	/*  63 */ { NULL, NULL },
	/*  64 */ { "nisplus-domain", "t" },
	/*  65 */ { "nisplus-servers", "IA" },
	/*  66 */ { "tftp-server-name", "t" },
	/*  67 */ { "bootfile-name", "t" },
	/*  68 */ { "mobile-ip-home-agent", "IA" },
	/*  69 */ { "smtp-server", "IA" },
	/*  70 */ { "pop-server", "IA" },
	/*  71 */ { "nntp-server", "IA" },
	/*  72 */ { "www-server", "IA" },
	/*  73 */ { "finger-server", "IA" },
	/*  74 */ { "irc-server", "IA" },
	/*  75 */ { "streettalk-server", "IA" },
	/*  76 */ { "streettalk-directory-assistance-server", "IA" },
	/*  77 */ { "user-class", "t" },
	/*  78 */ { NULL, NULL },
	/*  79 */ { NULL, NULL },
	/*  80 */ { NULL, NULL },
	/*  81 */ { NULL, NULL },
	/*  82 */ { "relay-agent-information", "X" },
	/*  83 */ { NULL, NULL },
	/*  84 */ { NULL, NULL },
	/*  85 */ { "nds-servers", "IA" },
	/*  86 */ { "nds-tree-name", "X" },
	/*  87 */ { "nds-context", "X" },
	/*  88 */ { NULL, NULL },
	/*  89 */ { NULL, NULL },
	/*  90 */ { NULL, NULL },
	/*  91 */ { NULL, NULL },
	/*  92 */ { NULL, NULL },
	/*  93 */ { NULL, NULL },
	/*  94 */ { NULL, NULL },
	/*  95 */ { NULL, NULL },
	/*  96 */ { NULL, NULL },
	/*  97 */ { NULL, NULL },
	/*  98 */ { NULL, NULL },
	/*  99 */ { NULL, NULL },
	/* 100 */ { NULL, NULL },
	/* 101 */ { NULL, NULL },
	/* 102 */ { NULL, NULL },
	/* 103 */ { NULL, NULL },
	/* 104 */ { NULL, NULL },
	/* 105 */ { NULL, NULL },
	/* 106 */ { NULL, NULL },
	/* 107 */ { NULL, NULL },
	/* 108 */ { NULL, NULL },
	/* 109 */ { NULL, NULL },
	/* 110 */ { NULL, NULL },
	/* 111 */ { NULL, NULL },
	/* 112 */ { NULL, NULL },
	/* 113 */ { NULL, NULL },
	/* 114 */ { NULL, NULL },
	/* 115 */ { NULL, NULL },
	/* 116 */ { NULL, NULL },
	/* 117 */ { NULL, NULL },
	/* 118 */ { NULL, NULL },
	/* 119 */ { "domain-search", "D" },
	/* 120 */ { NULL, NULL },
	/* 121 */ { "classless-static-routes", "CIA" },
	/* 122 */ { NULL, NULL },
	/* 123 */ { NULL, NULL },
	/* 124 */ { NULL, NULL },
	/* 125 */ { NULL, NULL },
	/* 126 */ { NULL, NULL },
	/* 127 */ { NULL, NULL },
	/* 128 */ { NULL, NULL },
	/* 129 */ { NULL, NULL },
	/* 130 */ { NULL, NULL },
	/* 131 */ { NULL, NULL },
	/* 132 */ { NULL, NULL },
	/* 133 */ { NULL, NULL },
	/* 134 */ { NULL, NULL },
	/* 135 */ { NULL, NULL },
	/* 136 */ { NULL, NULL },
	/* 137 */ { NULL, NULL },
	/* 138 */ { NULL, NULL },
	/* 139 */ { NULL, NULL },
	/* 140 */ { NULL, NULL },
	/* 141 */ { NULL, NULL },
	/* 142 */ { NULL, NULL },
	/* 143 */ { NULL, NULL },
	/* 144 */ { "tftp-config-file", "t" },
	/* 145 */ { NULL, NULL },
	/* 146 */ { NULL, NULL },
	/* 147 */ { NULL, NULL },
	/* 148 */ { NULL, NULL },
	/* 149 */ { NULL, NULL },
	/* 150 */ { "voip-configuration-server", "IA" },
	/* 151 */ { NULL, NULL },
	/* 152 */ { NULL, NULL },
	/* 153 */ { NULL, NULL },
	/* 154 */ { NULL, NULL },
	/* 155 */ { NULL, NULL },
	/* 156 */ { NULL, NULL },
	/* 157 */ { NULL, NULL },
	/* 158 */ { NULL, NULL },
	/* 159 */ { NULL, NULL },
	/* 160 */ { NULL, NULL },
	/* 161 */ { NULL, NULL },
	/* 162 */ { NULL, NULL },
	/* 163 */ { NULL, NULL },
	/* 164 */ { NULL, NULL },
	/* 165 */ { NULL, NULL },
	/* 166 */ { NULL, NULL },
	/* 167 */ { NULL, NULL },
	/* 168 */ { NULL, NULL },
	/* 169 */ { NULL, NULL },
	/* 170 */ { NULL, NULL },
	/* 171 */ { NULL, NULL },
	/* 172 */ { NULL, NULL },
	/* 173 */ { NULL, NULL },
	/* 174 */ { NULL, NULL },
	/* 175 */ { NULL, NULL },
	/* 176 */ { NULL, NULL },
	/* 177 */ { NULL, NULL },
	/* 178 */ { NULL, NULL },
	/* 179 */ { NULL, NULL },
	/* 180 */ { NULL, NULL },
	/* 181 */ { NULL, NULL },
	/* 182 */ { NULL, NULL },
	/* 183 */ { NULL, NULL },
	/* 184 */ { NULL, NULL },
	/* 185 */ { NULL, NULL },
	/* 186 */ { NULL, NULL },
	/* 187 */ { NULL, NULL },
	/* 188 */ { NULL, NULL },
	/* 189 */ { NULL, NULL },
	/* 190 */ { NULL, NULL },
	/* 191 */ { NULL, NULL },
	/* 192 */ { NULL, NULL },
	/* 193 */ { NULL, NULL },
	/* 194 */ { NULL, NULL },
	/* 195 */ { NULL, NULL },
	/* 196 */ { NULL, NULL },
	/* 197 */ { NULL, NULL },
	/* 198 */ { NULL, NULL },
	/* 199 */ { NULL, NULL },
	/* 200 */ { NULL, NULL },
	/* 201 */ { NULL, NULL },
	/* 202 */ { NULL, NULL },
	/* 203 */ { NULL, NULL },
	/* 204 */ { NULL, NULL },
	/* 205 */ { NULL, NULL },
	/* 206 */ { NULL, NULL },
	/* 207 */ { NULL, NULL },
	/* 208 */ { NULL, NULL },
	/* 209 */ { NULL, NULL },
	/* 210 */ { NULL, NULL },
	/* 211 */ { NULL, NULL },
	/* 212 */ { NULL, NULL },
	/* 213 */ { NULL, NULL },
	/* 214 */ { NULL, NULL },
	/* 215 */ { NULL, NULL },
	/* 216 */ { NULL, NULL },
	/* 217 */ { NULL, NULL },
	/* 218 */ { NULL, NULL },
	/* 219 */ { NULL, NULL },
	/* 220 */ { NULL, NULL },
	/* 221 */ { NULL, NULL },
	/* 222 */ { NULL, NULL },
	/* 223 */ { NULL, NULL },
	/* 224 */ { NULL, NULL },
	/* 225 */ { NULL, NULL },
	/* 226 */ { NULL, NULL },
	/* 227 */ { NULL, NULL },
	/* 228 */ { NULL, NULL },
	/* 229 */ { NULL, NULL },
	/* 230 */ { NULL, NULL },
	/* 231 */ { NULL, NULL },
	/* 232 */ { NULL, NULL },
	/* 233 */ { NULL, NULL },
	/* 234 */ { NULL, NULL },
	/* 235 */ { NULL, NULL },
	/* 236 */ { NULL, NULL },
	/* 237 */ { NULL, NULL },
	/* 238 */ { NULL, NULL },
	/* 239 */ { NULL, NULL },
	/* 240 */ { NULL, NULL },
	/* 241 */ { NULL, NULL },
	/* 242 */ { NULL, NULL },
	/* 243 */ { NULL, NULL },
	/* 244 */ { NULL, NULL },
	/* 245 */ { NULL, NULL },
	/* 246 */ { NULL, NULL },
	/* 247 */ { NULL, NULL },
	/* 248 */ { NULL, NULL },
	/* 249 */ { "classless-ms-static-routes", "CIA" },
	/* 250 */ { NULL, NULL },
	/* 251 */ { NULL, NULL },
	/* 252 */ { "autoproxy-script", "t" },
	/* 253 */ { NULL, NULL },
	/* 254 */ { NULL, NULL },
	/* 255 */ { "option-end", "e" },
};

char *
code_to_name(int code)
{
	static char	 unknown[11];	/* "option-NNN" */
	int		 ret;

	if (code < 0 || code >= DHO_COUNT)
		return "";

	if (dhcp_options[code].name != NULL)
		return dhcp_options[code].name;

	ret = snprintf(unknown, sizeof(unknown), "option-%d", code);
	if (ret < 0 || ret >= (int)sizeof(unknown))
		return "";

	return unknown;
}

int
name_to_code(char *name)
{
	char	unknown[11];	/* "option-NNN" */
	int	code, ret;

	for (code = 1; code < DHO_END; code++) {
		if (dhcp_options[code].name == NULL) {
			ret = snprintf(unknown, sizeof(unknown), "option-%d",
			    code);
			if (ret < 0 || ret >= (int)sizeof(unknown))
				return DHO_END;
			if (strcasecmp(unknown, name) == 0)
				return code;
		} else if (strcasecmp(dhcp_options[code].name, name) == 0) {
			return code;
		}
	}

	return DHO_END;
}

char *
code_to_format(int code)
{
	if (code < 0 || code >= DHO_COUNT)
		return "";

	if (dhcp_options[code].format == NULL)
		return "X";

	return dhcp_options[code].format;
}

/*
 * Some option data types cannot be appended or prepended to. For
 * such options change ACTION_PREPEND to ACTION_SUPERSEDE and
 * ACTION_APPEND to ACTION_DEFAULT.
 */
int
code_to_action(int code, int action)
{
	char	*fmt;

	fmt = code_to_format(code);
	if (fmt == NULL || strpbrk(fmt, "ADtX") != NULL)
		return action;

	/*
	 * For our protection all formats which have been excluded shall be
	 * deemed included.
	 */
	switch (action) {
	case ACTION_APPEND:
		action = ACTION_DEFAULT;
		break;
	case ACTION_PREPEND:
		action = ACTION_SUPERSEDE;
		break;
	default:
		break;
	}

	return action;
}

/*
 * Parse options out of the specified buffer, storing addresses of
 * option values in options. Return 0 if errors, 1 if not.
 */
int
parse_option_buffer(struct option_data *options, unsigned char *buffer,
    int length)
{
	unsigned char	*s, *t, *end;
	char		*name, *fmt;
	int		 code, len, newlen;

	s = buffer;
	end = s + length;
	while (s < end) {
		code = s[0];

		/* End options terminate processing. */
		if (code == DHO_END)
			break;

		/* Pad options don't have a length - just skip them. */
		if (code == DHO_PAD) {
			s++;
			continue;
		}

		name = code_to_name(code);
		fmt = code_to_format(code);

		/*
		 * All options other than DHO_PAD and DHO_END have a one-byte
		 * length field. It could be 0! Make sure that the length byte
		 * is present, and all the data is available.
		 */
		if (s + 1 < end) {
			len = s[1];
			if (s + 1 + len < end) {
				; /* option data is all there. */
			} else {
				log_warnx("%s: option %s (%d) larger than "
				    "buffer", log_procname, name, len);
				return 0;
			}
		} else {
			log_warnx("%s: option %s has no length field",
			    log_procname, name);
			return 0;
		}

		/*
		 * Strip trailing NULs from ascii ('t') options. RFC 2132
		 * says "Options containing NVT ASCII data SHOULD NOT include
		 * a trailing NULL; however, the receiver of such options
		 * MUST be prepared to delete trailing nulls if they exist."
		 */
		if (fmt[0] == 't') {
			while (len > 0 && s[len + 1] == '\0')
				len--;
		}

		/*
		 * Concatenate new data + NUL to existing option data.
		 *
		 * Note that the NUL is *not* counted in the len field!
		 */
		newlen = options[code].len + len;
		if ((t = realloc(options[code].data, newlen + 1)) == NULL)
			fatal("option %s", name);

		memcpy(t + options[code].len, &s[2], len);
		t[newlen] = 0;

		options[code].len = newlen;
		options[code].data = t;

		s += s[1] + 2;
	}

	return 1;
}

/*
 * Pack as many options as fit in buflen bytes of buf. Return the
 * offset of the start of the last option copied. A caller can check
 * to see if it's DHO_END to decide if all the options were copied.
 */
int
pack_options(unsigned char *buf, int buflen, struct option_data *options)
{
	int	 ix, incr, length, bufix, code, lastopt = -1;

	memset(buf, 0, buflen);

	memcpy(buf, DHCP_OPTIONS_COOKIE, 4);
	if (options[DHO_DHCP_MESSAGE_TYPE].data != NULL) {
		memcpy(&buf[4], DHCP_OPTIONS_MESSAGE_TYPE, 3);
		buf[6] = options[DHO_DHCP_MESSAGE_TYPE].data[0];
		bufix = 7;
	} else
		bufix = 4;

	for (code = DHO_SUBNET_MASK; code < DHO_END; code++) {
		if (options[code].data == NULL ||
		    code == DHO_DHCP_MESSAGE_TYPE)
			continue;

		length = options[code].len;
		if (bufix + length + 2*((length+254)/255) >= buflen)
			return lastopt;

		lastopt = bufix;
		ix = 0;

		while (length) {
			incr = length > 255 ? 255 : length;

			buf[bufix++] = code;
			buf[bufix++] = incr;
			memcpy(buf + bufix, options[code].data + ix, incr);

			length -= incr;
			ix += incr;
			bufix += incr;
		}
	}

	if (bufix < buflen) {
		buf[bufix] = DHO_END;
		lastopt = bufix;
	}

	return lastopt;
}

/*
 * Use vis() to encode characters of src and append encoded characters onto
 * dst. Also encode ", ', $, ` and \, to ensure resulting strings can be
 * represented as '"' delimited strings and safely passed to scripts. Surround
 * result with double quotes if emit_punct is true.
 */
char *
pretty_print_string(unsigned char *src, size_t srclen, int emit_punct)
{
	static char	 string[8196];
	char		 visbuf[5];
	unsigned char	*origsrc = src;
	size_t		 rslt = 0;

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

	if (emit_punct != 0)
		rslt = strlcat(string, "\"", sizeof(string));

	for (; src < origsrc + srclen; src++) {
		if (*src && strchr("\"'$`\\", *src))
			vis(visbuf, *src, VIS_ALL | VIS_OCTAL, *src+1);
		else
			vis(visbuf, *src, VIS_OCTAL, *src+1);
		rslt = strlcat(string, visbuf, sizeof(string));
	}

	if (emit_punct != 0)
		rslt = strlcat(string, "\"", sizeof(string));

	if (rslt >= sizeof(string))
		return NULL;

	return string;
}

/*
 * Must special case *_CLASSLESS_* route options due to the variable size
 * of the CIDR element in its CIA format.
 */
void
pretty_print_classless_routes(unsigned char *src, size_t srclen,
    unsigned char *buf, size_t buflen)
{
	char		 bitsbuf[5];	/* to hold "/nn " */
	struct in_addr	 dest, netmask, gateway;
	unsigned int	 bits, i, len;
	uint32_t	 m;
	int		 rslt;

	i = 0;
	while (i < srclen) {
		len = extract_route(&src[i], srclen - i, &dest.s_addr,
		    &netmask.s_addr, &gateway.s_addr);
		if (len == 0)
			goto bad;
		i += len;

		m = ntohl(netmask.s_addr);
		bits = 32;
		while ((bits > 0) && ((m & 1) == 0)) {
			m >>= 1;
			bits--;
		}

		rslt = snprintf(bitsbuf, sizeof(bitsbuf), "/%d ", bits);
		if (rslt < 0 || (unsigned int)rslt >= sizeof(bitsbuf))
			goto bad;

		if (strlen(buf) > 0)
			strlcat(buf, ", ", buflen);
		strlcat(buf, inet_ntoa(dest), buflen);
		strlcat(buf, bitsbuf, buflen);
		if (strlcat(buf, inet_ntoa(gateway), buflen) >= buflen)
			goto bad;
	}

	return;

bad:
	memset(buf, 0, buflen);
}

/*
 * Print string containing blank separated list of domain names
 * as a comma separated list of double-quote delimited strings.
 *
 * e.g.  "eng.apple.com. marketing.apple.com."
 *
 * will be translated to
 *
 * "eng.apple.com.", "marketing.apple.com."
 */
void
pretty_print_domain_list(unsigned char *src, size_t srclen,
    unsigned char *buf, size_t buflen)
{
	char	*dupnames, *hn, *inputstring;
	int	 count;

	memset(buf, 0, buflen);

	/*
	 * N.B.: option data is *NOT* guaranteed to be NUL
	 *	 terminated. Avoid strlen(), strdup(), etc.!
	 */
	if (srclen >= DHCP_DOMAIN_SEARCH_LEN || src[0] == '\0')
		return;

	inputstring = malloc(srclen + 1);
	if (inputstring == NULL)
		fatal("domain name list");
	memcpy(inputstring, src, srclen);
	inputstring[srclen] = '\0';
	dupnames = inputstring;

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

	free(dupnames);
	return;

bad:
	free(dupnames);
	memset(buf, 0, buflen);
}

/*
 * Format the specified option so that a human can easily read it.
 */
char *
pretty_print_option(unsigned int code, struct option_data *option,
    int emit_punct)
{
	static char	 optbuf[8192]; /* XXX */
	char		 fmtbuf[32];
	struct in_addr	 foo;
	unsigned char	*data = option->data;
	unsigned char	*dp = data;
	char		*op = optbuf, *buf, *name, *fmt;
	int		 hunksize = 0, numhunk = -1, numelem = 0;
	int		 i, j, k, opleft = sizeof(optbuf);
	int		 len = option->len;
	int		 opcount = 0;
	int32_t		 int32val;
	uint32_t	 uint32val;
	uint16_t	 uint16val;
	char		 comma;

	memset(optbuf, 0, sizeof(optbuf));

	/* Code should be between 0 and 255. */
	if (code > 255) {
		log_warnx("%s: pretty_print_option: bad code %d", log_procname,
		    code);
		goto done;
	}

	if (emit_punct != 0)
		comma = ',';
	else
		comma = ' ';

	/* Handle the princess class options with weirdo formats. */
	switch (code) {
	case DHO_CLASSLESS_STATIC_ROUTES:
	case DHO_CLASSLESS_MS_STATIC_ROUTES:
		pretty_print_classless_routes(dp, len, optbuf, sizeof(optbuf));
		goto done;
	case DHO_DOMAIN_SEARCH:
		pretty_print_domain_list(dp, len, optbuf, sizeof(optbuf));
		goto done;
	default:
		break;
	}

	name = code_to_name(code);
	fmt = code_to_format(code);

	/* Figure out the size of the data. */
	for (i = 0; fmt[i]; i++) {
		if (numhunk == 0) {
			log_warnx("%s: %s: excess information in format "
			    "string: %s", log_procname, name, &fmt[i]);
			goto done;
		}
		numelem++;
		fmtbuf[i] = fmt[i];
		switch (fmt[i]) {
		case 'A':
			--numelem;
			fmtbuf[i] = 0;
			numhunk = 0;
			if (hunksize == 0) {
				log_warnx("%s: %s: no size indicator before A"
				    " in format string: %s", log_procname,
				    name, fmt);
				goto done;
			}
			break;
		case 'X':
			for (k = 0; k < len; k++)
				if (isascii(data[k]) == 0 ||
				    isprint(data[k]) == 0)
					break;
			if (k == len) {
				fmtbuf[i] = 't';
				numhunk = -2;
			} else {
				hunksize++;
				comma = ':';
				numhunk = 0;
			}
			fmtbuf[i + 1] = 0;
			break;
		case 't':
			fmtbuf[i + 1] = 0;
			numhunk = -2;
			break;
		case 'I':
		case 'l':
		case 'L':
			hunksize += 4;
			break;
		case 'S':
			hunksize += 2;
			break;
		case 'B':
		case 'f':
			hunksize++;
			break;
		case 'e':
			break;
		default:
			log_warnx("%s: %s: garbage in format string: %s",
			    log_procname, name, &fmt[i]);
			goto done;
		}
	}

	/* Check for too few bytes. */
	if (hunksize > len) {
		log_warnx("%s: %s: expecting at least %d bytes; got %d",
		    log_procname, name, hunksize, len);
		goto done;
	}
	/* Check for too many bytes. */
	if (numhunk == -1 && hunksize < len) {
		log_warnx("%s: %s: expecting only %d bytes: got %d",
		    log_procname, name, hunksize, len);
		goto done;
	}

	/* If this is an array, compute its size. */
	if (numhunk == 0)
		numhunk = len / hunksize;
	/* See if we got an exact number of hunks. */
	if (numhunk > 0 && numhunk * hunksize != len) {
		log_warnx("%s: %s: expecting %d bytes: got %d", log_procname,
		    name, numhunk * hunksize, len);
		goto done;
	}

	/* A one-hunk array prints the same as a single hunk. */
	if (numhunk < 0)
		numhunk = 1;

	/* Cycle through the array (or hunk) printing the data. */
	for (i = 0; i < numhunk; i++) {
		for (j = 0; j < numelem; j++) {
			switch (fmtbuf[j]) {
			case 't':
				buf = pretty_print_string(dp, len, emit_punct);
				if (buf == NULL)
					opcount = -1;
				else
					opcount = strlcat(op, buf, opleft);
				break;
			case 'I':
				memcpy(&foo.s_addr, dp, sizeof(foo.s_addr));
				opcount = snprintf(op, opleft, "%s",
				    inet_ntoa(foo));
				dp += sizeof(foo.s_addr);
				break;
			case 'l':
				memcpy(&int32val, dp, sizeof(int32val));
				opcount = snprintf(op, opleft, "%d",
				    ntohl(int32val));
				dp += sizeof(int32val);
				break;
			case 'L':
				memcpy(&uint32val, dp, sizeof(uint32val));
				opcount = snprintf(op, opleft, "%u",
				    ntohl(uint32val));
				dp += sizeof(uint32val);
				break;
			case 'S':
				memcpy(&uint16val, dp, sizeof(uint16val));
				opcount = snprintf(op, opleft, "%hu",
				    ntohs(uint16val));
				dp += sizeof(uint16val);
				break;
			case 'B':
				opcount = snprintf(op, opleft, "%u", *dp);
				dp++;
				break;
			case 'X':
				opcount = snprintf(op, opleft, "%x", *dp);
				dp++;
				break;
			case 'f':
				opcount = snprintf(op, opleft, "%s",
				    *dp ? "true" : "false");
				dp++;
				break;
			default:
				log_warnx("%s: unexpected format code %c",
				    log_procname, fmtbuf[j]);
				goto toobig;
			}
			if (opcount < 0 || opcount >= opleft)
				goto toobig;
			opleft -= opcount;
			op += opcount;
			if (j + 1 < numelem && comma != ':') {
				opcount = snprintf(op, opleft, " ");
				if (opcount < 0 || opcount >= opleft)
					goto toobig;
				opleft -= opcount;
				op += opcount;
			}
		}
		if (i + 1 < numhunk) {
			opcount = snprintf(op, opleft, "%c", comma);
			if (opcount < 0 || opcount >= opleft)
				goto toobig;
			opleft -= opcount;
			op += opcount;
		}
	}

done:
	return optbuf;

toobig:
	memset(optbuf, 0, sizeof(optbuf));
	return optbuf;
}

struct option_data *
unpack_options(struct dhcp_packet *packet)
{
	static struct option_data	 options[DHO_COUNT];
	int				 i;

	for (i = 0; i < DHO_COUNT; i++) {
		free(options[i].data);
		options[i].data = NULL;
		options[i].len = 0;
	}

	if (memcmp(&packet->options, DHCP_OPTIONS_COOKIE, 4) == 0) {
		/* Parse the BOOTP/DHCP options field. */
		parse_option_buffer(options, &packet->options[4],
		    sizeof(packet->options) - 4);

		/* DHCP packets can also use overload areas for options. */
		if (options[DHO_DHCP_MESSAGE_TYPE].data != NULL &&
		    options[DHO_DHCP_OPTION_OVERLOAD].data != NULL) {
			if ((options[DHO_DHCP_OPTION_OVERLOAD].data[0] & 1) !=
			    0)
				parse_option_buffer(options,
				    (unsigned char *)packet->file,
				    sizeof(packet->file));
			if ((options[DHO_DHCP_OPTION_OVERLOAD].data[0] & 2) !=
			    0)
				parse_option_buffer(options,
				    (unsigned char *)packet->sname,
				    sizeof(packet->sname));
		}
	}

	return options;
}

void
merge_option_data(char *fmt, struct option_data *first,
    struct option_data *second, struct option_data *dest)
{
	int space = 0;

	free(dest->data);
	dest->data = NULL;
	dest->len = first->len + second->len;
	if (dest->len == 0)
		return;

	/*
	 * N.B.: option data is *NOT* guaranteed to be NUL
	 *	 terminated. Avoid strlen(), strdup(), etc.!
	 */
	if (fmt[0] == 'D') {
		if (first->len > 0 && second->len > 0)
			space = 1;
	}

	dest->len += space;
	dest->data = malloc(dest->len);
	if (dest->data == NULL)
		fatal("merged option data");

	memcpy(dest->data, first->data, first->len);
	if (space == 1)
		dest->data[first->len] = ' ';
	memcpy(dest->data + first->len + space, second->data, second->len);
}