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