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
82static void
83ptrlist_init(ptrlist_t * list)
84{
85 bzero(s: list, n: sizeof(*list));
86 return;
87}
88
89static void
90ptrlist_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
99static int
100ptrlist_count(ptrlist_t * list)
101{
102 if (list == NULL || list->array == NULL) {
103 return 0;
104 }
105
106 return list->count;
107}
108
109static const void *
110ptrlist_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
122static bool
123ptrlist_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
151static bool
152ptrlist_add(ptrlist_t * list, const void * element)
153{
154 if (!ptrlist_grow(list, n: 1)) {
155 return false;
156 }
157
158 list->array[list->count++] = element;
159 return true;
160}
161
162/* concatenates extra onto list */
163static bool
164ptrlist_concat(ptrlist_t * list, ptrlist_t * extra)
165{
166 if (!ptrlist_grow(list, n: extra->count)) {
167 return false;
168 }
169
170 bcopy(src: extra->array, dst: list->array + list->count,
171 n: 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 */
183boolean_t
184dhcpol_add(dhcpol_t * list, const void * element)
185{
186 return ptrlist_add(list: (ptrlist_t *)list, element);
187}
188
189int
190dhcpol_count(dhcpol_t * list)
191{
192 return ptrlist_count(list: (ptrlist_t *)list);
193}
194
195const void *
196dhcpol_element(dhcpol_t * list, int i)
197{
198 return ptrlist_element(list: (ptrlist_t *)list, i);
199}
200
201void
202dhcpol_init(dhcpol_t * list)
203{
204 ptrlist_init(list: (ptrlist_t *)list);
205}
206
207void
208dhcpol_free(dhcpol_t * list)
209{
210 ptrlist_free(list: (ptrlist_t *)list);
211}
212
213boolean_t
214dhcpol_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 */
228boolean_t
229dhcpol_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, element: 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, element: 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 */
289const void *
290dhcpol_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 */
329boolean_t
330dhcpol_parse_packet(dhcpol_t * options, const struct dhcp * pkt, int len)
331{
332 char rfc_magic[4] = RFC_OPTIONS_MAGIC;
333
334 dhcpol_init(list: 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(s1: pkt->dp_options, s2: rfc_magic, RFC_MAGIC_SIZE)) {
342 dprintf(("dhcp_options: missing magic number\n"));
343 return FALSE;
344 }
345 if (dhcpol_parse_buffer(list: options, buffer: pkt->dp_options + RFC_MAGIC_SIZE,
346 length: 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(list: options, tag: dhcptag_option_overload_e,
354 len_p: &overload_len, NULL);
355 if (overload && overload_len == 1) { /* has overloaded options */
356 dhcpol_t extra;
357
358 dhcpol_init(list: &extra);
359 if (*overload == DHCP_OVERLOAD_FILE
360 || *overload == DHCP_OVERLOAD_BOTH) {
361 if (dhcpol_parse_buffer(list: &extra, buffer: pkt->dp_file,
362 length: sizeof(pkt->dp_file))) {
363 dhcpol_concat(list: options, extra: &extra);
364 dhcpol_free(list: &extra);
365 }
366 }
367 if (*overload == DHCP_OVERLOAD_SNAME
368 || *overload == DHCP_OVERLOAD_BOTH) {
369 if (dhcpol_parse_buffer(list: &extra, buffer: pkt->dp_sname,
370 length: sizeof(pkt->dp_sname))) {
371 dhcpol_concat(list: options, extra: &extra);
372 dhcpol_free(list: &extra);
373 }
374 }
375 }
376 }
377 return TRUE;
378}
379
380#ifdef TEST_DHCP_OPTIONS
381char test_empty[] = {
382 99, 130, 83, 99,
383 255,
384};
385
386char test_short[] = {
387 99, 130, 83, 99,
388 1,
389};
390
391char 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
398char 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
407char 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
418char test_no_magic[] = {
419 0x1
420};
421struct test {
422 char * name;
423 char * data;
424 int len;
425 boolean_t result;
426};
427
428struct 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
439static char buf[2048];
440
441int
442main(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