xref: /openbsd/sbin/dhclient/options.c (revision 642cc348)
1*642cc348Skrw /*	$OpenBSD: options.c,v 1.83 2017/03/26 21:33:36 krw Exp $	*/
29a2590e5Sderaadt 
3e7eb2effShenning /* DHCP options parsing and reassembly. */
49a2590e5Sderaadt 
59a2590e5Sderaadt /*
69a2590e5Sderaadt  * Copyright (c) 1995, 1996, 1997, 1998 The Internet Software Consortium.
79a2590e5Sderaadt  * All rights reserved.
89a2590e5Sderaadt  *
99a2590e5Sderaadt  * Redistribution and use in source and binary forms, with or without
109a2590e5Sderaadt  * modification, are permitted provided that the following conditions
119a2590e5Sderaadt  * are met:
129a2590e5Sderaadt  *
139a2590e5Sderaadt  * 1. Redistributions of source code must retain the above copyright
149a2590e5Sderaadt  *    notice, this list of conditions and the following disclaimer.
159a2590e5Sderaadt  * 2. Redistributions in binary form must reproduce the above copyright
169a2590e5Sderaadt  *    notice, this list of conditions and the following disclaimer in the
179a2590e5Sderaadt  *    documentation and/or other materials provided with the distribution.
189a2590e5Sderaadt  * 3. Neither the name of The Internet Software Consortium nor the names
199a2590e5Sderaadt  *    of its contributors may be used to endorse or promote products derived
209a2590e5Sderaadt  *    from this software without specific prior written permission.
219a2590e5Sderaadt  *
229a2590e5Sderaadt  * THIS SOFTWARE IS PROVIDED BY THE INTERNET SOFTWARE CONSORTIUM AND
239a2590e5Sderaadt  * CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
249a2590e5Sderaadt  * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
259a2590e5Sderaadt  * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
269a2590e5Sderaadt  * DISCLAIMED.  IN NO EVENT SHALL THE INTERNET SOFTWARE CONSORTIUM OR
279a2590e5Sderaadt  * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
289a2590e5Sderaadt  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
299a2590e5Sderaadt  * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
309a2590e5Sderaadt  * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
319a2590e5Sderaadt  * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
329a2590e5Sderaadt  * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
339a2590e5Sderaadt  * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
349a2590e5Sderaadt  * SUCH DAMAGE.
359a2590e5Sderaadt  *
369a2590e5Sderaadt  * This software has been written for the Internet Software Consortium
379a2590e5Sderaadt  * by Ted Lemon <mellon@fugue.com> in cooperation with Vixie
389a2590e5Sderaadt  * Enterprises.  To learn more about the Internet Software Consortium,
399a2590e5Sderaadt  * see ``http://www.vix.com/isc''.  To learn more about Vixie
409a2590e5Sderaadt  * Enterprises, see ``http://www.vix.com''.
419a2590e5Sderaadt  */
429a2590e5Sderaadt 
43711cae1eSkrw #include <sys/queue.h>
44711cae1eSkrw #include <sys/socket.h>
459a2590e5Sderaadt 
46711cae1eSkrw #include <arpa/inet.h>
47711cae1eSkrw 
48711cae1eSkrw #include <net/if.h>
49711cae1eSkrw 
50711cae1eSkrw #include <netinet/in.h>
51711cae1eSkrw #include <netinet/if_ether.h>
52711cae1eSkrw 
53711cae1eSkrw #include <ctype.h>
54711cae1eSkrw #include <signal.h>
55711cae1eSkrw #include <stdio.h>
56711cae1eSkrw #include <stdlib.h>
57711cae1eSkrw #include <string.h>
588d2bd14bSkrw #include <vis.h>
598d2bd14bSkrw 
60711cae1eSkrw #include "dhcp.h"
61711cae1eSkrw #include "dhcpd.h"
62385a6373Skrw #include "log.h"
63711cae1eSkrw 
6402e02bd5Skrw int parse_option_buffer(struct option_data *, unsigned char *, int);
65968fe952Skrw int expand_search_domain_name(unsigned char *, size_t, int *, unsigned char *);
669a2590e5Sderaadt 
67c714dadcShenning /*
68c714dadcShenning  * Parse options out of the specified buffer, storing addresses of
6992018899Skrw  * option values in options. Return 0 if errors, 1 if not.
70c714dadcShenning  */
7102e02bd5Skrw int
724f062ee3Skrw parse_option_buffer(struct option_data *options, unsigned char *buffer,
734f062ee3Skrw     int length)
749a2590e5Sderaadt {
75285f06efSderaadt 	unsigned char *s, *t, *end = buffer + length;
76285f06efSderaadt 	int len, code;
779a2590e5Sderaadt 
789a2590e5Sderaadt 	for (s = buffer; *s != DHO_END && s < end; ) {
799a2590e5Sderaadt 		code = s[0];
809a2590e5Sderaadt 
819a2590e5Sderaadt 		/* Pad options don't have a length - just skip them. */
829a2590e5Sderaadt 		if (code == DHO_PAD) {
83f1e89499Shenning 			s++;
849a2590e5Sderaadt 			continue;
859a2590e5Sderaadt 		}
869a2590e5Sderaadt 
87c714dadcShenning 		/*
8899c003b1Skrw 		 * All options other than DHO_PAD and DHO_END have a one-byte
8999c003b1Skrw 		 * length field. It could be 0! Make sure that the length byte
9099c003b1Skrw 		 * is present, and all the data is available.
91c714dadcShenning 		 */
9299c003b1Skrw 		if (s + 1 < end) {
939a2590e5Sderaadt 			len = s[1];
9499c003b1Skrw 			if (s + 1 + len < end) {
9599c003b1Skrw 				; /* option data is all there. */
9699c003b1Skrw 			} else {
97385a6373Skrw 				log_warnx("option %s (%d) larger than buffer.",
98b6fc88b9Skrw 				    dhcp_options[code].name, len);
9902e02bd5Skrw 				return (0);
1009a2590e5Sderaadt 			}
10199c003b1Skrw 		} else {
102385a6373Skrw 			log_warnx("option %s has no length field.",
10399c003b1Skrw 			    dhcp_options[code].name);
10499c003b1Skrw 			return (0);
10599c003b1Skrw 		}
106df453039Skrw 
107df453039Skrw 		/*
108df453039Skrw 		 * Strip trailing NULs from ascii ('t') options. They
109df453039Skrw 		 * will be treated as DHO_PAD options. i.e. ignored. RFC 2132
110df453039Skrw 		 * says "Options containing NVT ASCII data SHOULD NOT include
111df453039Skrw 		 * a trailing NULL; however, the receiver of such options
112df453039Skrw 		 * MUST be prepared to delete trailing nulls if they exist."
113df453039Skrw 		 */
114df453039Skrw 		if (dhcp_options[code].format[0] == 't') {
11599c003b1Skrw 			while (len > 0 && s[len + 1] == '\0')
11699c003b1Skrw 				len--;
117df453039Skrw 		}
118df453039Skrw 
119c714dadcShenning 		/*
120c714dadcShenning 		 * If we haven't seen this option before, just make
121c714dadcShenning 		 * space for it and copy it there.
122c714dadcShenning 		 */
1234f062ee3Skrw 		if (!options[code].data) {
1248e916ab9Shenning 			if (!(t = calloc(1, len + 1)))
125385a6373Skrw 				fatalx("Can't allocate storage for option %s.",
1269a2590e5Sderaadt 				    dhcp_options[code].name);
127c714dadcShenning 			/*
128c714dadcShenning 			 * Copy and NUL-terminate the option (in case
129cff08477Sstevesk 			 * it's an ASCII string).
130c714dadcShenning 			 */
1319a2590e5Sderaadt 			memcpy(t, &s[2], len);
1329a2590e5Sderaadt 			t[len] = 0;
1334f062ee3Skrw 			options[code].len = len;
1344f062ee3Skrw 			options[code].data = t;
1359a2590e5Sderaadt 		} else {
136c714dadcShenning 			/*
137c714dadcShenning 			 * If it's a repeat, concatenate it to whatever
13892018899Skrw 			 * we last saw.
139c714dadcShenning 			 */
1404f062ee3Skrw 			t = calloc(1, len + options[code].len + 1);
1419a2590e5Sderaadt 			if (!t)
142385a6373Skrw 				fatalx("Can't expand storage for option %s.",
1439a2590e5Sderaadt 				    dhcp_options[code].name);
1444f062ee3Skrw 			memcpy(t, options[code].data, options[code].len);
1454f062ee3Skrw 			memcpy(t + options[code].len, &s[2], len);
1464f062ee3Skrw 			options[code].len += len;
1474f062ee3Skrw 			t[options[code].len] = 0;
1484f062ee3Skrw 			free(options[code].data);
1494f062ee3Skrw 			options[code].data = t;
1509a2590e5Sderaadt 		}
1519a2590e5Sderaadt 		s += len + 2;
1529a2590e5Sderaadt 	}
15302e02bd5Skrw 
15402e02bd5Skrw 	return (1);
1559a2590e5Sderaadt }
1569a2590e5Sderaadt 
157c714dadcShenning /*
15896978980Skrw  * Copy as many options as fit in buflen bytes of buf. Return the
15996978980Skrw  * offset of the start of the last option copied. A caller can check
16096978980Skrw  * to see if it's DHO_END to decide if all the options were copied.
161c714dadcShenning  */
162c714dadcShenning int
1636a2ee11aSmpi cons_options(struct interface_info *ifi, struct option_data *options)
1649a2590e5Sderaadt {
1656a2ee11aSmpi 	struct client_state *client = ifi->client;
166e7cf2d10Skrw 	unsigned char *buf = client->bootrequest_packet.options;
167d6a67f0fSkrw 	int buflen = 576 - DHCP_FIXED_LEN;
16896978980Skrw 	int ix, incr, length, bufix, code, lastopt = -1;
1699a2590e5Sderaadt 
170736b0ed2Skrw 	memset(buf, 0, buflen);
1719a2590e5Sderaadt 
17296978980Skrw 	memcpy(buf, DHCP_OPTIONS_COOKIE, 4);
173d6a67f0fSkrw 	if (options[DHO_DHCP_MESSAGE_TYPE].data) {
174d6a67f0fSkrw 		memcpy(&buf[4], DHCP_OPTIONS_MESSAGE_TYPE, 3);
175d6a67f0fSkrw 		buf[6] = options[DHO_DHCP_MESSAGE_TYPE].data[0];
176d6a67f0fSkrw 		bufix = 7;
177d6a67f0fSkrw 	} else
17896978980Skrw 		bufix = 4;
1799a2590e5Sderaadt 
18096978980Skrw 	for (code = DHO_SUBNET_MASK; code < DHO_END; code++) {
181d6a67f0fSkrw 		if (!options[code].data || code == DHO_DHCP_MESSAGE_TYPE)
1829a2590e5Sderaadt 			continue;
1839a2590e5Sderaadt 
184d7d9bbf5Skrw 		length = options[code].len;
18596978980Skrw 		if (bufix + length + 2*((length+254)/255) >= buflen)
18696978980Skrw 			return (lastopt);
1879a2590e5Sderaadt 
18896978980Skrw 		lastopt = bufix;
1899a2590e5Sderaadt 		ix = 0;
1909a2590e5Sderaadt 
1919a2590e5Sderaadt 		while (length) {
19296978980Skrw 			incr = length > 255 ? 255 : length;
1939a2590e5Sderaadt 
19496978980Skrw 			buf[bufix++] = code;
19596978980Skrw 			buf[bufix++] = incr;
19696978980Skrw 			memcpy(buf + bufix, options[code].data + ix, incr);
1979a2590e5Sderaadt 
1989a2590e5Sderaadt 			length -= incr;
1999a2590e5Sderaadt 			ix += incr;
2006fc9f4f6Skrw 			bufix += incr;
2019a2590e5Sderaadt 		}
2029a2590e5Sderaadt 	}
20396978980Skrw 
20496978980Skrw 	if (bufix < buflen) {
20596978980Skrw 		buf[bufix] = DHO_END;
20696978980Skrw 		lastopt = bufix;
20796978980Skrw 	}
20896978980Skrw 
20996978980Skrw 	return (lastopt);
2109a2590e5Sderaadt }
2119a2590e5Sderaadt 
212c714dadcShenning /*
213482123e8Skrw  * Use vis() to encode characters of src and append encoded characters onto
214482123e8Skrw  * dst. Also encode ", ', $, ` and \, to ensure resulting strings can be
215482123e8Skrw  * represented as '"' delimited strings and safely passed to scripts. Surround
216482123e8Skrw  * result with double quotes if emit_punct is true.
217482123e8Skrw  */
218482123e8Skrw int
219482123e8Skrw pretty_print_string(unsigned char *dst, size_t dstlen, unsigned char *src,
220482123e8Skrw     size_t srclen, int emit_punct)
221482123e8Skrw {
222482123e8Skrw 	char visbuf[5];
223482123e8Skrw 	unsigned char *origsrc = src;
224482123e8Skrw 	int opcount = 0, total = 0;
225482123e8Skrw 
226482123e8Skrw 	if (emit_punct) {
227482123e8Skrw 		opcount = snprintf(dst, dstlen, "\"");
228482123e8Skrw 		if (opcount == -1)
229482123e8Skrw 			return (-1);
230482123e8Skrw 		total += opcount;
231482123e8Skrw 		if (opcount >= dstlen)
232482123e8Skrw 			goto done;
233482123e8Skrw 		dstlen -= opcount;
234482123e8Skrw 		dst += opcount;
235482123e8Skrw 	}
236482123e8Skrw 
237482123e8Skrw 	for (; src < origsrc + srclen; src++) {
238482123e8Skrw 		if (*src && strchr("\"'$`\\", *src))
239*642cc348Skrw 			vis(visbuf, *src, VIS_ALL | VIS_OCTAL, *src+1);
240*642cc348Skrw 		else
241482123e8Skrw 			vis(visbuf, *src, VIS_OCTAL, *src+1);
242482123e8Skrw 		opcount = snprintf(dst, dstlen, "%s", visbuf);
243482123e8Skrw 		if (opcount == -1)
244482123e8Skrw 			return (-1);
245482123e8Skrw 		total += opcount;
246482123e8Skrw 		if (opcount >= dstlen)
247482123e8Skrw 			goto done;
248482123e8Skrw 		dstlen -= opcount;
249482123e8Skrw 		dst += opcount;
250482123e8Skrw 	}
251482123e8Skrw 
252482123e8Skrw 	if (emit_punct) {
253482123e8Skrw 		opcount = snprintf(dst, dstlen, "\"");
254482123e8Skrw 		if (opcount == -1)
255482123e8Skrw 			return (-1);
256482123e8Skrw 		total += opcount;
257482123e8Skrw 		if (opcount >= dstlen)
258482123e8Skrw 			goto done;
259482123e8Skrw 		dstlen -= opcount;
260482123e8Skrw 		dst += opcount;
261482123e8Skrw 	}
262482123e8Skrw done:
263482123e8Skrw 	return (total);
264482123e8Skrw }
265482123e8Skrw 
266482123e8Skrw /*
2675714f486Skrw  * Must special case *_CLASSLESS_* route options due to the variable size
2685714f486Skrw  * of the CIDR element in its CIA format.
2695714f486Skrw  */
2705714f486Skrw int
2715714f486Skrw pretty_print_classless_routes(unsigned char *dst, size_t dstlen,
2725714f486Skrw     unsigned char *src, size_t srclen)
2735714f486Skrw {
2745714f486Skrw 	struct in_addr mask, gateway;
2755714f486Skrw 	int opcount = 0, total = 0, bits, bytes;
2765714f486Skrw 	char ntoabuf[INET_ADDRSTRLEN];
2775714f486Skrw 
2785714f486Skrw 	while (srclen && dstlen) {
2795714f486Skrw 		bits = *src;
2805714f486Skrw 		src++;
2815714f486Skrw 		srclen--;
2825714f486Skrw 		bytes = (bits + 7) / 8;
2835714f486Skrw 		if (srclen < bytes || bytes > sizeof(mask.s_addr))
2845714f486Skrw 			break;
2855714f486Skrw 		memset(&mask, 0, sizeof(mask));
2865714f486Skrw 		memcpy(&mask.s_addr, src, bytes);
2875714f486Skrw 		src += bytes;
2885714f486Skrw 		srclen -= bytes;
2895714f486Skrw 		strlcpy(ntoabuf, inet_ntoa(mask), sizeof(ntoabuf));
2905714f486Skrw 		if (srclen < sizeof(gateway.s_addr))
2915714f486Skrw 			break;
2925714f486Skrw 		memcpy(&gateway.s_addr, src, sizeof(gateway.s_addr));
2935714f486Skrw 		src += sizeof(gateway.s_addr);
2945714f486Skrw 		srclen -= sizeof(gateway.s_addr);
2955714f486Skrw 		opcount = snprintf(dst, dstlen, "%s%s/%u %s",
2965714f486Skrw 		    total ? ", " : "", ntoabuf, bits,
2975714f486Skrw 		    inet_ntoa(gateway));
2985714f486Skrw 		if (opcount == -1)
2995714f486Skrw 			return (-1);
3005714f486Skrw 		total += opcount;
3015714f486Skrw 		if (opcount >= dstlen)
3025714f486Skrw 			break;
3035714f486Skrw 		dst += opcount;
3045714f486Skrw 		dstlen -= opcount;
3055714f486Skrw 	}
3065714f486Skrw 
3075714f486Skrw 	return (total);
3085714f486Skrw }
3095714f486Skrw 
310968fe952Skrw int
311968fe952Skrw expand_search_domain_name(unsigned char *src, size_t srclen, int *offset,
312968fe952Skrw     unsigned char *domain_search)
313968fe952Skrw {
314968fe952Skrw 	int domain_name_len, i, label_len, pointer, pointed_len;
315968fe952Skrw 	char *cursor;
316968fe952Skrw 
317968fe952Skrw 	cursor = domain_search + strlen(domain_search);
318968fe952Skrw 	domain_name_len = 0;
319968fe952Skrw 
320968fe952Skrw 	i = *offset;
321968fe952Skrw 	while (i <= srclen) {
322968fe952Skrw 		label_len = src[i];
323968fe952Skrw 		if (label_len == 0) {
324968fe952Skrw 			/*
325968fe952Skrw 			 * A zero-length label marks the end of this
326968fe952Skrw 			 * domain name.
327968fe952Skrw 			 */
328968fe952Skrw 			*offset = i + 1;
329968fe952Skrw 			return (domain_name_len);
330968fe952Skrw 		} else if (label_len & 0xC0) {
331968fe952Skrw 			/* This is a pointer to another list of labels. */
332968fe952Skrw 			if (i + 1 >= srclen) {
333968fe952Skrw 				/* The pointer is truncated. */
334385a6373Skrw 				log_warnx("Truncated pointer in DHCP Domain "
335968fe952Skrw 				    "Search option.");
336968fe952Skrw 				return (-1);
337968fe952Skrw 			}
338968fe952Skrw 
339968fe952Skrw 			pointer = ((label_len & ~(0xC0)) << 8) + src[i + 1];
340968fe952Skrw 			if (pointer >= *offset) {
341968fe952Skrw 				/*
342968fe952Skrw 				 * The pointer must indicates a prior
343968fe952Skrw 				 * occurance.
344968fe952Skrw 				 */
345385a6373Skrw 				log_warnx("Invalid forward pointer in DHCP "
346968fe952Skrw 				    "Domain Search option compression.");
347968fe952Skrw 				return (-1);
348968fe952Skrw 			}
349968fe952Skrw 
350968fe952Skrw 			pointed_len = expand_search_domain_name(src, srclen,
351968fe952Skrw 			    &pointer, domain_search);
352968fe952Skrw 			domain_name_len += pointed_len;
353968fe952Skrw 
354968fe952Skrw 			*offset = i + 2;
355968fe952Skrw 			return (domain_name_len);
356968fe952Skrw 		}
357968fe952Skrw 		if (i + label_len + 1 > srclen) {
358385a6373Skrw 			log_warnx("Truncated label in DHCP Domain Search "
359968fe952Skrw 			    "option.");
360968fe952Skrw 			return (-1);
361968fe952Skrw 		}
362968fe952Skrw 		/*
363968fe952Skrw 		 * Update the domain name length with the length of the
364968fe952Skrw 		 * current label, plus a trailing dot ('.').
365968fe952Skrw 		 */
366968fe952Skrw 		domain_name_len += label_len + 1;
367968fe952Skrw 
368968fe952Skrw 		if (strlen(domain_search) + domain_name_len >=
369968fe952Skrw 		    DHCP_DOMAIN_SEARCH_LEN) {
370385a6373Skrw 			log_warnx("Domain search list too long.");
371968fe952Skrw 			return (-1);
372968fe952Skrw 		}
373968fe952Skrw 
374968fe952Skrw 		/* Copy the label found. */
375968fe952Skrw 		memcpy(cursor, src + i + 1, label_len);
376968fe952Skrw 		cursor[label_len] = '.';
377968fe952Skrw 
378968fe952Skrw 		/* Move cursor. */
379968fe952Skrw 		i += label_len + 1;
380968fe952Skrw 		cursor += label_len + 1;
381968fe952Skrw 	}
382968fe952Skrw 
383385a6373Skrw 	log_warnx("Truncated DHCP Domain Search option.");
384968fe952Skrw 
385968fe952Skrw 	return (-1);
386968fe952Skrw }
387968fe952Skrw 
388968fe952Skrw /*
389968fe952Skrw  * Must special case DHO_DOMAIN_SEARCH because it is encoded as described
390968fe952Skrw  * in RFC 1035 section 4.1.4.
391968fe952Skrw  */
392968fe952Skrw int
393968fe952Skrw pretty_print_domain_search(unsigned char *dst, size_t dstlen,
394968fe952Skrw     unsigned char *src, size_t srclen)
395968fe952Skrw {
396968fe952Skrw 	int offset, len, expanded_len, domains;
397968fe952Skrw 	unsigned char *domain_search, *cursor;
398968fe952Skrw 
399968fe952Skrw 	domain_search = calloc(1, DHCP_DOMAIN_SEARCH_LEN);
400968fe952Skrw 	if (domain_search == NULL)
401385a6373Skrw 		fatalx("Can't allocate storage for expanded domain-search\n");
402968fe952Skrw 
403968fe952Skrw 	/* Compute expanded length. */
404968fe952Skrw 	expanded_len = len = 0;
405968fe952Skrw 	domains = 0;
406968fe952Skrw 	offset = 0;
407968fe952Skrw 	while (offset < srclen) {
408968fe952Skrw 		cursor = domain_search + strlen(domain_search);
409968fe952Skrw 		if (domain_search[0]) {
410968fe952Skrw 			*cursor = ' ';
411968fe952Skrw 			expanded_len++;
412968fe952Skrw 		}
413968fe952Skrw 		len = expand_search_domain_name(src, srclen, &offset,
414968fe952Skrw 		    domain_search);
415968fe952Skrw 		if (len == -1) {
416968fe952Skrw 			free(domain_search);
417968fe952Skrw 			return (-1);
418968fe952Skrw 		}
419968fe952Skrw 		domains++;
420968fe952Skrw 		expanded_len += len;
421968fe952Skrw 		if (domains > DHCP_DOMAIN_SEARCH_CNT) {
422968fe952Skrw 			free(domain_search);
423968fe952Skrw 			return (-1);
424968fe952Skrw 		}
425968fe952Skrw 	}
426968fe952Skrw 
427968fe952Skrw 	strlcat(dst, domain_search, dstlen);
428968fe952Skrw 	free(domain_search);
429968fe952Skrw 
430968fe952Skrw 	return (0);
431968fe952Skrw }
432968fe952Skrw 
4335714f486Skrw /*
434c714dadcShenning  * Format the specified option so that a human can easily read it.
435c714dadcShenning  */
436c714dadcShenning char *
437acf4c28bSkrw pretty_print_option(unsigned int code, struct option_data *option,
438acf4c28bSkrw     int emit_punct)
4399a2590e5Sderaadt {
4409a2590e5Sderaadt 	static char optbuf[32768]; /* XXX */
441285f06efSderaadt 	int hunksize = 0, numhunk = -1, numelem = 0;
442482123e8Skrw 	char fmtbuf[32], *op = optbuf;
443285f06efSderaadt 	int i, j, k, opleft = sizeof(optbuf);
444acf4c28bSkrw 	unsigned char *data = option->data;
4459a2590e5Sderaadt 	unsigned char *dp = data;
446acf4c28bSkrw 	int len = option->len;
447f3a8c5fdSkrw 	int opcount = 0;
4489a2590e5Sderaadt 	struct in_addr foo;
4499a2590e5Sderaadt 	char comma;
450bce09e58Skrw 	int32_t int32val;
451bce09e58Skrw 	u_int32_t uint32val;
452bce09e58Skrw 	u_int16_t uint16val;
4539a2590e5Sderaadt 
4542f18daabSkrw 	memset(optbuf, 0, sizeof(optbuf));
4552f18daabSkrw 
4569a2590e5Sderaadt 	/* Code should be between 0 and 255. */
4572f18daabSkrw 	if (code > 255) {
458385a6373Skrw 		log_warnx("pretty_print_option: bad code %d", code);
4592f18daabSkrw 		goto done;
4602f18daabSkrw 	}
4619a2590e5Sderaadt 
462acf4c28bSkrw 	if (emit_punct)
4639a2590e5Sderaadt 		comma = ',';
4649a2590e5Sderaadt 	else
4659a2590e5Sderaadt 		comma = ' ';
4669a2590e5Sderaadt 
4675714f486Skrw 	/* Handle the princess class options with weirdo formats. */
4685714f486Skrw 	switch (code) {
4695714f486Skrw 	case DHO_CLASSLESS_STATIC_ROUTES:
4705714f486Skrw 	case DHO_CLASSLESS_MS_STATIC_ROUTES:
4715714f486Skrw 		opcount = pretty_print_classless_routes(op, opleft, dp, len);
4725714f486Skrw 		if (opcount >= opleft || opcount == -1)
4735714f486Skrw 			goto toobig;
4745714f486Skrw 		goto done;
4755714f486Skrw 	default:
4765714f486Skrw 		break;
4775714f486Skrw 	}
4785714f486Skrw 
4799a2590e5Sderaadt 	/* Figure out the size of the data. */
4809a2590e5Sderaadt 	for (i = 0; dhcp_options[code].format[i]; i++) {
4819a2590e5Sderaadt 		if (!numhunk) {
482833082e5Skrw 			log_warnx("%s: Excess information in format string: "
483833082e5Skrw 			    "%s", dhcp_options[code].name,
4849a2590e5Sderaadt 			    &(dhcp_options[code].format[i]));
4852f18daabSkrw 			goto done;
4869a2590e5Sderaadt 		}
4879a2590e5Sderaadt 		numelem++;
4889a2590e5Sderaadt 		fmtbuf[i] = dhcp_options[code].format[i];
4899a2590e5Sderaadt 		switch (dhcp_options[code].format[i]) {
4909a2590e5Sderaadt 		case 'A':
4919a2590e5Sderaadt 			--numelem;
4929a2590e5Sderaadt 			fmtbuf[i] = 0;
4939a2590e5Sderaadt 			numhunk = 0;
49429432cd9Sphessler 			if (hunksize == 0) {
495385a6373Skrw 				log_warnx("%s: no size indicator before A"
49629432cd9Sphessler 				    " in format string: %s",
49729432cd9Sphessler 				    dhcp_options[code].name,
49829432cd9Sphessler 				    dhcp_options[code].format);
4992f18daabSkrw 				goto done;
50029432cd9Sphessler 			}
5019a2590e5Sderaadt 			break;
5029a2590e5Sderaadt 		case 'X':
503c714dadcShenning 			for (k = 0; k < len; k++)
5049a2590e5Sderaadt 				if (!isascii(data[k]) ||
5059a2590e5Sderaadt 				    !isprint(data[k]))
5069a2590e5Sderaadt 					break;
507b54c879eShenning 			if (k == len) {
5089a2590e5Sderaadt 				fmtbuf[i] = 't';
5099a2590e5Sderaadt 				numhunk = -2;
5109a2590e5Sderaadt 			} else {
5119a2590e5Sderaadt 				hunksize++;
5129a2590e5Sderaadt 				comma = ':';
5139a2590e5Sderaadt 				numhunk = 0;
5149a2590e5Sderaadt 			}
5159a2590e5Sderaadt 			fmtbuf[i + 1] = 0;
5169a2590e5Sderaadt 			break;
5179a2590e5Sderaadt 		case 't':
5189a2590e5Sderaadt 			fmtbuf[i + 1] = 0;
5199a2590e5Sderaadt 			numhunk = -2;
5209a2590e5Sderaadt 			break;
5219a2590e5Sderaadt 		case 'I':
5229a2590e5Sderaadt 		case 'l':
5239a2590e5Sderaadt 		case 'L':
5249a2590e5Sderaadt 			hunksize += 4;
5259a2590e5Sderaadt 			break;
5269a2590e5Sderaadt 		case 'S':
5279a2590e5Sderaadt 			hunksize += 2;
5289a2590e5Sderaadt 			break;
5299a2590e5Sderaadt 		case 'B':
5309a2590e5Sderaadt 		case 'f':
5319a2590e5Sderaadt 			hunksize++;
5329a2590e5Sderaadt 			break;
5339a2590e5Sderaadt 		case 'e':
5349a2590e5Sderaadt 			break;
5359a2590e5Sderaadt 		default:
536385a6373Skrw 			log_warnx("%s: garbage in format string: %s",
5379a2590e5Sderaadt 			    dhcp_options[code].name,
5389a2590e5Sderaadt 			    &(dhcp_options[code].format[i]));
5392f18daabSkrw 			goto done;
5409a2590e5Sderaadt 		}
5419a2590e5Sderaadt 	}
5429a2590e5Sderaadt 
543d22f105fSkrw 	/* Check for too few bytes. */
5449a2590e5Sderaadt 	if (hunksize > len) {
545385a6373Skrw 		log_warnx("%s: expecting at least %d bytes; got %d",
546c714dadcShenning 		    dhcp_options[code].name, hunksize, len);
5472f18daabSkrw 		goto done;
5489a2590e5Sderaadt 	}
549d22f105fSkrw 	/* Check for too many bytes. */
5502f18daabSkrw 	if (numhunk == -1 && hunksize < len) {
551385a6373Skrw 		log_warnx("%s: expecting only %d bytes: got %d",
55228f2359aSkrw 		    dhcp_options[code].name, hunksize, len);
5532f18daabSkrw 		goto done;
5542f18daabSkrw 	}
5559a2590e5Sderaadt 
5569a2590e5Sderaadt 	/* If this is an array, compute its size. */
5579a2590e5Sderaadt 	if (!numhunk)
5589a2590e5Sderaadt 		numhunk = len / hunksize;
5599a2590e5Sderaadt 	/* See if we got an exact number of hunks. */
5602f18daabSkrw 	if (numhunk > 0 && numhunk * hunksize != len) {
561385a6373Skrw 		log_warnx("%s: expecting %d bytes: got %d",
5622f18daabSkrw 		    dhcp_options[code].name, numhunk * hunksize, len);
5632f18daabSkrw 		goto done;
5642f18daabSkrw 	}
5659a2590e5Sderaadt 
5669a2590e5Sderaadt 	/* A one-hunk array prints the same as a single hunk. */
5679a2590e5Sderaadt 	if (numhunk < 0)
5689a2590e5Sderaadt 		numhunk = 1;
5699a2590e5Sderaadt 
5709a2590e5Sderaadt 	/* Cycle through the array (or hunk) printing the data. */
5719a2590e5Sderaadt 	for (i = 0; i < numhunk; i++) {
5729a2590e5Sderaadt 		for (j = 0; j < numelem; j++) {
5739a2590e5Sderaadt 			switch (fmtbuf[j]) {
5749a2590e5Sderaadt 			case 't':
575482123e8Skrw 				opcount = pretty_print_string(op, opleft,
576482123e8Skrw 				    dp, len, emit_punct);
5779a2590e5Sderaadt 				break;
5789a2590e5Sderaadt 			case 'I':
579e95625edSkrw 				memcpy(&foo.s_addr, dp, sizeof(foo.s_addr));
580f3a8c5fdSkrw 				opcount = snprintf(op, opleft, "%s",
581f3a8c5fdSkrw 				    inet_ntoa(foo));
582e95625edSkrw 				dp += sizeof(foo.s_addr);
5839a2590e5Sderaadt 				break;
5849a2590e5Sderaadt 			case 'l':
585bce09e58Skrw 				memcpy(&int32val, dp, sizeof(int32val));
586bce09e58Skrw 				opcount = snprintf(op, opleft, "%d",
587bce09e58Skrw 				    ntohl(int32val));
588bce09e58Skrw 				dp += sizeof(int32val);
5899a2590e5Sderaadt 				break;
5909a2590e5Sderaadt 			case 'L':
591bce09e58Skrw 				memcpy(&uint32val, dp, sizeof(uint32val));
592bce09e58Skrw 				opcount = snprintf(op, opleft, "%u",
593bce09e58Skrw 				    ntohl(uint32val));
594bce09e58Skrw 				dp += sizeof(uint32val);
5959a2590e5Sderaadt 				break;
5969a2590e5Sderaadt 			case 'S':
597bce09e58Skrw 				memcpy(&uint16val, dp, sizeof(uint16val));
598bce09e58Skrw 				opcount = snprintf(op, opleft, "%hu",
599bce09e58Skrw 				    ntohs(uint16val));
600bce09e58Skrw 				dp += sizeof(uint16val);
6019a2590e5Sderaadt 				break;
6029a2590e5Sderaadt 			case 'B':
603221bd6c0Skrw 				opcount = snprintf(op, opleft, "%u", *dp);
604de3ca9dbSkrw 				dp++;
6059a2590e5Sderaadt 				break;
606920d03efSkrw 			case 'X':
607de3ca9dbSkrw 				opcount = snprintf(op, opleft, "%x", *dp);
608de3ca9dbSkrw 				dp++;
6099a2590e5Sderaadt 				break;
6109a2590e5Sderaadt 			case 'f':
611f3a8c5fdSkrw 				opcount = snprintf(op, opleft, "%s",
612f3a8c5fdSkrw 				    *dp ? "true" : "false");
613de3ca9dbSkrw 				dp++;
6149a2590e5Sderaadt 				break;
6159a2590e5Sderaadt 			default:
616833082e5Skrw 				log_warnx("Unexpected format code %c",
617833082e5Skrw 				    fmtbuf[j]);
6189a2590e5Sderaadt 				goto toobig;
619f3a8c5fdSkrw 			}
620f3a8c5fdSkrw 			if (opcount >= opleft || opcount == -1)
621f3a8c5fdSkrw 				goto toobig;
622f3a8c5fdSkrw 			opleft -= opcount;
623f3a8c5fdSkrw 			op += opcount;
6249a2590e5Sderaadt 			if (j + 1 < numelem && comma != ':') {
625f3a8c5fdSkrw 				opcount = snprintf(op, opleft, " ");
626f3a8c5fdSkrw 				if (opcount >= opleft || opcount == -1)
627f3a8c5fdSkrw 					goto toobig;
628f3a8c5fdSkrw 				opleft -= opcount;
629f3a8c5fdSkrw 				op += opcount;
6309a2590e5Sderaadt 			}
6319a2590e5Sderaadt 		}
6329a2590e5Sderaadt 		if (i + 1 < numhunk) {
633f3a8c5fdSkrw 			opcount = snprintf(op, opleft, "%c", comma);
634f3a8c5fdSkrw 			if (opcount >= opleft || opcount == -1)
6359a2590e5Sderaadt 				goto toobig;
636f3a8c5fdSkrw 			opleft -= opcount;
637f3a8c5fdSkrw 			op += opcount;
638f3a8c5fdSkrw 		}
6399a2590e5Sderaadt 	}
6402f18daabSkrw 
6412f18daabSkrw done:
642c714dadcShenning 	return (optbuf);
6432f18daabSkrw 
6449a2590e5Sderaadt toobig:
6452f18daabSkrw 	memset(optbuf, 0, sizeof(optbuf));
6462f18daabSkrw 	return (optbuf);
6479a2590e5Sderaadt }
6489a2590e5Sderaadt 
649c714dadcShenning void
650916c3997Smpi do_packet(struct interface_info *ifi, unsigned int from_port,
651916c3997Smpi     struct in_addr from, struct ether_addr *hfrom)
6529a2590e5Sderaadt {
6536a2ee11aSmpi 	struct client_state *client = ifi->client;
65402e02bd5Skrw 	struct dhcp_packet *packet = &client->packet;
6554f062ee3Skrw 	struct option_data options[256];
656b21b72f8Skrw 	struct reject_elem *ap;
65733b81fd8Smpi 	void (*handler)(struct interface_info *, struct in_addr,
65833b81fd8Smpi 	    struct option_data *, char *);
6596896c986Skrw 	char *type, *info;
6606896c986Skrw 	int i, rslt, options_valid = 1;
6619a2590e5Sderaadt 
662393831bbSkrw 	if (packet->hlen != ETHER_ADDR_LEN) {
663aff84b99Skrw #ifdef DEBUG
664385a6373Skrw 		log_debug("Discarding packet with hlen != %s (%u)",
665aff84b99Skrw 		    ifi->name, packet->hlen);
66668c1ec45Skrw #endif	/* DEBUG */
667aff84b99Skrw 		return;
668393831bbSkrw 	} else if (memcmp(&ifi->hw_address, packet->chaddr,
669393831bbSkrw 	    sizeof(ifi->hw_address))) {
670aff84b99Skrw #ifdef DEBUG
671833082e5Skrw 		log_debug("Discarding packet with chaddr != %s (%s)",
672833082e5Skrw 		    ifi->name,
673aff84b99Skrw 		    ether_ntoa((struct ether_addr *)packet->chaddr));
67468c1ec45Skrw #endif	/* DEBUG */
6759a2590e5Sderaadt 		return;
6769a2590e5Sderaadt 	}
6779a2590e5Sderaadt 
678aff84b99Skrw 	if (client->xid != client->packet.xid) {
679aff84b99Skrw #ifdef DEBUG
680385a6373Skrw 		log_debug("Discarding packet with XID != %u (%u)", client->xid,
681aff84b99Skrw 		    client->packet.xid);
68268c1ec45Skrw #endif	/* DEBUG */
68302e02bd5Skrw 		return;
684aff84b99Skrw 	}
685aff84b99Skrw 
686649a5e03Skrw 	TAILQ_FOREACH(ap, &config->reject_list, next)
687aff84b99Skrw 		if (from.s_addr == ap->addr.s_addr) {
688aff84b99Skrw #ifdef DEBUG
689833082e5Skrw 			log_debug("Discarding packet from address on reject "
690833082e5Skrw 			    "list (%s)", inet_ntoa(from));
69168c1ec45Skrw #endif	/* DEBUG */
692aff84b99Skrw 			return;
693aff84b99Skrw 		}
6949a2590e5Sderaadt 
69502e02bd5Skrw 	memset(options, 0, sizeof(options));
69602e02bd5Skrw 
69702e02bd5Skrw 	if (memcmp(&packet->options, DHCP_OPTIONS_COOKIE, 4) == 0) {
69802e02bd5Skrw 		/* Parse the BOOTP/DHCP options field. */
69902e02bd5Skrw 		options_valid = parse_option_buffer(options,
70002e02bd5Skrw 		    &packet->options[4], sizeof(packet->options) - 4);
70102e02bd5Skrw 
70202e02bd5Skrw 		/* Only DHCP packets have overload areas for options. */
70302e02bd5Skrw 		if (options_valid &&
70402e02bd5Skrw 		    options[DHO_DHCP_MESSAGE_TYPE].data &&
70502e02bd5Skrw 		    options[DHO_DHCP_OPTION_OVERLOAD].data) {
70602e02bd5Skrw 			if (options[DHO_DHCP_OPTION_OVERLOAD].data[0] & 1)
70702e02bd5Skrw 				options_valid = parse_option_buffer(options,
70802e02bd5Skrw 				    (unsigned char *)packet->file,
70902e02bd5Skrw 				    sizeof(packet->file));
71002e02bd5Skrw 			if (options_valid &&
71102e02bd5Skrw 			    options[DHO_DHCP_OPTION_OVERLOAD].data[0] & 2)
71202e02bd5Skrw 				options_valid = parse_option_buffer(options,
71302e02bd5Skrw 				    (unsigned char *)packet->sname,
71402e02bd5Skrw 				    sizeof(packet->sname));
71502e02bd5Skrw 		}
71618d08eb0Skrw 
71718d08eb0Skrw 		/*
71818d08eb0Skrw 		 * RFC 6842 says if the server sends a client identifier
71918d08eb0Skrw 		 * that doesn't match then the packet must be dropped.
72018d08eb0Skrw 		 */
72118d08eb0Skrw 		i = DHO_DHCP_CLIENT_IDENTIFIER;
72218d08eb0Skrw 		if ((options[i].len != 0) &&
72318d08eb0Skrw 		    ((options[i].len != config->send_options[i].len) ||
72418d08eb0Skrw 		    memcmp(options[i].data, config->send_options[i].data,
72518d08eb0Skrw 		    options[i].len) != 0)) {
72618d08eb0Skrw #ifdef DEBUG
727833082e5Skrw 			log_debug("Discarding packet with client-identifier "
728833082e5Skrw 			    "'%s'", pretty_print_option(i, &options[i], 0));
72968c1ec45Skrw #endif	/* DEBUG */
73018d08eb0Skrw 			goto done;
73118d08eb0Skrw 		}
73202e02bd5Skrw 	}
73302e02bd5Skrw 
7346896c986Skrw 	type = "<unknown>";
73502e02bd5Skrw 	handler = NULL;
73602e02bd5Skrw 
7374f062ee3Skrw 	if (options[DHO_DHCP_MESSAGE_TYPE].data) {
73802e02bd5Skrw 		/* Always try a DHCP packet, even if a bad option was seen. */
73902e02bd5Skrw 		switch (options[DHO_DHCP_MESSAGE_TYPE].data[0]) {
74002e02bd5Skrw 		case DHCPOFFER:
74102e02bd5Skrw 			handler = dhcpoffer;
74202e02bd5Skrw 			type = "DHCPOFFER";
74302e02bd5Skrw 			break;
74402e02bd5Skrw 		case DHCPNAK:
74502e02bd5Skrw 			handler = dhcpnak;
74602e02bd5Skrw 			type = "DHCPNACK";
74702e02bd5Skrw 			break;
74802e02bd5Skrw 		case DHCPACK:
74902e02bd5Skrw 			handler = dhcpack;
75002e02bd5Skrw 			type = "DHCPACK";
75102e02bd5Skrw 			break;
75202e02bd5Skrw 		default:
753aff84b99Skrw #ifdef DEBUG
754833082e5Skrw 			log_debug("Discarding DHCP packet of unknown type "
755833082e5Skrw 			    "(%d)", options[DHO_DHCP_MESSAGE_TYPE].data[0]);
75668c1ec45Skrw #endif	/* DEBUG */
75702e02bd5Skrw 			break;
75802e02bd5Skrw 		}
75902e02bd5Skrw 	} else if (options_valid && packet->op == BOOTREPLY) {
76002e02bd5Skrw 		handler = dhcpoffer;
76102e02bd5Skrw 		type = "BOOTREPLY";
762aff84b99Skrw 	} else {
763aff84b99Skrw #ifdef DEBUG
764385a6373Skrw 		log_debug("Discarding packet which is neither DHCP nor BOOTP");
76568c1ec45Skrw #endif	/* DEBUG */
76602e02bd5Skrw 	}
7679a2590e5Sderaadt 
7686896c986Skrw 	rslt = asprintf(&info, "%s from %s (%s)", type, inet_ntoa(from),
769393831bbSkrw 	    ether_ntoa(hfrom));
7706896c986Skrw 	if (rslt == -1)
771385a6373Skrw 		fatalx("no memory for info string");
7726896c986Skrw 
77302e02bd5Skrw 	if (handler)
77433b81fd8Smpi 		(*handler)(ifi, from, options, info);
7756896c986Skrw 
7766896c986Skrw 	free(info);
77702e02bd5Skrw 
77818d08eb0Skrw done:
779c714dadcShenning 	for (i = 0; i < 256; i++)
7804f062ee3Skrw 		free(options[i].data);
7819a2590e5Sderaadt }
782