1147072Sbrooks/*	$OpenBSD: options.c,v 1.15 2004/12/26 03:17:07 deraadt Exp $	*/
2147072Sbrooks
3147072Sbrooks/* DHCP options parsing and reassembly. */
4147072Sbrooks
5147072Sbrooks/*
6147072Sbrooks * Copyright (c) 1995, 1996, 1997, 1998 The Internet Software Consortium.
7147072Sbrooks * All rights reserved.
8147072Sbrooks *
9147072Sbrooks * Redistribution and use in source and binary forms, with or without
10147072Sbrooks * modification, are permitted provided that the following conditions
11147072Sbrooks * are met:
12147072Sbrooks *
13147072Sbrooks * 1. Redistributions of source code must retain the above copyright
14147072Sbrooks *    notice, this list of conditions and the following disclaimer.
15147072Sbrooks * 2. Redistributions in binary form must reproduce the above copyright
16147072Sbrooks *    notice, this list of conditions and the following disclaimer in the
17147072Sbrooks *    documentation and/or other materials provided with the distribution.
18147072Sbrooks * 3. Neither the name of The Internet Software Consortium nor the names
19147072Sbrooks *    of its contributors may be used to endorse or promote products derived
20147072Sbrooks *    from this software without specific prior written permission.
21147072Sbrooks *
22147072Sbrooks * THIS SOFTWARE IS PROVIDED BY THE INTERNET SOFTWARE CONSORTIUM AND
23147072Sbrooks * CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
24147072Sbrooks * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
25147072Sbrooks * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
26147072Sbrooks * DISCLAIMED.  IN NO EVENT SHALL THE INTERNET SOFTWARE CONSORTIUM OR
27147072Sbrooks * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
28147072Sbrooks * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
29147072Sbrooks * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
30147072Sbrooks * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
31147072Sbrooks * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
32147072Sbrooks * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
33147072Sbrooks * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
34147072Sbrooks * SUCH DAMAGE.
35147072Sbrooks *
36147072Sbrooks * This software has been written for the Internet Software Consortium
37147072Sbrooks * by Ted Lemon <mellon@fugue.com> in cooperation with Vixie
38147072Sbrooks * Enterprises.  To learn more about the Internet Software Consortium,
39147072Sbrooks * see ``http://www.vix.com/isc''.  To learn more about Vixie
40147072Sbrooks * Enterprises, see ``http://www.vix.com''.
41147072Sbrooks */
42147072Sbrooks
43149399Sbrooks#include <sys/cdefs.h>
44149399Sbrooks__FBSDID("$FreeBSD$");
45149399Sbrooks
46147072Sbrooks#include <ctype.h>
47147072Sbrooks
48147072Sbrooks#define DHCP_OPTION_DATA
49147072Sbrooks#include "dhcpd.h"
50147072Sbrooks
51147072Sbrooksint bad_options = 0;
52147072Sbrooksint bad_options_max = 5;
53147072Sbrooks
54147072Sbrooksvoid	parse_options(struct packet *);
55147072Sbrooksvoid	parse_option_buffer(struct packet *, unsigned char *, int);
56147072Sbrooksint	store_options(unsigned char *, int, struct tree_cache **,
57147072Sbrooks	    unsigned char *, int, int, int, int);
58230597Sdumbbellvoid	expand_domain_search(struct packet *packet);
59230597Sdumbbellint	find_search_domain_name_len(struct option_data *option, int *offset);
60230597Sdumbbellvoid	expand_search_domain_name(struct option_data *option, int *offset,
61230597Sdumbbell	    unsigned char **domain_search);
62147072Sbrooks
63147072Sbrooks
64147072Sbrooks/*
65147072Sbrooks * Parse all available options out of the specified packet.
66147072Sbrooks */
67147072Sbrooksvoid
68147072Sbrooksparse_options(struct packet *packet)
69147072Sbrooks{
70147072Sbrooks	/* Initially, zero all option pointers. */
71147072Sbrooks	memset(packet->options, 0, sizeof(packet->options));
72147072Sbrooks
73147072Sbrooks	/* If we don't see the magic cookie, there's nothing to parse. */
74147072Sbrooks	if (memcmp(packet->raw->options, DHCP_OPTIONS_COOKIE, 4)) {
75147072Sbrooks		packet->options_valid = 0;
76147072Sbrooks		return;
77147072Sbrooks	}
78147072Sbrooks
79147072Sbrooks	/*
80147072Sbrooks	 * Go through the options field, up to the end of the packet or
81147072Sbrooks	 * the End field.
82147072Sbrooks	 */
83147072Sbrooks	parse_option_buffer(packet, &packet->raw->options[4],
84147072Sbrooks	    packet->packet_length - DHCP_FIXED_NON_UDP - 4);
85147072Sbrooks
86147072Sbrooks	/*
87147072Sbrooks	 * If we parsed a DHCP Option Overload option, parse more
88147072Sbrooks	 * options out of the buffer(s) containing them.
89147072Sbrooks	 */
90147072Sbrooks	if (packet->options_valid &&
91147072Sbrooks	    packet->options[DHO_DHCP_OPTION_OVERLOAD].data) {
92147072Sbrooks		if (packet->options[DHO_DHCP_OPTION_OVERLOAD].data[0] & 1)
93147072Sbrooks			parse_option_buffer(packet,
94147072Sbrooks			    (unsigned char *)packet->raw->file,
95147072Sbrooks			    sizeof(packet->raw->file));
96147072Sbrooks		if (packet->options[DHO_DHCP_OPTION_OVERLOAD].data[0] & 2)
97147072Sbrooks			parse_option_buffer(packet,
98147072Sbrooks			    (unsigned char *)packet->raw->sname,
99147072Sbrooks			    sizeof(packet->raw->sname));
100147072Sbrooks	}
101230597Sdumbbell
102230597Sdumbbell	/* Expand DHCP Domain Search option. */
103230597Sdumbbell	if (packet->options_valid) {
104230597Sdumbbell		expand_domain_search(packet);
105230597Sdumbbell	}
106147072Sbrooks}
107147072Sbrooks
108147072Sbrooks/*
109147072Sbrooks * Parse options out of the specified buffer, storing addresses of
110147072Sbrooks * option values in packet->options and setting packet->options_valid if
111147072Sbrooks * no errors are encountered.
112147072Sbrooks */
113147072Sbrooksvoid
114147072Sbrooksparse_option_buffer(struct packet *packet,
115147072Sbrooks    unsigned char *buffer, int length)
116147072Sbrooks{
117147072Sbrooks	unsigned char *s, *t, *end = buffer + length;
118147072Sbrooks	int len, code;
119147072Sbrooks
120147072Sbrooks	for (s = buffer; *s != DHO_END && s < end; ) {
121147072Sbrooks		code = s[0];
122147072Sbrooks
123147072Sbrooks		/* Pad options don't have a length - just skip them. */
124147072Sbrooks		if (code == DHO_PAD) {
125147072Sbrooks			s++;
126147072Sbrooks			continue;
127147072Sbrooks		}
128147072Sbrooks		if (s + 2 > end) {
129147072Sbrooks			len = 65536;
130147072Sbrooks			goto bogus;
131147072Sbrooks		}
132147072Sbrooks
133147072Sbrooks		/*
134147072Sbrooks		 * All other fields (except end, see above) have a
135147072Sbrooks		 * one-byte length.
136147072Sbrooks		 */
137147072Sbrooks		len = s[1];
138147072Sbrooks
139147072Sbrooks		/*
140147072Sbrooks		 * If the length is outrageous, silently skip the rest,
141147072Sbrooks		 * and mark the packet bad. Unfortunately some crappy
142147072Sbrooks		 * dhcp servers always seem to give us garbage on the
143147072Sbrooks		 * end of a packet. so rather than keep refusing, give
144147072Sbrooks		 * up and try to take one after seeing a few without
145147072Sbrooks		 * anything good.
146147072Sbrooks		 */
147147072Sbrooks		if (s + len + 2 > end) {
148147072Sbrooks		    bogus:
149147072Sbrooks			bad_options++;
150147072Sbrooks			warning("option %s (%d) %s.",
151147072Sbrooks			    dhcp_options[code].name, len,
152147072Sbrooks			    "larger than buffer");
153147072Sbrooks			if (bad_options == bad_options_max) {
154147072Sbrooks				packet->options_valid = 1;
155147072Sbrooks				bad_options = 0;
156147072Sbrooks				warning("Many bogus options seen in offers. "
157147072Sbrooks				    "Taking this offer in spite of bogus "
158147072Sbrooks				    "options - hope for the best!");
159147072Sbrooks			} else {
160147072Sbrooks				warning("rejecting bogus offer.");
161147072Sbrooks				packet->options_valid = 0;
162147072Sbrooks			}
163147072Sbrooks			return;
164147072Sbrooks		}
165147072Sbrooks		/*
166147072Sbrooks		 * If we haven't seen this option before, just make
167147072Sbrooks		 * space for it and copy it there.
168147072Sbrooks		 */
169147072Sbrooks		if (!packet->options[code].data) {
170147072Sbrooks			if (!(t = calloc(1, len + 1)))
171147072Sbrooks				error("Can't allocate storage for option %s.",
172147072Sbrooks				    dhcp_options[code].name);
173147072Sbrooks			/*
174147072Sbrooks			 * Copy and NUL-terminate the option (in case
175147072Sbrooks			 * it's an ASCII string.
176147072Sbrooks			 */
177147072Sbrooks			memcpy(t, &s[2], len);
178147072Sbrooks			t[len] = 0;
179147072Sbrooks			packet->options[code].len = len;
180147072Sbrooks			packet->options[code].data = t;
181147072Sbrooks		} else {
182147072Sbrooks			/*
183147072Sbrooks			 * If it's a repeat, concatenate it to whatever
184147072Sbrooks			 * we last saw.   This is really only required
185147072Sbrooks			 * for clients, but what the heck...
186147072Sbrooks			 */
187147072Sbrooks			t = calloc(1, len + packet->options[code].len + 1);
188147072Sbrooks			if (!t)
189147072Sbrooks				error("Can't expand storage for option %s.",
190147072Sbrooks				    dhcp_options[code].name);
191147072Sbrooks			memcpy(t, packet->options[code].data,
192147072Sbrooks				packet->options[code].len);
193147072Sbrooks			memcpy(t + packet->options[code].len,
194147072Sbrooks				&s[2], len);
195147072Sbrooks			packet->options[code].len += len;
196147072Sbrooks			t[packet->options[code].len] = 0;
197147072Sbrooks			free(packet->options[code].data);
198147072Sbrooks			packet->options[code].data = t;
199147072Sbrooks		}
200147072Sbrooks		s += len + 2;
201147072Sbrooks	}
202147072Sbrooks	packet->options_valid = 1;
203147072Sbrooks}
204147072Sbrooks
205147072Sbrooks/*
206230597Sdumbbell * Expand DHCP Domain Search option. The value of this option is
207230597Sdumbbell * encoded like DNS' list of labels. See:
208230597Sdumbbell *   RFC 3397
209230597Sdumbbell *   RFC 1035
210230597Sdumbbell */
211230597Sdumbbellvoid
212230597Sdumbbellexpand_domain_search(struct packet *packet)
213230597Sdumbbell{
214230597Sdumbbell	int offset, expanded_len, next_domain_len;
215230597Sdumbbell	struct option_data *option;
216230597Sdumbbell	unsigned char *domain_search, *cursor;
217230597Sdumbbell
218230597Sdumbbell	if (packet->options[DHO_DOMAIN_SEARCH].data == NULL)
219230597Sdumbbell		return;
220230597Sdumbbell
221230597Sdumbbell	option = &packet->options[DHO_DOMAIN_SEARCH];
222230597Sdumbbell
223230597Sdumbbell	/* Compute final expanded length. */
224230597Sdumbbell	expanded_len = 0;
225230597Sdumbbell	offset = 0;
226230597Sdumbbell	while (offset < option->len) {
227230597Sdumbbell		next_domain_len = find_search_domain_name_len(option, &offset);
228230597Sdumbbell		if (next_domain_len < 0)
229230597Sdumbbell			/* The Domain Search option value is invalid. */
230230597Sdumbbell			return;
231230597Sdumbbell
232230597Sdumbbell		/* We add 1 for the space between domain names. */
233230597Sdumbbell		expanded_len += next_domain_len + 1;
234230597Sdumbbell	}
235230597Sdumbbell	if (expanded_len > 0)
236230597Sdumbbell		/* Remove 1 for the superfluous trailing space. */
237230597Sdumbbell		--expanded_len;
238230597Sdumbbell
239230597Sdumbbell	domain_search = malloc(expanded_len + 1);
240230597Sdumbbell	if (domain_search == NULL)
241230597Sdumbbell		error("Can't allocate storage for expanded domain-search\n");
242230597Sdumbbell
243230597Sdumbbell	offset = 0;
244230597Sdumbbell	cursor = domain_search;
245230597Sdumbbell	while (offset < option->len) {
246230597Sdumbbell		expand_search_domain_name(option, &offset, &cursor);
247230597Sdumbbell		cursor[0] = ' ';
248230597Sdumbbell		cursor++;
249230597Sdumbbell	}
250230597Sdumbbell	domain_search[expanded_len] = '\0';
251230597Sdumbbell
252230597Sdumbbell	free(option->data);
253230597Sdumbbell	option->len = expanded_len;
254230597Sdumbbell	option->data = domain_search;
255230597Sdumbbell}
256230597Sdumbbell
257230597Sdumbbellint
258230597Sdumbbellfind_search_domain_name_len(struct option_data *option, int *offset)
259230597Sdumbbell{
260230597Sdumbbell	int domain_name_len, i, label_len, pointer, pointed_len;
261230597Sdumbbell
262230597Sdumbbell	domain_name_len = 0;
263230597Sdumbbell
264230597Sdumbbell	i = *offset;
265230597Sdumbbell	while (i < option->len) {
266230597Sdumbbell		label_len = option->data[i];
267230597Sdumbbell		if (label_len == 0) {
268230597Sdumbbell			/*
269230597Sdumbbell			 * A zero-length label marks the end of this
270230597Sdumbbell			 * domain name.
271230597Sdumbbell			 */
272230597Sdumbbell			*offset = i + 1;
273230597Sdumbbell			return (domain_name_len);
274230597Sdumbbell		} else if (label_len & 0xC0) {
275230597Sdumbbell			/* This is a pointer to another list of labels. */
276230597Sdumbbell			if (i + 1 >= option->len) {
277230597Sdumbbell				/* The pointer is truncated. */
278230597Sdumbbell				warning("Truncated pointer in DHCP Domain "
279230597Sdumbbell				    "Search option.");
280230597Sdumbbell				return (-1);
281230597Sdumbbell			}
282230597Sdumbbell
283230597Sdumbbell			pointer = ((label_len & ~(0xC0)) << 8) +
284230597Sdumbbell			    option->data[i + 1];
285230597Sdumbbell			if (pointer >= *offset) {
286230597Sdumbbell				/*
287230597Sdumbbell				 * The pointer must indicates a prior
288230597Sdumbbell				 * occurance.
289230597Sdumbbell				 */
290230597Sdumbbell				warning("Invalid forward pointer in DHCP "
291230597Sdumbbell				    "Domain Search option compression.");
292230597Sdumbbell				return (-1);
293230597Sdumbbell			}
294230597Sdumbbell
295230597Sdumbbell			pointed_len = find_search_domain_name_len(option,
296230597Sdumbbell			    &pointer);
297230597Sdumbbell			domain_name_len += pointed_len;
298230597Sdumbbell
299230597Sdumbbell			*offset = i + 2;
300230597Sdumbbell			return (domain_name_len);
301230597Sdumbbell		}
302230597Sdumbbell
303230597Sdumbbell		if (i + label_len >= option->len) {
304230597Sdumbbell			warning("Truncated label in DHCP Domain Search "
305230597Sdumbbell			    "option.");
306230597Sdumbbell			return (-1);
307230597Sdumbbell		}
308230597Sdumbbell
309230597Sdumbbell		/*
310230597Sdumbbell		 * Update the domain name length with the length of the
311230597Sdumbbell		 * current label, plus a trailing dot ('.').
312230597Sdumbbell		 */
313230597Sdumbbell		domain_name_len += label_len + 1;
314230597Sdumbbell
315230597Sdumbbell		/* Move cursor. */
316230597Sdumbbell		i += label_len + 1;
317230597Sdumbbell	}
318230597Sdumbbell
319230597Sdumbbell	warning("Truncated DHCP Domain Search option.");
320230597Sdumbbell
321230597Sdumbbell	return (-1);
322230597Sdumbbell}
323230597Sdumbbell
324230597Sdumbbellvoid
325230597Sdumbbellexpand_search_domain_name(struct option_data *option, int *offset,
326230597Sdumbbell    unsigned char **domain_search)
327230597Sdumbbell{
328230597Sdumbbell	int i, label_len, pointer;
329230597Sdumbbell	unsigned char *cursor;
330230597Sdumbbell
331230597Sdumbbell	/*
332230597Sdumbbell	 * This is the same loop than the function above
333230597Sdumbbell	 * (find_search_domain_name_len). Therefore, we remove checks,
334230597Sdumbbell	 * they're already done. Here, we just make the copy.
335230597Sdumbbell	 */
336230597Sdumbbell	i = *offset;
337230597Sdumbbell	cursor = *domain_search;
338230597Sdumbbell	while (i < option->len) {
339230597Sdumbbell		label_len = option->data[i];
340230597Sdumbbell		if (label_len == 0) {
341230597Sdumbbell			/*
342230597Sdumbbell			 * A zero-length label marks the end of this
343230597Sdumbbell			 * domain name.
344230597Sdumbbell			 */
345230597Sdumbbell			*offset = i + 1;
346230597Sdumbbell			*domain_search = cursor;
347230597Sdumbbell			return;
348230597Sdumbbell		} else if (label_len & 0xC0) {
349230597Sdumbbell			/* This is a pointer to another list of labels. */
350230597Sdumbbell			pointer = ((label_len & ~(0xC0)) << 8) +
351230597Sdumbbell			    option->data[i + 1];
352230597Sdumbbell
353230597Sdumbbell			expand_search_domain_name(option, &pointer, &cursor);
354230597Sdumbbell
355230597Sdumbbell			*offset = i + 2;
356230597Sdumbbell			*domain_search = cursor;
357230597Sdumbbell			return;
358230597Sdumbbell		}
359230597Sdumbbell
360230597Sdumbbell		/* Copy the label found. */
361230597Sdumbbell		memcpy(cursor, option->data + i + 1, label_len);
362230597Sdumbbell		cursor[label_len] = '.';
363230597Sdumbbell
364230597Sdumbbell		/* Move cursor. */
365230597Sdumbbell		i += label_len + 1;
366230597Sdumbbell		cursor += label_len + 1;
367230597Sdumbbell	}
368230597Sdumbbell}
369230597Sdumbbell
370230597Sdumbbell/*
371147072Sbrooks * cons options into a big buffer, and then split them out into the
372147072Sbrooks * three separate buffers if needed.  This allows us to cons up a set of
373147072Sbrooks * vendor options using the same routine.
374147072Sbrooks */
375147072Sbrooksint
376147072Sbrookscons_options(struct packet *inpacket, struct dhcp_packet *outpacket,
377147072Sbrooks    int mms, struct tree_cache **options,
378147072Sbrooks    int overload, /* Overload flags that may be set. */
379147072Sbrooks    int terminate, int bootpp, u_int8_t *prl, int prl_len)
380147072Sbrooks{
381147072Sbrooks	unsigned char priority_list[300], buffer[4096];
382147072Sbrooks	int priority_len, main_buffer_size, mainbufix, bufix;
383147072Sbrooks	int option_size, length;
384147072Sbrooks
385147072Sbrooks	/*
386147072Sbrooks	 * If the client has provided a maximum DHCP message size, use
387147072Sbrooks	 * that; otherwise, if it's BOOTP, only 64 bytes; otherwise use
388147072Sbrooks	 * up to the minimum IP MTU size (576 bytes).
389147072Sbrooks	 *
390147072Sbrooks	 * XXX if a BOOTP client specifies a max message size, we will
391147072Sbrooks	 * honor it.
392147072Sbrooks	 */
393147072Sbrooks	if (!mms &&
394147072Sbrooks	    inpacket &&
395147072Sbrooks	    inpacket->options[DHO_DHCP_MAX_MESSAGE_SIZE].data &&
396147072Sbrooks	    (inpacket->options[DHO_DHCP_MAX_MESSAGE_SIZE].len >=
397147072Sbrooks	    sizeof(u_int16_t)))
398147072Sbrooks		mms = getUShort(
399147072Sbrooks		    inpacket->options[DHO_DHCP_MAX_MESSAGE_SIZE].data);
400147072Sbrooks
401147072Sbrooks	if (mms)
402147072Sbrooks		main_buffer_size = mms - DHCP_FIXED_LEN;
403147072Sbrooks	else if (bootpp)
404147072Sbrooks		main_buffer_size = 64;
405147072Sbrooks	else
406147072Sbrooks		main_buffer_size = 576 - DHCP_FIXED_LEN;
407147072Sbrooks
408147072Sbrooks	if (main_buffer_size > sizeof(buffer))
409147072Sbrooks		main_buffer_size = sizeof(buffer);
410147072Sbrooks
411147072Sbrooks	/* Preload the option priority list with mandatory options. */
412147072Sbrooks	priority_len = 0;
413147072Sbrooks	priority_list[priority_len++] = DHO_DHCP_MESSAGE_TYPE;
414147072Sbrooks	priority_list[priority_len++] = DHO_DHCP_SERVER_IDENTIFIER;
415147072Sbrooks	priority_list[priority_len++] = DHO_DHCP_LEASE_TIME;
416147072Sbrooks	priority_list[priority_len++] = DHO_DHCP_MESSAGE;
417147072Sbrooks
418147072Sbrooks	/*
419147072Sbrooks	 * If the client has provided a list of options that it wishes
420147072Sbrooks	 * returned, use it to prioritize.  Otherwise, prioritize based
421147072Sbrooks	 * on the default priority list.
422147072Sbrooks	 */
423147072Sbrooks	if (inpacket &&
424147072Sbrooks	    inpacket->options[DHO_DHCP_PARAMETER_REQUEST_LIST].data) {
425147072Sbrooks		int prlen =
426147072Sbrooks		    inpacket->options[DHO_DHCP_PARAMETER_REQUEST_LIST].len;
427147072Sbrooks		if (prlen + priority_len > sizeof(priority_list))
428147072Sbrooks			prlen = sizeof(priority_list) - priority_len;
429147072Sbrooks
430147072Sbrooks		memcpy(&priority_list[priority_len],
431147072Sbrooks		    inpacket->options[DHO_DHCP_PARAMETER_REQUEST_LIST].data,
432147072Sbrooks		    prlen);
433147072Sbrooks		priority_len += prlen;
434147072Sbrooks		prl = priority_list;
435147072Sbrooks	} else if (prl) {
436147072Sbrooks		if (prl_len + priority_len > sizeof(priority_list))
437147072Sbrooks			prl_len = sizeof(priority_list) - priority_len;
438147072Sbrooks
439147072Sbrooks		memcpy(&priority_list[priority_len], prl, prl_len);
440147072Sbrooks		priority_len += prl_len;
441147072Sbrooks		prl = priority_list;
442147072Sbrooks	} else {
443147072Sbrooks		memcpy(&priority_list[priority_len],
444147072Sbrooks		    dhcp_option_default_priority_list,
445147072Sbrooks		    sizeof_dhcp_option_default_priority_list);
446147072Sbrooks		priority_len += sizeof_dhcp_option_default_priority_list;
447147072Sbrooks	}
448147072Sbrooks
449147072Sbrooks	/* Copy the options into the big buffer... */
450147072Sbrooks	option_size = store_options(
451147072Sbrooks	    buffer,
452147072Sbrooks	    (main_buffer_size - 7 + ((overload & 1) ? DHCP_FILE_LEN : 0) +
453147072Sbrooks		((overload & 2) ? DHCP_SNAME_LEN : 0)),
454147072Sbrooks	    options, priority_list, priority_len, main_buffer_size,
455147072Sbrooks	    (main_buffer_size + ((overload & 1) ? DHCP_FILE_LEN : 0)),
456147072Sbrooks	    terminate);
457147072Sbrooks
458147072Sbrooks	/* Put the cookie up front... */
459147072Sbrooks	memcpy(outpacket->options, DHCP_OPTIONS_COOKIE, 4);
460147072Sbrooks	mainbufix = 4;
461147072Sbrooks
462147072Sbrooks	/*
463147072Sbrooks	 * If we're going to have to overload, store the overload option
464147072Sbrooks	 * at the beginning.  If we can, though, just store the whole
465147072Sbrooks	 * thing in the packet's option buffer and leave it at that.
466147072Sbrooks	 */
467147072Sbrooks	if (option_size <= main_buffer_size - mainbufix) {
468147072Sbrooks		memcpy(&outpacket->options[mainbufix],
469147072Sbrooks		    buffer, option_size);
470147072Sbrooks		mainbufix += option_size;
471147072Sbrooks		if (mainbufix < main_buffer_size)
472147072Sbrooks			outpacket->options[mainbufix++] = DHO_END;
473147072Sbrooks		length = DHCP_FIXED_NON_UDP + mainbufix;
474147072Sbrooks	} else {
475147072Sbrooks		outpacket->options[mainbufix++] = DHO_DHCP_OPTION_OVERLOAD;
476147072Sbrooks		outpacket->options[mainbufix++] = 1;
477147072Sbrooks		if (option_size >
478147072Sbrooks		    main_buffer_size - mainbufix + DHCP_FILE_LEN)
479147072Sbrooks			outpacket->options[mainbufix++] = 3;
480147072Sbrooks		else
481147072Sbrooks			outpacket->options[mainbufix++] = 1;
482147072Sbrooks
483147072Sbrooks		memcpy(&outpacket->options[mainbufix],
484147072Sbrooks		    buffer, main_buffer_size - mainbufix);
485147072Sbrooks		bufix = main_buffer_size - mainbufix;
486147072Sbrooks		length = DHCP_FIXED_NON_UDP + mainbufix;
487147072Sbrooks		if (overload & 1) {
488147072Sbrooks			if (option_size - bufix <= DHCP_FILE_LEN) {
489147072Sbrooks				memcpy(outpacket->file,
490147072Sbrooks				    &buffer[bufix], option_size - bufix);
491147072Sbrooks				mainbufix = option_size - bufix;
492147072Sbrooks				if (mainbufix < DHCP_FILE_LEN)
493147072Sbrooks					outpacket->file[mainbufix++] = (char)DHO_END;
494147072Sbrooks				while (mainbufix < DHCP_FILE_LEN)
495147072Sbrooks					outpacket->file[mainbufix++] = (char)DHO_PAD;
496147072Sbrooks			} else {
497147072Sbrooks				memcpy(outpacket->file,
498147072Sbrooks				    &buffer[bufix], DHCP_FILE_LEN);
499147072Sbrooks				bufix += DHCP_FILE_LEN;
500147072Sbrooks			}
501147072Sbrooks		}
502147072Sbrooks		if ((overload & 2) && option_size < bufix) {
503147072Sbrooks			memcpy(outpacket->sname,
504147072Sbrooks			    &buffer[bufix], option_size - bufix);
505147072Sbrooks
506147072Sbrooks			mainbufix = option_size - bufix;
507147072Sbrooks			if (mainbufix < DHCP_SNAME_LEN)
508147072Sbrooks				outpacket->file[mainbufix++] = (char)DHO_END;
509147072Sbrooks			while (mainbufix < DHCP_SNAME_LEN)
510147072Sbrooks				outpacket->file[mainbufix++] = (char)DHO_PAD;
511147072Sbrooks		}
512147072Sbrooks	}
513147072Sbrooks	return (length);
514147072Sbrooks}
515147072Sbrooks
516147072Sbrooks/*
517147072Sbrooks * Store all the requested options into the requested buffer.
518147072Sbrooks */
519147072Sbrooksint
520147072Sbrooksstore_options(unsigned char *buffer, int buflen, struct tree_cache **options,
521147072Sbrooks    unsigned char *priority_list, int priority_len, int first_cutoff,
522147072Sbrooks    int second_cutoff, int terminate)
523147072Sbrooks{
524147072Sbrooks	int bufix = 0, option_stored[256], i, ix, tto;
525147072Sbrooks
526147072Sbrooks	/* Zero out the stored-lengths array. */
527147072Sbrooks	memset(option_stored, 0, sizeof(option_stored));
528147072Sbrooks
529147072Sbrooks	/*
530147072Sbrooks	 * Copy out the options in the order that they appear in the
531147072Sbrooks	 * priority list...
532147072Sbrooks	 */
533147072Sbrooks	for (i = 0; i < priority_len; i++) {
534147072Sbrooks		/* Code for next option to try to store. */
535147072Sbrooks		int code = priority_list[i];
536147072Sbrooks		int optstart;
537147072Sbrooks
538147072Sbrooks		/*
539147072Sbrooks		 * Number of bytes left to store (some may already have
540147072Sbrooks		 * been stored by a previous pass).
541147072Sbrooks		 */
542147072Sbrooks		int length;
543147072Sbrooks
544147072Sbrooks		/* If no data is available for this option, skip it. */
545147072Sbrooks		if (!options[code]) {
546147072Sbrooks			continue;
547147072Sbrooks		}
548147072Sbrooks
549147072Sbrooks		/*
550147072Sbrooks		 * The client could ask for things that are mandatory,
551147072Sbrooks		 * in which case we should avoid storing them twice...
552147072Sbrooks		 */
553147072Sbrooks		if (option_stored[code])
554147072Sbrooks			continue;
555147072Sbrooks		option_stored[code] = 1;
556147072Sbrooks
557147072Sbrooks		/* We should now have a constant length for the option. */
558147072Sbrooks		length = options[code]->len;
559147072Sbrooks
560147072Sbrooks		/* Do we add a NUL? */
561147072Sbrooks		if (terminate && dhcp_options[code].format[0] == 't') {
562147072Sbrooks			length++;
563147072Sbrooks			tto = 1;
564147072Sbrooks		} else
565147072Sbrooks			tto = 0;
566147072Sbrooks
567147072Sbrooks		/* Try to store the option. */
568147072Sbrooks
569147072Sbrooks		/*
570147072Sbrooks		 * If the option's length is more than 255, we must
571147072Sbrooks		 * store it in multiple hunks.   Store 255-byte hunks
572147072Sbrooks		 * first.  However, in any case, if the option data will
573147072Sbrooks		 * cross a buffer boundary, split it across that
574147072Sbrooks		 * boundary.
575147072Sbrooks		 */
576147072Sbrooks		ix = 0;
577147072Sbrooks
578147072Sbrooks		optstart = bufix;
579147072Sbrooks		while (length) {
580147072Sbrooks			unsigned char incr = length > 255 ? 255 : length;
581147072Sbrooks
582147072Sbrooks			/*
583147072Sbrooks			 * If this hunk of the buffer will cross a
584147072Sbrooks			 * boundary, only go up to the boundary in this
585147072Sbrooks			 * pass.
586147072Sbrooks			 */
587147072Sbrooks			if (bufix < first_cutoff &&
588147072Sbrooks			    bufix + incr > first_cutoff)
589147072Sbrooks				incr = first_cutoff - bufix;
590147072Sbrooks			else if (bufix < second_cutoff &&
591147072Sbrooks			    bufix + incr > second_cutoff)
592147072Sbrooks				incr = second_cutoff - bufix;
593147072Sbrooks
594147072Sbrooks			/*
595147072Sbrooks			 * If this option is going to overflow the
596147072Sbrooks			 * buffer, skip it.
597147072Sbrooks			 */
598147072Sbrooks			if (bufix + 2 + incr > buflen) {
599147072Sbrooks				bufix = optstart;
600147072Sbrooks				break;
601147072Sbrooks			}
602147072Sbrooks
603147072Sbrooks			/* Everything looks good - copy it in! */
604147072Sbrooks			buffer[bufix] = code;
605147072Sbrooks			buffer[bufix + 1] = incr;
606147072Sbrooks			if (tto && incr == length) {
607147072Sbrooks				memcpy(buffer + bufix + 2,
608147072Sbrooks				    options[code]->value + ix, incr - 1);
609147072Sbrooks				buffer[bufix + 2 + incr - 1] = 0;
610147072Sbrooks			} else
611147072Sbrooks				memcpy(buffer + bufix + 2,
612147072Sbrooks				    options[code]->value + ix, incr);
613147072Sbrooks			length -= incr;
614147072Sbrooks			ix += incr;
615147072Sbrooks			bufix += 2 + incr;
616147072Sbrooks		}
617147072Sbrooks	}
618147072Sbrooks	return (bufix);
619147072Sbrooks}
620147072Sbrooks
621147072Sbrooks/*
622147072Sbrooks * Format the specified option so that a human can easily read it.
623147072Sbrooks */
624147072Sbrookschar *
625147072Sbrookspretty_print_option(unsigned int code, unsigned char *data, int len,
626147072Sbrooks    int emit_commas, int emit_quotes)
627147072Sbrooks{
628147072Sbrooks	static char optbuf[32768]; /* XXX */
629147072Sbrooks	int hunksize = 0, numhunk = -1, numelem = 0;
630147072Sbrooks	char fmtbuf[32], *op = optbuf;
631147072Sbrooks	int i, j, k, opleft = sizeof(optbuf);
632147072Sbrooks	unsigned char *dp = data;
633147072Sbrooks	struct in_addr foo;
634147072Sbrooks	char comma;
635147072Sbrooks
636147072Sbrooks	/* Code should be between 0 and 255. */
637147072Sbrooks	if (code > 255)
638147072Sbrooks		error("pretty_print_option: bad code %d", code);
639147072Sbrooks
640147072Sbrooks	if (emit_commas)
641147072Sbrooks		comma = ',';
642147072Sbrooks	else
643147072Sbrooks		comma = ' ';
644147072Sbrooks
645147072Sbrooks	/* Figure out the size of the data. */
646147072Sbrooks	for (i = 0; dhcp_options[code].format[i]; i++) {
647147072Sbrooks		if (!numhunk) {
648147072Sbrooks			warning("%s: Excess information in format string: %s",
649147072Sbrooks			    dhcp_options[code].name,
650147072Sbrooks			    &(dhcp_options[code].format[i]));
651147072Sbrooks			break;
652147072Sbrooks		}
653147072Sbrooks		numelem++;
654147072Sbrooks		fmtbuf[i] = dhcp_options[code].format[i];
655147072Sbrooks		switch (dhcp_options[code].format[i]) {
656147072Sbrooks		case 'A':
657147072Sbrooks			--numelem;
658147072Sbrooks			fmtbuf[i] = 0;
659147072Sbrooks			numhunk = 0;
660147072Sbrooks			break;
661147072Sbrooks		case 'X':
662147072Sbrooks			for (k = 0; k < len; k++)
663147072Sbrooks				if (!isascii(data[k]) ||
664147072Sbrooks				    !isprint(data[k]))
665147072Sbrooks					break;
666147072Sbrooks			if (k == len) {
667147072Sbrooks				fmtbuf[i] = 't';
668147072Sbrooks				numhunk = -2;
669147072Sbrooks			} else {
670147072Sbrooks				fmtbuf[i] = 'x';
671147072Sbrooks				hunksize++;
672147072Sbrooks				comma = ':';
673147072Sbrooks				numhunk = 0;
674147072Sbrooks			}
675147072Sbrooks			fmtbuf[i + 1] = 0;
676147072Sbrooks			break;
677147072Sbrooks		case 't':
678147072Sbrooks			fmtbuf[i] = 't';
679147072Sbrooks			fmtbuf[i + 1] = 0;
680147072Sbrooks			numhunk = -2;
681147072Sbrooks			break;
682147072Sbrooks		case 'I':
683147072Sbrooks		case 'l':
684147072Sbrooks		case 'L':
685147072Sbrooks			hunksize += 4;
686147072Sbrooks			break;
687147072Sbrooks		case 's':
688147072Sbrooks		case 'S':
689147072Sbrooks			hunksize += 2;
690147072Sbrooks			break;
691147072Sbrooks		case 'b':
692147072Sbrooks		case 'B':
693147072Sbrooks		case 'f':
694147072Sbrooks			hunksize++;
695147072Sbrooks			break;
696147072Sbrooks		case 'e':
697147072Sbrooks			break;
698147072Sbrooks		default:
699147072Sbrooks			warning("%s: garbage in format string: %s",
700147072Sbrooks			    dhcp_options[code].name,
701147072Sbrooks			    &(dhcp_options[code].format[i]));
702147072Sbrooks			break;
703147072Sbrooks		}
704147072Sbrooks	}
705147072Sbrooks
706147072Sbrooks	/* Check for too few bytes... */
707147072Sbrooks	if (hunksize > len) {
708147072Sbrooks		warning("%s: expecting at least %d bytes; got %d",
709147072Sbrooks		    dhcp_options[code].name, hunksize, len);
710147072Sbrooks		return ("<error>");
711147072Sbrooks	}
712147072Sbrooks	/* Check for too many bytes... */
713147072Sbrooks	if (numhunk == -1 && hunksize < len)
714147072Sbrooks		warning("%s: %d extra bytes",
715147072Sbrooks		    dhcp_options[code].name, len - hunksize);
716147072Sbrooks
717147072Sbrooks	/* If this is an array, compute its size. */
718147072Sbrooks	if (!numhunk)
719147072Sbrooks		numhunk = len / hunksize;
720147072Sbrooks	/* See if we got an exact number of hunks. */
721147072Sbrooks	if (numhunk > 0 && numhunk * hunksize < len)
722147072Sbrooks		warning("%s: %d extra bytes at end of array",
723147072Sbrooks		    dhcp_options[code].name, len - numhunk * hunksize);
724147072Sbrooks
725147072Sbrooks	/* A one-hunk array prints the same as a single hunk. */
726147072Sbrooks	if (numhunk < 0)
727147072Sbrooks		numhunk = 1;
728147072Sbrooks
729147072Sbrooks	/* Cycle through the array (or hunk) printing the data. */
730147072Sbrooks	for (i = 0; i < numhunk; i++) {
731147072Sbrooks		for (j = 0; j < numelem; j++) {
732147072Sbrooks			int opcount;
733147072Sbrooks			switch (fmtbuf[j]) {
734147072Sbrooks			case 't':
735147072Sbrooks				if (emit_quotes) {
736147072Sbrooks					*op++ = '"';
737147072Sbrooks					opleft--;
738147072Sbrooks				}
739147072Sbrooks				for (; dp < data + len; dp++) {
740147072Sbrooks					if (!isascii(*dp) ||
741147072Sbrooks					    !isprint(*dp)) {
742147072Sbrooks						if (dp + 1 != data + len ||
743147072Sbrooks						    *dp != 0) {
744147072Sbrooks							snprintf(op, opleft,
745147072Sbrooks							    "\\%03o", *dp);
746147072Sbrooks							op += 4;
747147072Sbrooks							opleft -= 4;
748147072Sbrooks						}
749147072Sbrooks					} else if (*dp == '"' ||
750147072Sbrooks					    *dp == '\'' ||
751147072Sbrooks					    *dp == '$' ||
752147072Sbrooks					    *dp == '`' ||
753147072Sbrooks					    *dp == '\\') {
754147072Sbrooks						*op++ = '\\';
755147072Sbrooks						*op++ = *dp;
756147072Sbrooks						opleft -= 2;
757147072Sbrooks					} else {
758147072Sbrooks						*op++ = *dp;
759147072Sbrooks						opleft--;
760147072Sbrooks					}
761147072Sbrooks				}
762147072Sbrooks				if (emit_quotes) {
763147072Sbrooks					*op++ = '"';
764147072Sbrooks					opleft--;
765147072Sbrooks				}
766147072Sbrooks
767147072Sbrooks				*op = 0;
768147072Sbrooks				break;
769147072Sbrooks			case 'I':
770147072Sbrooks				foo.s_addr = htonl(getULong(dp));
771147072Sbrooks				opcount = strlcpy(op, inet_ntoa(foo), opleft);
772147072Sbrooks				if (opcount >= opleft)
773147072Sbrooks					goto toobig;
774147072Sbrooks				opleft -= opcount;
775147072Sbrooks				dp += 4;
776147072Sbrooks				break;
777147072Sbrooks			case 'l':
778147072Sbrooks				opcount = snprintf(op, opleft, "%ld",
779147072Sbrooks				    (long)getLong(dp));
780147072Sbrooks				if (opcount >= opleft || opcount == -1)
781147072Sbrooks					goto toobig;
782147072Sbrooks				opleft -= opcount;
783147072Sbrooks				dp += 4;
784147072Sbrooks				break;
785147072Sbrooks			case 'L':
786147072Sbrooks				opcount = snprintf(op, opleft, "%ld",
787147072Sbrooks				    (unsigned long)getULong(dp));
788147072Sbrooks				if (opcount >= opleft || opcount == -1)
789147072Sbrooks					goto toobig;
790147072Sbrooks				opleft -= opcount;
791147072Sbrooks				dp += 4;
792147072Sbrooks				break;
793147072Sbrooks			case 's':
794147072Sbrooks				opcount = snprintf(op, opleft, "%d",
795147072Sbrooks				    getShort(dp));
796147072Sbrooks				if (opcount >= opleft || opcount == -1)
797147072Sbrooks					goto toobig;
798147072Sbrooks				opleft -= opcount;
799147072Sbrooks				dp += 2;
800147072Sbrooks				break;
801147072Sbrooks			case 'S':
802147072Sbrooks				opcount = snprintf(op, opleft, "%d",
803147072Sbrooks				    getUShort(dp));
804147072Sbrooks				if (opcount >= opleft || opcount == -1)
805147072Sbrooks					goto toobig;
806147072Sbrooks				opleft -= opcount;
807147072Sbrooks				dp += 2;
808147072Sbrooks				break;
809147072Sbrooks			case 'b':
810147072Sbrooks				opcount = snprintf(op, opleft, "%d",
811147072Sbrooks				    *(char *)dp++);
812147072Sbrooks				if (opcount >= opleft || opcount == -1)
813147072Sbrooks					goto toobig;
814147072Sbrooks				opleft -= opcount;
815147072Sbrooks				break;
816147072Sbrooks			case 'B':
817147072Sbrooks				opcount = snprintf(op, opleft, "%d", *dp++);
818147072Sbrooks				if (opcount >= opleft || opcount == -1)
819147072Sbrooks					goto toobig;
820147072Sbrooks				opleft -= opcount;
821147072Sbrooks				break;
822147072Sbrooks			case 'x':
823147072Sbrooks				opcount = snprintf(op, opleft, "%x", *dp++);
824147072Sbrooks				if (opcount >= opleft || opcount == -1)
825147072Sbrooks					goto toobig;
826147072Sbrooks				opleft -= opcount;
827147072Sbrooks				break;
828147072Sbrooks			case 'f':
829147072Sbrooks				opcount = strlcpy(op,
830147072Sbrooks				    *dp++ ? "true" : "false", opleft);
831147072Sbrooks				if (opcount >= opleft)
832147072Sbrooks					goto toobig;
833147072Sbrooks				opleft -= opcount;
834147072Sbrooks				break;
835147072Sbrooks			default:
836147072Sbrooks				warning("Unexpected format code %c", fmtbuf[j]);
837147072Sbrooks			}
838147072Sbrooks			op += strlen(op);
839147072Sbrooks			opleft -= strlen(op);
840147072Sbrooks			if (opleft < 1)
841147072Sbrooks				goto toobig;
842147072Sbrooks			if (j + 1 < numelem && comma != ':') {
843147072Sbrooks				*op++ = ' ';
844147072Sbrooks				opleft--;
845147072Sbrooks			}
846147072Sbrooks		}
847147072Sbrooks		if (i + 1 < numhunk) {
848147072Sbrooks			*op++ = comma;
849147072Sbrooks			opleft--;
850147072Sbrooks		}
851147072Sbrooks		if (opleft < 1)
852147072Sbrooks			goto toobig;
853147072Sbrooks
854147072Sbrooks	}
855147072Sbrooks	return (optbuf);
856147072Sbrooks toobig:
857147072Sbrooks	warning("dhcp option too large");
858147072Sbrooks	return ("<error>");
859147072Sbrooks}
860147072Sbrooks
861147072Sbrooksvoid
862147072Sbrooksdo_packet(struct interface_info *interface, struct dhcp_packet *packet,
863147072Sbrooks    int len, unsigned int from_port, struct iaddr from, struct hardware *hfrom)
864147072Sbrooks{
865147072Sbrooks	struct packet tp;
866147072Sbrooks	int i;
867147072Sbrooks
868147072Sbrooks	if (packet->hlen > sizeof(packet->chaddr)) {
869147072Sbrooks		note("Discarding packet with invalid hlen.");
870147072Sbrooks		return;
871147072Sbrooks	}
872147072Sbrooks
873147072Sbrooks	memset(&tp, 0, sizeof(tp));
874147072Sbrooks	tp.raw = packet;
875147072Sbrooks	tp.packet_length = len;
876147072Sbrooks	tp.client_port = from_port;
877147072Sbrooks	tp.client_addr = from;
878147072Sbrooks	tp.interface = interface;
879147072Sbrooks	tp.haddr = hfrom;
880147072Sbrooks
881147072Sbrooks	parse_options(&tp);
882147072Sbrooks	if (tp.options_valid &&
883147072Sbrooks	    tp.options[DHO_DHCP_MESSAGE_TYPE].data)
884147072Sbrooks		tp.packet_type = tp.options[DHO_DHCP_MESSAGE_TYPE].data[0];
885147072Sbrooks	if (tp.packet_type)
886147072Sbrooks		dhcp(&tp);
887147072Sbrooks	else
888147072Sbrooks		bootp(&tp);
889147072Sbrooks
890147072Sbrooks	/* Free the data associated with the options. */
891147072Sbrooks	for (i = 0; i < 256; i++)
892147072Sbrooks		if (tp.options[i].len && tp.options[i].data)
893147072Sbrooks			free(tp.options[i].data);
894147072Sbrooks}
895