xref: /xnu-12377.41.6/bsd/netinet/dhcp_options.c (revision bbb1b6f9e71b8cdde6e5cd6f4841f207dee3d828)
1 /*
2  * Copyright (c) 2002-2019 Apple Inc. All rights reserved.
3  *
4  * @APPLE_OSREFERENCE_LICENSE_HEADER_START@
5  *
6  * This file contains Original Code and/or Modifications of Original Code
7  * as defined in and that are subject to the Apple Public Source License
8  * Version 2.0 (the 'License'). You may not use this file except in
9  * compliance with the License. The rights granted to you under the License
10  * may not be used to create, or enable the creation or redistribution of,
11  * unlawful or unlicensed copies of an Apple operating system, or to
12  * circumvent, violate, or enable the circumvention or violation of, any
13  * terms of an Apple operating system software license agreement.
14  *
15  * Please obtain a copy of the License at
16  * http://www.opensource.apple.com/apsl/ and read it before using this file.
17  *
18  * The Original Code and all software distributed under the License are
19  * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
20  * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
21  * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
22  * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
23  * Please see the License for the specific language governing rights and
24  * limitations under the License.
25  *
26  * @APPLE_OSREFERENCE_LICENSE_HEADER_END@
27  */
28 /*
29  * dhcp_options.c
30  * - routines to parse and access dhcp options
31  *   and create new dhcp option areas
32  * - handles overloaded areas as well as vendor-specific options
33  *   that are encoded using the RFC 2132 encoding
34  */
35 
36 /*
37  * Modification History
38  *
39  * March 15, 2002	Dieter Siegmund (dieter@apple)
40  * - imported from bootp project
41  */
42 
43 #include <string.h>
44 #include <sys/types.h>
45 #include <sys/param.h>
46 #include <netinet/in.h>
47 #include <sys/malloc.h>
48 #include <netinet/dhcp.h>
49 #include <netinet/dhcp_options.h>
50 
51 #ifndef TEST_DHCP_OPTIONS
52 #include <libkern/libkern.h>
53 
54 #ifdef  DHCP_DEBUG
55 #define dprintf(x) printf x;
56 #else   /* !DHCP_DEBUG */
57 #define dprintf(x)
58 #endif  /* DHCP_DEBUG */
59 
60 #else
61 /*
62  * To build:
63  * xcrun -sdk macosx.internal cc -DTEST_DHCP_OPTIONS -o /tmp/dhcp_options dhcp_options.c -I ..
64  */
65 #include <stdlib.h>
66 #include <unistd.h>
67 #include <stdio.h>
68 #define kfree_type(type, n, v)  free(v)
69 #define krealloc_type(type, old_n, new_n, ptr, flags) \
70 	realloc(ptr, new_n * sizeof(type)))
71 #define dprintf(x) printf x;
72 #endif
73 
74 /*
75  * Functions: ptrlist_*
76  * Purpose:
77  *   A dynamically growable array of pointers.
78  */
79 
80 #define PTRLIST_NUMBER          16
81 
82 static void
ptrlist_init(ptrlist_t * list)83 ptrlist_init(ptrlist_t * list)
84 {
85 	bzero(list, sizeof(*list));
86 	return;
87 }
88 
89 static void
ptrlist_free(ptrlist_t * list)90 ptrlist_free(ptrlist_t * list)
91 {
92 	if (list->array) {
93 		kfree_type(const void *, list->size, list->array);
94 	}
95 	ptrlist_init(list);
96 	return;
97 }
98 
99 static int
ptrlist_count(ptrlist_t * list)100 ptrlist_count(ptrlist_t * list)
101 {
102 	if (list == NULL || list->array == NULL) {
103 		return 0;
104 	}
105 
106 	return list->count;
107 }
108 
109 static const void *
ptrlist_element(ptrlist_t * list,int i)110 ptrlist_element(ptrlist_t * list, int i)
111 {
112 	if (list->array == NULL) {
113 		return NULL;
114 	}
115 	if (i < list->count) {
116 		return list->array[i];
117 	}
118 	return NULL;
119 }
120 
121 
122 static bool
ptrlist_grow(ptrlist_t * list,uint32_t n)123 ptrlist_grow(ptrlist_t * list, uint32_t n)
124 {
125 	uint32_t new_size;
126 	const void **arr;
127 
128 	if (os_add_overflow(list->count, n, &n)) {
129 		return false;
130 	}
131 	if (n <= list->size) {
132 		return true;
133 	}
134 
135 	if (list->size == 0) {
136 		new_size = MAX(PTRLIST_NUMBER, n);
137 	} else {
138 		new_size = MAX(list->size * 2, n);
139 	}
140 
141 	arr = krealloc_type(const void *, list->size, new_size, list->array, Z_WAITOK);
142 	if (arr == NULL) {
143 		return false;
144 	}
145 
146 	list->size = new_size;
147 	list->array = arr;
148 	return true;
149 }
150 
151 static bool
ptrlist_add(ptrlist_t * list,const void * element)152 ptrlist_add(ptrlist_t * list, const void * element)
153 {
154 	if (!ptrlist_grow(list, 1)) {
155 		return false;
156 	}
157 
158 	list->array[list->count++] = element;
159 	return true;
160 }
161 
162 /* concatenates extra onto list */
163 static bool
ptrlist_concat(ptrlist_t * list,ptrlist_t * extra)164 ptrlist_concat(ptrlist_t * list, ptrlist_t * extra)
165 {
166 	if (!ptrlist_grow(list, extra->count)) {
167 		return false;
168 	}
169 
170 	bcopy(extra->array, list->array + list->count,
171 	    extra->count * sizeof(*list->array));
172 	list->count += extra->count;
173 	return true;
174 }
175 
176 
177 /*
178  * Functions: dhcpol_*
179  *
180  * Purpose:
181  *   Routines to parse/access existing options buffers.
182  */
183 boolean_t
dhcpol_add(dhcpol_t * list,const void * element)184 dhcpol_add(dhcpol_t * list, const void * element)
185 {
186 	return ptrlist_add((ptrlist_t *)list, element);
187 }
188 
189 int
dhcpol_count(dhcpol_t * list)190 dhcpol_count(dhcpol_t * list)
191 {
192 	return ptrlist_count((ptrlist_t *)list);
193 }
194 
195 const void *
dhcpol_element(dhcpol_t * list,int i)196 dhcpol_element(dhcpol_t * list, int i)
197 {
198 	return ptrlist_element((ptrlist_t *)list, i);
199 }
200 
201 void
dhcpol_init(dhcpol_t * list)202 dhcpol_init(dhcpol_t * list)
203 {
204 	ptrlist_init((ptrlist_t *)list);
205 }
206 
207 void
dhcpol_free(dhcpol_t * list)208 dhcpol_free(dhcpol_t * list)
209 {
210 	ptrlist_free((ptrlist_t *)list);
211 }
212 
213 boolean_t
dhcpol_concat(dhcpol_t * list,dhcpol_t * extra)214 dhcpol_concat(dhcpol_t * list, dhcpol_t * extra)
215 {
216 	return ptrlist_concat(list, extra);
217 }
218 
219 /*
220  * Function: dhcpol_parse_buffer
221  *
222  * Purpose:
223  *   Parse the given buffer into DHCP options, returning the
224  *   list of option pointers in the given dhcpol_t.
225  *   Parsing continues until we hit the end of the buffer or
226  *   the end tag.
227  */
228 boolean_t
dhcpol_parse_buffer(dhcpol_t * list,const void * buffer,int length)229 dhcpol_parse_buffer(dhcpol_t * list, const void * buffer, int length)
230 {
231 	int                 len;
232 	const uint8_t *     scan;
233 	uint8_t             tag;
234 
235 	dhcpol_init(list);
236 
237 	len = length;
238 	tag = dhcptag_pad_e;
239 	for (scan = (const uint8_t *)buffer;
240 	    tag != dhcptag_end_e && len > DHCP_TAG_OFFSET;) {
241 		tag = scan[DHCP_TAG_OFFSET];
242 
243 		switch (tag) {
244 		case dhcptag_end_e:
245 			/* remember that it was terminated */
246 			dhcpol_add(list, scan);
247 			scan++;
248 			len--;
249 			break;
250 		case dhcptag_pad_e: /* ignore pad */
251 			scan++;
252 			len--;
253 			break;
254 		default:
255 			if (len > DHCP_LEN_OFFSET) {
256 				uint8_t       option_len;
257 
258 				option_len = scan[DHCP_LEN_OFFSET];
259 				dhcpol_add(list, scan);
260 				len -= (option_len + DHCP_OPTION_OFFSET);
261 				scan += (option_len + DHCP_OPTION_OFFSET);
262 			} else {
263 				len = -1;
264 			}
265 			break;
266 		}
267 	}
268 	if (len < 0) {
269 		/* ran off the end */
270 		dprintf(("dhcp_options: parse failed near tag %d\n", tag));
271 		dhcpol_free(list);
272 		return FALSE;
273 	}
274 	return TRUE;
275 }
276 
277 /*
278  * Function: dhcpol_find
279  *
280  * Purpose:
281  *   Finds the first occurence of the given option, and returns its
282  *   length and the option data pointer.
283  *
284  *   The optional start parameter allows this function to
285  *   return the next start point so that successive
286  *   calls will retrieve the next occurence of the option.
287  *   Before the first call, *start should be set to 0.
288  */
289 const void *
dhcpol_find(dhcpol_t * list,int tag,int * len_p,int * start)290 dhcpol_find(dhcpol_t * list, int tag, int * len_p, int * start)
291 {
292 	int         i = 0;
293 
294 	if (tag == dhcptag_end_e || tag == dhcptag_pad_e) {
295 		return NULL;
296 	}
297 
298 	if (start) {
299 		i = *start;
300 	}
301 
302 	for (; i < dhcpol_count(list); i++) {
303 		const uint8_t *         option = dhcpol_element(list, i);
304 
305 		if (option[DHCP_TAG_OFFSET] == tag) {
306 			if (len_p) {
307 				*len_p = option[DHCP_LEN_OFFSET];
308 			}
309 			if (start) {
310 				*start = i + 1;
311 			}
312 			return option + DHCP_OPTION_OFFSET;
313 		}
314 	}
315 	return NULL;
316 }
317 
318 /*
319  * Function: dhcpol_parse_packet
320  *
321  * Purpose:
322  *    Parse the option areas in the DHCP packet.
323  *    Verifies that the packet has the right magic number,
324  *    then parses and accumulates the option areas.
325  *    First the pkt->dp_options is parsed.  If that contains
326  *    the overload option, it parses pkt->dp_file if specified,
327  *    then parses pkt->dp_sname if specified.
328  */
329 boolean_t
dhcpol_parse_packet(dhcpol_t * options,const struct dhcp * pkt,int len)330 dhcpol_parse_packet(dhcpol_t * options, const struct dhcp * pkt, int len)
331 {
332 	char                rfc_magic[4] = RFC_OPTIONS_MAGIC;
333 
334 	dhcpol_init(options);   /* make sure it's empty */
335 
336 	if (len < (sizeof(*pkt) + RFC_MAGIC_SIZE)) {
337 		dprintf(("dhcp_options: packet is too short: %d < %d\n",
338 		    len, (int)sizeof(*pkt) + RFC_MAGIC_SIZE));
339 		return FALSE;
340 	}
341 	if (bcmp(pkt->dp_options, rfc_magic, RFC_MAGIC_SIZE)) {
342 		dprintf(("dhcp_options: missing magic number\n"));
343 		return FALSE;
344 	}
345 	if (dhcpol_parse_buffer(options, pkt->dp_options + RFC_MAGIC_SIZE,
346 	    len - sizeof(*pkt) - RFC_MAGIC_SIZE) == FALSE) {
347 		return FALSE;
348 	}
349 	{ /* get overloaded options */
350 		const uint8_t * overload;
351 		int             overload_len;
352 
353 		overload = dhcpol_find(options, dhcptag_option_overload_e,
354 		    &overload_len, NULL);
355 		if (overload && overload_len == 1) { /* has overloaded options */
356 			dhcpol_t    extra;
357 
358 			dhcpol_init(&extra);
359 			if (*overload == DHCP_OVERLOAD_FILE
360 			    || *overload == DHCP_OVERLOAD_BOTH) {
361 				if (dhcpol_parse_buffer(&extra, pkt->dp_file,
362 				    sizeof(pkt->dp_file))) {
363 					dhcpol_concat(options, &extra);
364 					dhcpol_free(&extra);
365 				}
366 			}
367 			if (*overload == DHCP_OVERLOAD_SNAME
368 			    || *overload == DHCP_OVERLOAD_BOTH) {
369 				if (dhcpol_parse_buffer(&extra, pkt->dp_sname,
370 				    sizeof(pkt->dp_sname))) {
371 					dhcpol_concat(options, &extra);
372 					dhcpol_free(&extra);
373 				}
374 			}
375 		}
376 	}
377 	return TRUE;
378 }
379 
380 #ifdef TEST_DHCP_OPTIONS
381 char test_empty[] = {
382 	99, 130, 83, 99,
383 	255,
384 };
385 
386 char test_short[] = {
387 	99, 130, 83, 99,
388 	1,
389 };
390 
391 char test_simple[] = {
392 	99, 130, 83, 99,
393 	1, 4, 255, 255, 252, 0,
394 	3, 4, 17, 202, 40, 1,
395 	255,
396 };
397 
398 char test_vendor[] = {
399 	99, 130, 83, 99,
400 	1, 4, 255, 255, 252, 0,
401 	3, 4, 17, 202, 40, 1,
402 	43, 6, 1, 4, 1, 2, 3, 4,
403 	43, 6, 1, 4, 1, 2, 3, 4,
404 	255,
405 };
406 
407 char test_no_end[] = {
408 	0x63, 0x82, 0x53, 0x63, 0x35, 0x01, 0x05, 0x36,
409 	0x04, 0xc0, 0xa8, 0x01, 0x01, 0x33, 0x04, 0x80,
410 	0x00, 0x80, 0x00, 0x01, 0x04, 0xff, 0xff, 0xff,
411 	0x00, 0x03, 0x04, 0xc0, 0xa8, 0x01, 0x01, 0x06,
412 	0x0c, 0x18, 0x1a, 0xa3, 0x21, 0x18, 0x1a, 0xa3,
413 	0x20, 0x18, 0x5e, 0xa3, 0x21, 0x00, 0x00, 0x00,
414 	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
415 	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
416 };
417 
418 char test_no_magic[] = {
419 	0x1
420 };
421 struct test {
422 	char *              name;
423 	char *              data;
424 	int                 len;
425 	boolean_t           result;
426 };
427 
428 struct test tests[] = {
429 	{ .name = "empty", .data = test_empty, .len = sizeof(test_empty), .result = TRUE },
430 	{ .name = "simple", .data = test_simple, .len = sizeof(test_simple), .result = TRUE },
431 	{ .name = "vendor", .data = test_vendor, .len = sizeof(test_vendor), .result = TRUE },
432 	{ .name = "no_end", .data = test_no_end, .len = sizeof(test_no_end), .result = TRUE },
433 	{ .name = "no magic", .data = test_no_magic, .len = sizeof(test_no_magic), .result = FALSE },
434 	{ .name = "short", .data = test_short, .len = sizeof(test_short), .result =  FALSE },
435 	{ .name = NULL, .data = NULL, .len = 0, .result = FALSE },
436 };
437 
438 
439 static char buf[2048];
440 
441 int
main(void)442 main(void)
443 {
444 	int         i;
445 	dhcpol_t    options;
446 	struct dhcp * pkt = (struct dhcp *)buf;
447 
448 	dhcpol_init(&options);
449 
450 	for (i = 0; tests[i].name; i++) {
451 		printf("\nTest %d: ", i);
452 		bcopy(tests[i].data, pkt->dp_options, tests[i].len);
453 		if (dhcpol_parse_packet(&options, pkt,
454 		    sizeof(*pkt) + tests[i].len)
455 		    != tests[i].result) {
456 			printf("test '%s' FAILED\n", tests[i].name);
457 		} else {
458 			printf("test '%s' PASSED\n", tests[i].name);
459 		}
460 		dhcpol_free(&options);
461 	}
462 	exit(0);
463 }
464 #endif /* TEST_DHCP_OPTIONS */
465