xref: /xnu-8019.80.24/bsd/netinet/dhcp_options.c (revision a325d9c4a84054e40bbe985afedcb50ab80993ea)
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 static __inline__ void
my_free(void * ptr)61 my_free(void * ptr)
62 {
63 	_FREE(ptr, M_TEMP);
64 }
65 
66 static __inline__ void *
my_malloc(int size)67 my_malloc(int size)
68 {
69 	void * data;
70 	MALLOC(data, void *, size, M_TEMP, M_WAITOK);
71 	return data;
72 }
73 
74 static __inline__ void *
my_realloc(void * oldptr,int oldsize,int newsize)75 my_realloc(void * oldptr, int oldsize, int newsize)
76 {
77 	void * data;
78 
79 	MALLOC(data, void *, newsize, M_TEMP, M_WAITOK);
80 	bcopy(oldptr, data, oldsize);
81 	my_free(oldptr);
82 	return data;
83 }
84 #else
85 /*
86  * To build:
87  * xcrun -sdk macosx.internal cc -DTEST_DHCP_OPTIONS -o /tmp/dhcp_options dhcp_options.c -I ..
88  */
89 #include <stdlib.h>
90 #include <unistd.h>
91 #include <stdio.h>
92 #define my_free free
93 #define my_malloc malloc
94 #define my_realloc(ptr, old_size, new_size) realloc(ptr, new_size)
95 #define dprintf(x) printf x;
96 #endif
97 
98 /*
99  * Functions: ptrlist_*
100  * Purpose:
101  *   A dynamically growable array of pointers.
102  */
103 
104 #define PTRLIST_NUMBER          16
105 
106 static void
ptrlist_init(ptrlist_t * list)107 ptrlist_init(ptrlist_t * list)
108 {
109 	bzero(list, sizeof(*list));
110 	return;
111 }
112 
113 static void
ptrlist_free(ptrlist_t * list)114 ptrlist_free(ptrlist_t * list)
115 {
116 	if (list->array) {
117 		my_free(list->array);
118 	}
119 	ptrlist_init(list);
120 	return;
121 }
122 
123 static int
ptrlist_count(ptrlist_t * list)124 ptrlist_count(ptrlist_t * list)
125 {
126 	if (list == NULL || list->array == NULL) {
127 		return 0;
128 	}
129 
130 	return list->count;
131 }
132 
133 static const void *
ptrlist_element(ptrlist_t * list,int i)134 ptrlist_element(ptrlist_t * list, int i)
135 {
136 	if (list->array == NULL) {
137 		return NULL;
138 	}
139 	if (i < list->count) {
140 		return list->array[i];
141 	}
142 	return NULL;
143 }
144 
145 
146 static boolean_t
ptrlist_grow(ptrlist_t * list)147 ptrlist_grow(ptrlist_t * list)
148 {
149 	if (list->array == NULL) {
150 		if (list->size == 0) {
151 			list->size = PTRLIST_NUMBER;
152 		}
153 		list->count = 0;
154 		list->array = my_malloc(sizeof(*list->array) * list->size);
155 	} else if (list->size == list->count) {
156 		dprintf(("doubling %d to %d\n", list->size, list->size * 2));
157 		list->array = my_realloc(list->array,
158 		    sizeof(*list->array) * list->size,
159 		    sizeof(*list->array) * list->size * 2);
160 		list->size *= 2;
161 	}
162 	if (list->array == NULL) {
163 		return FALSE;
164 	}
165 	return TRUE;
166 }
167 
168 static boolean_t
ptrlist_add(ptrlist_t * list,const void * element)169 ptrlist_add(ptrlist_t * list, const void * element)
170 {
171 	if (ptrlist_grow(list) == FALSE) {
172 		return FALSE;
173 	}
174 
175 	list->array[list->count++] = element;
176 	return TRUE;
177 }
178 
179 /* concatenates extra onto list */
180 static boolean_t
ptrlist_concat(ptrlist_t * list,ptrlist_t * extra)181 ptrlist_concat(ptrlist_t * list, ptrlist_t * extra)
182 {
183 	if (extra->count == 0) {
184 		return TRUE;
185 	}
186 
187 	if ((extra->count + list->count) > list->size) {
188 		int old_size = list->size;
189 
190 		list->size = extra->count + list->count;
191 		if (list->array == NULL) {
192 			list->array = my_malloc(sizeof(*list->array) * list->size);
193 		} else {
194 			list->array = my_realloc(list->array, old_size,
195 			    sizeof(*list->array) * list->size);
196 		}
197 	}
198 	if (list->array == NULL) {
199 		return FALSE;
200 	}
201 	bcopy(extra->array, list->array + list->count,
202 	    extra->count * sizeof(*list->array));
203 	list->count += extra->count;
204 	return TRUE;
205 }
206 
207 
208 /*
209  * Functions: dhcpol_*
210  *
211  * Purpose:
212  *   Routines to parse/access existing options buffers.
213  */
214 boolean_t
dhcpol_add(dhcpol_t * list,const void * element)215 dhcpol_add(dhcpol_t * list, const void * element)
216 {
217 	return ptrlist_add((ptrlist_t *)list, element);
218 }
219 
220 int
dhcpol_count(dhcpol_t * list)221 dhcpol_count(dhcpol_t * list)
222 {
223 	return ptrlist_count((ptrlist_t *)list);
224 }
225 
226 const void *
dhcpol_element(dhcpol_t * list,int i)227 dhcpol_element(dhcpol_t * list, int i)
228 {
229 	return ptrlist_element((ptrlist_t *)list, i);
230 }
231 
232 void
dhcpol_init(dhcpol_t * list)233 dhcpol_init(dhcpol_t * list)
234 {
235 	ptrlist_init((ptrlist_t *)list);
236 }
237 
238 void
dhcpol_free(dhcpol_t * list)239 dhcpol_free(dhcpol_t * list)
240 {
241 	ptrlist_free((ptrlist_t *)list);
242 }
243 
244 boolean_t
dhcpol_concat(dhcpol_t * list,dhcpol_t * extra)245 dhcpol_concat(dhcpol_t * list, dhcpol_t * extra)
246 {
247 	return ptrlist_concat((ptrlist_t *)list, (ptrlist_t *)extra);
248 }
249 
250 /*
251  * Function: dhcpol_parse_buffer
252  *
253  * Purpose:
254  *   Parse the given buffer into DHCP options, returning the
255  *   list of option pointers in the given dhcpol_t.
256  *   Parsing continues until we hit the end of the buffer or
257  *   the end tag.
258  */
259 boolean_t
dhcpol_parse_buffer(dhcpol_t * list,const void * buffer,int length)260 dhcpol_parse_buffer(dhcpol_t * list, const void * buffer, int length)
261 {
262 	int                 len;
263 	const uint8_t *     scan;
264 	uint8_t             tag;
265 
266 	dhcpol_init(list);
267 
268 	len = length;
269 	tag = dhcptag_pad_e;
270 	for (scan = (const uint8_t *)buffer;
271 	    tag != dhcptag_end_e && len > DHCP_TAG_OFFSET;) {
272 		tag = scan[DHCP_TAG_OFFSET];
273 
274 		switch (tag) {
275 		case dhcptag_end_e:
276 			/* remember that it was terminated */
277 			dhcpol_add(list, scan);
278 			scan++;
279 			len--;
280 			break;
281 		case dhcptag_pad_e: /* ignore pad */
282 			scan++;
283 			len--;
284 			break;
285 		default:
286 			if (len > DHCP_LEN_OFFSET) {
287 				uint8_t       option_len;
288 
289 				option_len = scan[DHCP_LEN_OFFSET];
290 				dhcpol_add(list, scan);
291 				len -= (option_len + DHCP_OPTION_OFFSET);
292 				scan += (option_len + DHCP_OPTION_OFFSET);
293 			} else {
294 				len = -1;
295 			}
296 			break;
297 		}
298 	}
299 	if (len < 0) {
300 		/* ran off the end */
301 		dprintf(("dhcp_options: parse failed near tag %d\n", tag));
302 		dhcpol_free(list);
303 		return FALSE;
304 	}
305 	return TRUE;
306 }
307 
308 /*
309  * Function: dhcpol_find
310  *
311  * Purpose:
312  *   Finds the first occurence of the given option, and returns its
313  *   length and the option data pointer.
314  *
315  *   The optional start parameter allows this function to
316  *   return the next start point so that successive
317  *   calls will retrieve the next occurence of the option.
318  *   Before the first call, *start should be set to 0.
319  */
320 const void *
dhcpol_find(dhcpol_t * list,int tag,int * len_p,int * start)321 dhcpol_find(dhcpol_t * list, int tag, int * len_p, int * start)
322 {
323 	int         i = 0;
324 
325 	if (tag == dhcptag_end_e || tag == dhcptag_pad_e) {
326 		return NULL;
327 	}
328 
329 	if (start) {
330 		i = *start;
331 	}
332 
333 	for (; i < dhcpol_count(list); i++) {
334 		const uint8_t *         option = dhcpol_element(list, i);
335 
336 		if (option[DHCP_TAG_OFFSET] == tag) {
337 			if (len_p) {
338 				*len_p = option[DHCP_LEN_OFFSET];
339 			}
340 			if (start) {
341 				*start = i + 1;
342 			}
343 			return option + DHCP_OPTION_OFFSET;
344 		}
345 	}
346 	return NULL;
347 }
348 
349 /*
350  * Function: dhcpol_parse_packet
351  *
352  * Purpose:
353  *    Parse the option areas in the DHCP packet.
354  *    Verifies that the packet has the right magic number,
355  *    then parses and accumulates the option areas.
356  *    First the pkt->dp_options is parsed.  If that contains
357  *    the overload option, it parses pkt->dp_file if specified,
358  *    then parses pkt->dp_sname if specified.
359  */
360 boolean_t
dhcpol_parse_packet(dhcpol_t * options,const struct dhcp * pkt,int len)361 dhcpol_parse_packet(dhcpol_t * options, const struct dhcp * pkt, int len)
362 {
363 	char                rfc_magic[4] = RFC_OPTIONS_MAGIC;
364 
365 	dhcpol_init(options);   /* make sure it's empty */
366 
367 	if (len < (sizeof(*pkt) + RFC_MAGIC_SIZE)) {
368 		dprintf(("dhcp_options: packet is too short: %d < %d\n",
369 		    len, (int)sizeof(*pkt) + RFC_MAGIC_SIZE));
370 		return FALSE;
371 	}
372 	if (bcmp(pkt->dp_options, rfc_magic, RFC_MAGIC_SIZE)) {
373 		dprintf(("dhcp_options: missing magic number\n"));
374 		return FALSE;
375 	}
376 	if (dhcpol_parse_buffer(options, pkt->dp_options + RFC_MAGIC_SIZE,
377 	    len - sizeof(*pkt) - RFC_MAGIC_SIZE) == FALSE) {
378 		return FALSE;
379 	}
380 	{ /* get overloaded options */
381 		const uint8_t * overload;
382 		int             overload_len;
383 
384 		overload = dhcpol_find(options, dhcptag_option_overload_e,
385 		    &overload_len, NULL);
386 		if (overload && overload_len == 1) { /* has overloaded options */
387 			dhcpol_t    extra;
388 
389 			dhcpol_init(&extra);
390 			if (*overload == DHCP_OVERLOAD_FILE
391 			    || *overload == DHCP_OVERLOAD_BOTH) {
392 				if (dhcpol_parse_buffer(&extra, pkt->dp_file,
393 				    sizeof(pkt->dp_file))) {
394 					dhcpol_concat(options, &extra);
395 					dhcpol_free(&extra);
396 				}
397 			}
398 			if (*overload == DHCP_OVERLOAD_SNAME
399 			    || *overload == DHCP_OVERLOAD_BOTH) {
400 				if (dhcpol_parse_buffer(&extra, pkt->dp_sname,
401 				    sizeof(pkt->dp_sname))) {
402 					dhcpol_concat(options, &extra);
403 					dhcpol_free(&extra);
404 				}
405 			}
406 		}
407 	}
408 	return TRUE;
409 }
410 
411 #ifdef TEST_DHCP_OPTIONS
412 char test_empty[] = {
413 	99, 130, 83, 99,
414 	255,
415 };
416 
417 char test_short[] = {
418 	99, 130, 83, 99,
419 	1,
420 };
421 
422 char test_simple[] = {
423 	99, 130, 83, 99,
424 	1, 4, 255, 255, 252, 0,
425 	3, 4, 17, 202, 40, 1,
426 	255,
427 };
428 
429 char test_vendor[] = {
430 	99, 130, 83, 99,
431 	1, 4, 255, 255, 252, 0,
432 	3, 4, 17, 202, 40, 1,
433 	43, 6, 1, 4, 1, 2, 3, 4,
434 	43, 6, 1, 4, 1, 2, 3, 4,
435 	255,
436 };
437 
438 char test_no_end[] = {
439 	0x63, 0x82, 0x53, 0x63, 0x35, 0x01, 0x05, 0x36,
440 	0x04, 0xc0, 0xa8, 0x01, 0x01, 0x33, 0x04, 0x80,
441 	0x00, 0x80, 0x00, 0x01, 0x04, 0xff, 0xff, 0xff,
442 	0x00, 0x03, 0x04, 0xc0, 0xa8, 0x01, 0x01, 0x06,
443 	0x0c, 0x18, 0x1a, 0xa3, 0x21, 0x18, 0x1a, 0xa3,
444 	0x20, 0x18, 0x5e, 0xa3, 0x21, 0x00, 0x00, 0x00,
445 	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
446 	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
447 };
448 
449 char test_no_magic[] = {
450 	0x1
451 };
452 struct test {
453 	char *              name;
454 	char *              data;
455 	int                 len;
456 	boolean_t           result;
457 };
458 
459 struct test tests[] = {
460 	{ .name = "empty", .data = test_empty, .len = sizeof(test_empty), .result = TRUE },
461 	{ .name = "simple", .data = test_simple, .len = sizeof(test_simple), .result = TRUE },
462 	{ .name = "vendor", .data = test_vendor, .len = sizeof(test_vendor), .result = TRUE },
463 	{ .name = "no_end", .data = test_no_end, .len = sizeof(test_no_end), .result = TRUE },
464 	{ .name = "no magic", .data = test_no_magic, .len = sizeof(test_no_magic), .result = FALSE },
465 	{ .name = "short", .data = test_short, .len = sizeof(test_short), .result =  FALSE },
466 	{ .name = NULL, .data = NULL, .len = 0, .result = FALSE },
467 };
468 
469 
470 static char buf[2048];
471 
472 int
main(void)473 main(void)
474 {
475 	int         i;
476 	dhcpol_t    options;
477 	struct dhcp * pkt = (struct dhcp *)buf;
478 
479 	dhcpol_init(&options);
480 
481 	for (i = 0; tests[i].name; i++) {
482 		printf("\nTest %d: ", i);
483 		bcopy(tests[i].data, pkt->dp_options, tests[i].len);
484 		if (dhcpol_parse_packet(&options, pkt,
485 		    sizeof(*pkt) + tests[i].len)
486 		    != tests[i].result) {
487 			printf("test '%s' FAILED\n", tests[i].name);
488 		} else {
489 			printf("test '%s' PASSED\n", tests[i].name);
490 		}
491 		dhcpol_free(&options);
492 	}
493 	exit(0);
494 }
495 #endif /* TEST_DHCP_OPTIONS */
496