]> git.saurik.com Git - apple/xnu.git/blob - bsd/netinet/dhcp_options.c
xnu-517.tar.gz
[apple/xnu.git] / bsd / netinet / dhcp_options.c
1 /*
2 * dhcp_options.c
3 * - routines to parse and access dhcp options
4 * and create new dhcp option areas
5 * - handles overloaded areas as well as vendor-specific options
6 * that are encoded using the RFC 2132 encoding
7 */
8
9 /*
10 * Modification History
11 *
12 * March 15, 2002 Dieter Siegmund (dieter@apple)
13 * - imported from bootp project
14 */
15
16 #include <sys/types.h>
17 #include <sys/param.h>
18 #include <netinet/in.h>
19 #include <sys/malloc.h>
20
21 #include <netinet/dhcp.h>
22 #include <netinet/dhcp_options.h>
23
24 static __inline__ void
25 my_free(void * ptr)
26 {
27 _FREE(ptr, M_TEMP);
28 }
29
30 static __inline__ void *
31 my_malloc(int size)
32 {
33 void * data;
34 MALLOC(data, void *, size, M_TEMP, M_WAITOK);
35 return (data);
36 }
37
38 static __inline__ void *
39 my_realloc(void * oldptr, int oldsize, int newsize)
40 {
41 void * data;
42
43 MALLOC(data, void *, newsize, M_TEMP, M_WAITOK);
44 bcopy(oldptr, data, oldsize);
45 my_free(oldptr);
46 return (data);
47 }
48
49 /*
50 * Functions: ptrlist_*
51 * Purpose:
52 * A dynamically growable array of pointers.
53 */
54
55 #define PTRLIST_NUMBER 16
56
57 static void
58 ptrlist_init(ptrlist_t * list)
59 {
60 bzero(list, sizeof(*list));
61 return;
62 }
63
64 static void
65 ptrlist_free(ptrlist_t * list)
66 {
67 if (list->array)
68 my_free(list->array);
69 ptrlist_init(list);
70 return;
71 }
72
73 static int
74 ptrlist_count(ptrlist_t * list)
75 {
76 if (list == NULL || list->array == NULL)
77 return (0);
78
79 return (list->count);
80 }
81
82 static void *
83 ptrlist_element(ptrlist_t * list, int i)
84 {
85 if (list->array == NULL)
86 return (NULL);
87 if (i < list->count)
88 return (list->array[i]);
89 return (NULL);
90 }
91
92
93 static boolean_t
94 ptrlist_grow(ptrlist_t * list)
95 {
96 if (list->array == NULL) {
97 if (list->size == 0)
98 list->size = PTRLIST_NUMBER;
99 list->count = 0;
100 list->array = my_malloc(sizeof(*list->array) * list->size);
101 }
102 else if (list->size == list->count) {
103 #ifdef DEBUG
104 printf("doubling %d to %d\n", list->size, list->size * 2);
105 #endif /* DEBUG */
106 list->array = my_realloc(list->array,
107 sizeof(*list->array) * list->size,
108 sizeof(*list->array) * list->size * 2);
109 list->size *= 2;
110 }
111 if (list->array == NULL)
112 return (FALSE);
113 return (TRUE);
114 }
115
116 static boolean_t
117 ptrlist_add(ptrlist_t * list, void * element)
118 {
119 if (ptrlist_grow(list) == FALSE)
120 return (FALSE);
121
122 list->array[list->count++] = element;
123 return (TRUE);
124 }
125
126 /* concatenates extra onto list */
127 static boolean_t
128 ptrlist_concat(ptrlist_t * list, ptrlist_t * extra)
129 {
130 if (extra->count == 0)
131 return (TRUE);
132
133 if ((extra->count + list->count) > list->size) {
134 int old_size = list->size;
135
136 list->size = extra->count + list->count;
137 if (list->array == NULL)
138 list->array = my_malloc(sizeof(*list->array) * list->size);
139 else
140 list->array = my_realloc(list->array, old_size,
141 sizeof(*list->array) * list->size);
142 }
143 if (list->array == NULL)
144 return (FALSE);
145 bcopy(extra->array, list->array + list->count,
146 extra->count * sizeof(*list->array));
147 list->count += extra->count;
148 return (TRUE);
149 }
150
151
152 /*
153 * Functions: dhcpol_*
154 *
155 * Purpose:
156 * Routines to parse/access existing options buffers.
157 */
158 boolean_t
159 dhcpol_add(dhcpol_t * list, void * element)
160 {
161 return (ptrlist_add((ptrlist_t *)list, element));
162 }
163
164 int
165 dhcpol_count(dhcpol_t * list)
166 {
167 return (ptrlist_count((ptrlist_t *)list));
168 }
169
170 void *
171 dhcpol_element(dhcpol_t * list, int i)
172 {
173 return (ptrlist_element((ptrlist_t *)list, i));
174 }
175
176 void
177 dhcpol_init(dhcpol_t * list)
178 {
179 ptrlist_init((ptrlist_t *)list);
180 }
181
182 void
183 dhcpol_free(dhcpol_t * list)
184 {
185 ptrlist_free((ptrlist_t *)list);
186 }
187
188 boolean_t
189 dhcpol_concat(dhcpol_t * list, dhcpol_t * extra)
190 {
191 return (ptrlist_concat((ptrlist_t *)list, (ptrlist_t *)extra));
192 }
193
194 /*
195 * Function: dhcpol_parse_buffer
196 *
197 * Purpose:
198 * Parse the given buffer into DHCP options, returning the
199 * list of option pointers in the given dhcpol_t.
200 * Parsing continues until we hit the end of the buffer or
201 * the end tag.
202 */
203 boolean_t
204 dhcpol_parse_buffer(dhcpol_t * list, void * buffer, int length,
205 unsigned char * err)
206 {
207 int len;
208 unsigned char * scan;
209 unsigned char tag;
210
211 if (err)
212 err[0] = '\0';
213
214 dhcpol_init(list);
215
216 len = length;
217 tag = dhcptag_pad_e;
218 for (scan = (unsigned char *)buffer; tag != dhcptag_end_e && len > 0; ) {
219
220 tag = scan[DHCP_TAG_OFFSET];
221
222 switch (tag) {
223 case dhcptag_end_e:
224 dhcpol_add(list, scan); /* remember that it was terminated */
225 scan++;
226 len--;
227 break;
228 case dhcptag_pad_e: /* ignore pad */
229 scan++;
230 len--;
231 break;
232 default: {
233 unsigned char option_len = scan[DHCP_LEN_OFFSET];
234
235 dhcpol_add(list, scan);
236 len -= (option_len + 2);
237 scan += (option_len + 2);
238 break;
239 }
240 }
241 }
242 if (len < 0) {
243 /* ran off the end */
244 if (err)
245 sprintf(err, "parse failed near tag %d", tag);
246 dhcpol_free(list);
247 return (FALSE);
248 }
249 return (TRUE);
250 }
251
252 /*
253 * Function: dhcpol_find
254 *
255 * Purpose:
256 * Finds the first occurence of the given option, and returns its
257 * length and the option data pointer.
258 *
259 * The optional start parameter allows this function to
260 * return the next start point so that successive
261 * calls will retrieve the next occurence of the option.
262 * Before the first call, *start should be set to 0.
263 */
264 void *
265 dhcpol_find(dhcpol_t * list, int tag, int * len_p, int * start)
266 {
267 int i = 0;
268
269 if (tag == dhcptag_end_e || tag == dhcptag_pad_e)
270 return (NULL);
271
272 if (start)
273 i = *start;
274
275 for (; i < dhcpol_count(list); i++) {
276 unsigned char * option = dhcpol_element(list, i);
277
278 if (option[DHCP_TAG_OFFSET] == tag) {
279 if (len_p)
280 *len_p = option[DHCP_LEN_OFFSET];
281 if (start)
282 *start = i + 1;
283 return (option + DHCP_OPTION_OFFSET);
284 }
285 }
286 return (NULL);
287 }
288
289 /*
290 * Function: dhcpol_get
291 *
292 * Purpose:
293 * Accumulate all occurences of the given option into a
294 * malloc'd buffer, and return its length. Used to get
295 * all occurrences of a particular option in a single
296 * data area.
297 * Note:
298 * Use _FREE(val, M_TEMP) to free the returned data area.
299 */
300 void *
301 dhcpol_get(dhcpol_t * list, int tag, int * len_p)
302 {
303 int i;
304 char * data = NULL;
305 int data_len = 0;
306
307 if (tag == dhcptag_end_e || tag == dhcptag_pad_e)
308 return (NULL);
309
310 for (i = 0; i < dhcpol_count(list); i++) {
311 unsigned char * option = dhcpol_element(list, i);
312
313 if (option[DHCP_TAG_OFFSET] == tag) {
314 int len = option[DHCP_LEN_OFFSET];
315
316 if (data_len == 0) {
317 data = my_malloc(len);
318 }
319 else {
320 data = my_realloc(data, data_len, data_len + len);
321 }
322 bcopy(option + DHCP_OPTION_OFFSET, data + data_len, len);
323 data_len += len;
324 }
325 }
326 *len_p = data_len;
327 return (data);
328 }
329
330 /*
331 * Function: dhcpol_parse_packet
332 *
333 * Purpose:
334 * Parse the option areas in the DHCP packet.
335 * Verifies that the packet has the right magic number,
336 * then parses and accumulates the option areas.
337 * First the pkt->dp_options is parsed. If that contains
338 * the overload option, it parses pkt->dp_file if specified,
339 * then parses pkt->dp_sname if specified.
340 */
341 boolean_t
342 dhcpol_parse_packet(dhcpol_t * options, struct dhcp * pkt, int len,
343 unsigned char * err)
344 {
345 char rfc_magic[4] = RFC_OPTIONS_MAGIC;
346
347 dhcpol_init(options); /* make sure it's empty */
348
349 if (err)
350 err[0] = '\0';
351
352 if (len < (sizeof(*pkt) + RFC_MAGIC_SIZE)) {
353 if (err) {
354 sprintf(err, "packet is too short: %d < %d",
355 len, (int)sizeof(*pkt) + RFC_MAGIC_SIZE);
356 }
357 return (FALSE);
358 }
359 if (bcmp(pkt->dp_options, rfc_magic, RFC_MAGIC_SIZE)) {
360 if (err)
361 sprintf(err, "missing magic number");
362 return (FALSE);
363 }
364 if (dhcpol_parse_buffer(options, pkt->dp_options + RFC_MAGIC_SIZE,
365 len - sizeof(*pkt) - RFC_MAGIC_SIZE, err) == FALSE)
366 return (FALSE);
367 { /* get overloaded options */
368 unsigned char * overload;
369 int overload_len;
370
371 overload = (unsigned char *)
372 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), NULL)) {
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), NULL)) {
390 dhcpol_concat(options, &extra);
391 dhcpol_free(&extra);
392 }
393 }
394 }
395 }
396 return (TRUE);
397 }
398
399 /*
400 * Function: dhcpol_parse_vendor
401 *
402 * Purpose:
403 * Given a set of options, find the vendor specific option(s)
404 * and parse all of them into a single option list.
405 *
406 * Return value:
407 * TRUE if vendor specific options existed and were parsed succesfully,
408 * FALSE otherwise.
409 */
410 boolean_t
411 dhcpol_parse_vendor(dhcpol_t * vendor, dhcpol_t * options,
412 unsigned char * err)
413 {
414 dhcpol_t extra;
415 boolean_t ret = FALSE;
416 int start = 0;
417
418 if (err)
419 err[0] = '\0';
420
421 dhcpol_init(vendor);
422 dhcpol_init(&extra);
423
424 for (;;) {
425 void * data;
426 int len;
427
428 data = dhcpol_find(options, dhcptag_vendor_specific_e, &len, &start);
429 if (data == NULL) {
430 break; /* out of for */
431 }
432
433 if (dhcpol_parse_buffer(&extra, data, len, err) == FALSE) {
434 goto failed;
435 }
436
437 if (dhcpol_concat(vendor, &extra) == FALSE) {
438 if (err)
439 sprintf(err, "dhcpol_concat() failed at %d\n", start);
440 goto failed;
441 }
442 dhcpol_free(&extra);
443 ret = TRUE;
444 }
445 if (ret == FALSE) {
446 if (err)
447 strcpy(err, "missing vendor specific options");
448 }
449 return (ret);
450
451 failed:
452 dhcpol_free(vendor);
453 dhcpol_free(&extra);
454 return (FALSE);
455 }
456
457 #ifdef TEST_DHCP_OPTIONS
458 char test_empty[] = {
459 99, 130, 83, 99,
460 255,
461 };
462
463 char test_simple[] = {
464 99, 130, 83, 99,
465 1, 4, 255, 255, 252, 0,
466 3, 4, 17, 202, 40, 1,
467 255,
468 };
469
470 char test_vendor[] = {
471 99, 130, 83, 99,
472 1, 4, 255, 255, 252, 0,
473 3, 4, 17, 202, 40, 1,
474 43, 6, 1, 4, 1, 2, 3, 4,
475 43, 6, 1, 4, 1, 2, 3, 4,
476 255,
477 };
478
479 char test_no_end[] = {
480 0x63, 0x82, 0x53, 0x63, 0x35, 0x01, 0x05, 0x36,
481 0x04, 0xc0, 0xa8, 0x01, 0x01, 0x33, 0x04, 0x80,
482 0x00, 0x80, 0x00, 0x01, 0x04, 0xff, 0xff, 0xff,
483 0x00, 0x03, 0x04, 0xc0, 0xa8, 0x01, 0x01, 0x06,
484 0x0c, 0x18, 0x1a, 0xa3, 0x21, 0x18, 0x1a, 0xa3,
485 0x20, 0x18, 0x5e, 0xa3, 0x21, 0x00, 0x00, 0x00,
486 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
487 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
488 };
489
490 char test_too_short[] = {
491 0x1
492 };
493 struct test {
494 char * name;
495 char * data;
496 int len;
497 boolean_t result;
498 };
499
500 struct test tests[] = {
501 { "empty", test_empty, sizeof(test_empty), TRUE },
502 { "simple", test_simple, sizeof(test_simple), TRUE },
503 { "vendor", test_vendor, sizeof(test_vendor), TRUE },
504 { "no_end", test_no_end, sizeof(test_no_end), TRUE },
505 { "too_short", test_too_short, sizeof(test_too_short), FALSE },
506 { NULL, NULL, 0, FALSE },
507 };
508
509
510 static char buf[2048];
511
512 int
513 main()
514 {
515 int i;
516 dhcpol_t options;
517 char error[256];
518 struct dhcp * pkt = (struct dhcp *)buf;
519
520 dhcpol_init(&options);
521
522 for (i = 0; tests[i].name; i++) {
523 printf("\nTest %d: ", i);
524 bcopy(tests[i].data, pkt->dp_options, tests[i].len);
525 if (dhcpol_parse_packet(&options, pkt,
526 sizeof(*pkt) + tests[i].len,
527 error) != tests[i].result) {
528 printf("test '%s' FAILED\n", tests[i].name);
529 if (tests[i].result == TRUE) {
530 printf("error message returned was %s\n", error);
531 }
532 }
533 else {
534 printf("test '%s' PASSED\n", tests[i].name);
535 if (tests[i].result == FALSE) {
536 printf("error message returned was %s\n", error);
537 }
538 }
539 dhcpol_free(&options);
540 }
541 exit(0);
542 }
543 #endif /* TEST_DHCP_OPTIONS */