1/* 2 * Copyright (c) 2002-2007 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 <libkern/libkern.h> 49 50#include <netinet/dhcp.h> 51#include <netinet/dhcp_options.h> 52 53#ifdef DHCP_DEBUG 54#define dprintf(x) printf x; 55#else /* !DHCP_DEBUG */ 56#define dprintf(x) 57#endif /* DHCP_DEBUG */ 58 59static __inline__ void 60my_free(void * ptr) 61{ 62 _FREE(ptr, M_TEMP); 63} 64 65static __inline__ void * 66my_malloc(int size) 67{ 68 void * data; 69 MALLOC(data, void *, size, M_TEMP, M_WAITOK); 70 return (data); 71} 72 73static __inline__ void * 74my_realloc(void * oldptr, int oldsize, int newsize) 75{ 76 void * data; 77 78 MALLOC(data, void *, newsize, M_TEMP, M_WAITOK); 79 bcopy(oldptr, data, oldsize); 80 my_free(oldptr); 81 return (data); 82} 83 84/* 85 * Functions: ptrlist_* 86 * Purpose: 87 * A dynamically growable array of pointers. 88 */ 89 90#define PTRLIST_NUMBER 16 91 92static void 93ptrlist_init(ptrlist_t * list) 94{ 95 bzero(list, sizeof(*list)); 96 return; 97} 98 99static void 100ptrlist_free(ptrlist_t * list) 101{ 102 if (list->array) 103 my_free(list->array); 104 ptrlist_init(list); 105 return; 106} 107 108static int 109ptrlist_count(ptrlist_t * list) 110{ 111 if (list == NULL || list->array == NULL) 112 return (0); 113 114 return (list->count); 115} 116 117static const void * 118ptrlist_element(ptrlist_t * list, int i) 119{ 120 if (list->array == NULL) 121 return (NULL); 122 if (i < list->count) 123 return (list->array[i]); 124 return (NULL); 125} 126 127 128static boolean_t 129ptrlist_grow(ptrlist_t * list) 130{ 131 if (list->array == NULL) { 132 if (list->size == 0) 133 list->size = PTRLIST_NUMBER; 134 list->count = 0; 135 list->array = my_malloc(sizeof(*list->array) * list->size); 136 } 137 else if (list->size == list->count) { 138 dprintf(("doubling %d to %d\n", list->size, list->size * 2)); 139 list->array = my_realloc(list->array, 140 sizeof(*list->array) * list->size, 141 sizeof(*list->array) * list->size * 2); 142 list->size *= 2; 143 } 144 if (list->array == NULL) 145 return (FALSE); 146 return (TRUE); 147} 148 149static boolean_t 150ptrlist_add(ptrlist_t * list, const void * element) 151{ 152 if (ptrlist_grow(list) == FALSE) 153 return (FALSE); 154 155 list->array[list->count++] = element; 156 return (TRUE); 157} 158 159/* concatenates extra onto list */ 160static boolean_t 161ptrlist_concat(ptrlist_t * list, ptrlist_t * extra) 162{ 163 if (extra->count == 0) 164 return (TRUE); 165 166 if ((extra->count + list->count) > list->size) { 167 int old_size = list->size; 168 169 list->size = extra->count + list->count; 170 if (list->array == NULL) 171 list->array = my_malloc(sizeof(*list->array) * list->size); 172 else 173 list->array = my_realloc(list->array, old_size, 174 sizeof(*list->array) * list->size); 175 } 176 if (list->array == NULL) 177 return (FALSE); 178 bcopy(extra->array, list->array + list->count, 179 extra->count * sizeof(*list->array)); 180 list->count += extra->count; 181 return (TRUE); 182} 183 184 185/* 186 * Functions: dhcpol_* 187 * 188 * Purpose: 189 * Routines to parse/access existing options buffers. 190 */ 191boolean_t 192dhcpol_add(dhcpol_t * list, const void * element) 193{ 194 return (ptrlist_add((ptrlist_t *)list, element)); 195} 196 197int 198dhcpol_count(dhcpol_t * list) 199{ 200 return (ptrlist_count((ptrlist_t *)list)); 201} 202 203const void * 204dhcpol_element(dhcpol_t * list, int i) 205{ 206 return (ptrlist_element((ptrlist_t *)list, i)); 207} 208 209void 210dhcpol_init(dhcpol_t * list) 211{ 212 ptrlist_init((ptrlist_t *)list); 213} 214 215void 216dhcpol_free(dhcpol_t * list) 217{ 218 ptrlist_free((ptrlist_t *)list); 219} 220 221boolean_t 222dhcpol_concat(dhcpol_t * list, dhcpol_t * extra) 223{ 224 return (ptrlist_concat((ptrlist_t *)list, (ptrlist_t *)extra)); 225} 226 227/* 228 * Function: dhcpol_parse_buffer 229 * 230 * Purpose: 231 * Parse the given buffer into DHCP options, returning the 232 * list of option pointers in the given dhcpol_t. 233 * Parsing continues until we hit the end of the buffer or 234 * the end tag. 235 */ 236boolean_t 237dhcpol_parse_buffer(dhcpol_t * list, const void * buffer, int length) 238{ 239 int len; 240 const uint8_t * scan; 241 uint8_t tag; 242 243 dhcpol_init(list); 244 245 len = length; 246 tag = dhcptag_pad_e; 247 for (scan = (const uint8_t *)buffer; tag != dhcptag_end_e && len > 0; ) { 248 249 tag = scan[DHCP_TAG_OFFSET]; 250 251 switch (tag) { 252 case dhcptag_end_e: 253 /* remember that it was terminated */ 254 dhcpol_add(list, scan); 255 scan++; 256 len--; 257 break; 258 case dhcptag_pad_e: /* ignore pad */ 259 scan++; 260 len--; 261 break; 262 default: { 263 uint8_t option_len = scan[DHCP_LEN_OFFSET]; 264 265 dhcpol_add(list, scan); 266 len -= (option_len + 2); 267 scan += (option_len + 2); 268 break; 269 } 270 } 271 } 272 if (len < 0) { 273 /* ran off the end */ 274 dprintf(("dhcp_options: parse failed near tag %d", tag)); 275 dhcpol_free(list); 276 return (FALSE); 277 } 278 return (TRUE); 279} 280 281/* 282 * Function: dhcpol_find 283 * 284 * Purpose: 285 * Finds the first occurence of the given option, and returns its 286 * length and the option data pointer. 287 * 288 * The optional start parameter allows this function to 289 * return the next start point so that successive 290 * calls will retrieve the next occurence of the option. 291 * Before the first call, *start should be set to 0. 292 */ 293const void * 294dhcpol_find(dhcpol_t * list, int tag, int * len_p, int * start) 295{ 296 int i = 0; 297 298 if (tag == dhcptag_end_e || tag == dhcptag_pad_e) 299 return (NULL); 300 301 if (start) 302 i = *start; 303 304 for (; i < dhcpol_count(list); i++) { 305 const uint8_t * option = dhcpol_element(list, i); 306 307 if (option[DHCP_TAG_OFFSET] == tag) { 308 if (len_p) 309 *len_p = option[DHCP_LEN_OFFSET]; 310 if (start) 311 *start = i + 1; 312 return (option + DHCP_OPTION_OFFSET); 313 } 314 } 315 return (NULL); 316} 317 318#if 0 319/* 320 * Function: dhcpol_get 321 * 322 * Purpose: 323 * Accumulate all occurences of the given option into a 324 * malloc'd buffer, and return its length. Used to get 325 * all occurrences of a particular option in a single 326 * data area. 327 * Note: 328 * Use _FREE(val, M_TEMP) to free the returned data area. 329 */ 330void * 331dhcpol_get(dhcpol_t * list, int tag, int * len_p) 332{ 333 int i; 334 char * data = NULL; 335 int data_len = 0; 336 337 if (tag == dhcptag_end_e || tag == dhcptag_pad_e) 338 return (NULL); 339 340 for (i = 0; i < dhcpol_count(list); i++) { 341 const uint8_t * option = dhcpol_element(list, i); 342 343 if (option[DHCP_TAG_OFFSET] == tag) { 344 int len = option[DHCP_LEN_OFFSET]; 345 346 if (data_len == 0) { 347 data = my_malloc(len); 348 } 349 else { 350 data = my_realloc(data, data_len, data_len + len); 351 } 352 bcopy(option + DHCP_OPTION_OFFSET, data + data_len, len); 353 data_len += len; 354 } 355 } 356 *len_p = data_len; 357 return (data); 358} 359#endif 0 360 361/* 362 * Function: dhcpol_parse_packet 363 * 364 * Purpose: 365 * Parse the option areas in the DHCP packet. 366 * Verifies that the packet has the right magic number, 367 * then parses and accumulates the option areas. 368 * First the pkt->dp_options is parsed. If that contains 369 * the overload option, it parses pkt->dp_file if specified, 370 * then parses pkt->dp_sname if specified. 371 */ 372boolean_t 373dhcpol_parse_packet(dhcpol_t * options, const struct dhcp * pkt, int len) 374{ 375 char rfc_magic[4] = RFC_OPTIONS_MAGIC; 376 377 dhcpol_init(options); /* make sure it's empty */ 378 379 if (len < (sizeof(*pkt) + RFC_MAGIC_SIZE)) { 380 dprintf(("dhcp_options: packet is too short: %d < %d\n", 381 len, (int)sizeof(*pkt) + RFC_MAGIC_SIZE)); 382 return (FALSE); 383 } 384 if (bcmp(pkt->dp_options, rfc_magic, RFC_MAGIC_SIZE)) { 385 dprintf(("dhcp_options: missing magic number\n")); 386 return (FALSE); 387 } 388 if (dhcpol_parse_buffer(options, pkt->dp_options + RFC_MAGIC_SIZE, 389 len - sizeof(*pkt) - RFC_MAGIC_SIZE) == FALSE) 390 return (FALSE); 391 { /* get overloaded options */ 392 const uint8_t * overload; 393 int overload_len; 394 395 overload = dhcpol_find(options, dhcptag_option_overload_e, 396 &overload_len, NULL); 397 if (overload && overload_len == 1) { /* has overloaded options */ 398 dhcpol_t extra; 399 400 dhcpol_init(&extra); 401 if (*overload == DHCP_OVERLOAD_FILE 402 || *overload == DHCP_OVERLOAD_BOTH) { 403 if (dhcpol_parse_buffer(&extra, pkt->dp_file, 404 sizeof(pkt->dp_file))) { 405 dhcpol_concat(options, &extra); 406 dhcpol_free(&extra); 407 } 408 } 409 if (*overload == DHCP_OVERLOAD_SNAME 410 || *overload == DHCP_OVERLOAD_BOTH) { 411 if (dhcpol_parse_buffer(&extra, pkt->dp_sname, 412 sizeof(pkt->dp_sname))) { 413 dhcpol_concat(options, &extra); 414 dhcpol_free(&extra); 415 } 416 } 417 } 418 } 419 return (TRUE); 420} 421 422/* 423 * Module: dhcpoa 424 * 425 * Purpose: 426 * Types and functions to create new dhcp option areas. 427 */ 428 429/* 430 * Function: dhcpoa_{init_common, init_no_end, init} 431 * 432 * Purpose: 433 * Initialize an option area structure so that it can be used 434 * in calling the dhcpoa_* routines. 435 */ 436static void 437dhcpoa_init_common(dhcpoa_t * oa_p, void * buffer, int size, int reserve) 438{ 439 bzero(oa_p, sizeof(*oa_p)); 440 oa_p->oa_buffer = buffer; 441 oa_p->oa_size = size; 442 oa_p->oa_reserve = reserve; 443} 444 445void 446dhcpoa_init_no_end(dhcpoa_t * oa_p, void * buffer, int size) 447{ 448 dhcpoa_init_common(oa_p, buffer, size, 0); 449 return; 450} 451 452int 453dhcpoa_size(dhcpoa_t * oa_p) 454{ 455 return (oa_p->oa_size); 456} 457 458void 459dhcpoa_init(dhcpoa_t * oa_p, void * buffer, int size) 460{ 461 /* initialize the area, reserve space for the end tag */ 462 dhcpoa_init_common(oa_p, buffer, size, 1); 463 return; 464} 465/* 466 * Function: dhcpoa_add 467 * 468 * Purpose: 469 * Add an option to the option area. 470 */ 471dhcpoa_ret_t 472dhcpoa_add(dhcpoa_t * oa_p, dhcptag_t tag, int len, const void * option) 473{ 474 if (len > DHCP_OPTION_SIZE_MAX) { 475 dprintf(("tag %d option %d > %d\n", tag, len, DHCP_OPTION_SIZE_MAX)); 476 return (dhcpoa_failed_e); 477 } 478 479 if (oa_p->oa_end_tag) { 480 dprintf(("attempt to add data after end tag\n")); 481 return (dhcpoa_failed_e); 482 } 483 484 switch (tag) { 485 case dhcptag_end_e: 486 if ((oa_p->oa_offset + 1) > oa_p->oa_size) { 487 /* this can't happen since we're careful to leave space */ 488 dprintf(("can't add end tag %d > %d\n", 489 oa_p->oa_offset + oa_p->oa_reserve, oa_p->oa_size)); 490 return (dhcpoa_failed_e); 491 } 492 ((uint8_t *)oa_p->oa_buffer)[oa_p->oa_offset + DHCP_TAG_OFFSET] = tag; 493 oa_p->oa_offset++; 494 oa_p->oa_end_tag = 1; 495 break; 496 497 case dhcptag_pad_e: 498 /* 1 for pad tag */ 499 if ((oa_p->oa_offset + oa_p->oa_reserve + 1) > oa_p->oa_size) { 500 dprintf(("can't add pad tag %d > %d\n", 501 oa_p->oa_offset + oa_p->oa_reserve + 1, oa_p->oa_size)); 502 return (dhcpoa_full_e); 503 } 504 ((uint8_t *)oa_p->oa_buffer)[oa_p->oa_offset + DHCP_TAG_OFFSET] = tag; 505 oa_p->oa_offset++; 506 break; 507 508 default: 509 /* 2 for tag/len */ 510 if ((oa_p->oa_offset + len + 2 + oa_p->oa_reserve) > oa_p->oa_size) { 511 dprintf(("can't add tag %d (%d > %d)\n", tag, 512 oa_p->oa_offset + len + 2 + oa_p->oa_reserve, 513 oa_p->oa_size)); 514 return (dhcpoa_full_e); 515 } 516 ((uint8_t *)oa_p->oa_buffer)[oa_p->oa_offset + DHCP_TAG_OFFSET] = tag; 517 ((uint8_t *)oa_p->oa_buffer)[oa_p->oa_offset + DHCP_LEN_OFFSET] = (uint8_t)len; 518 if (len) { 519 memcpy(oa_p->oa_buffer + (DHCP_OPTION_OFFSET + oa_p->oa_offset), 520 option, len); 521 } 522 oa_p->oa_offset += len + DHCP_OPTION_OFFSET; 523 break; 524 } 525 oa_p->oa_option_count++; 526 return (dhcpoa_success_e); 527} 528 529/* 530 * Function: dhcpoa_add_dhcpmsg 531 * 532 * Purpose: 533 * Add a dhcp message option to the option area. 534 */ 535dhcpoa_ret_t 536dhcpoa_add_dhcpmsg(dhcpoa_t * oa_p, dhcp_msgtype_t msgtype) 537{ 538 return (dhcpoa_add(oa_p, dhcptag_dhcp_message_type_e, 539 sizeof(msgtype), &msgtype)); 540} 541 542int 543dhcpoa_used(dhcpoa_t * oa_p) 544{ 545 return (oa_p->oa_offset); 546} 547 548int 549dhcpoa_freespace(dhcpoa_t * oa_p) 550{ 551 int freespace; 552 553 freespace = oa_p->oa_size - oa_p->oa_offset - oa_p->oa_reserve; 554 if (freespace < 0) { 555 freespace = 0; 556 } 557 return (freespace); 558} 559 560int 561dhcpoa_count(dhcpoa_t * oa_p) 562{ 563 return (oa_p->oa_option_count); 564} 565 566void * 567dhcpoa_buffer(dhcpoa_t * oa_p) 568{ 569 return (oa_p->oa_buffer); 570} 571 572 573#ifdef TEST_DHCP_OPTIONS 574char test_empty[] = { 575 99, 130, 83, 99, 576 255, 577}; 578 579char test_simple[] = { 580 99, 130, 83, 99, 581 1, 4, 255, 255, 252, 0, 582 3, 4, 17, 202, 40, 1, 583 255, 584}; 585 586char test_vendor[] = { 587 99, 130, 83, 99, 588 1, 4, 255, 255, 252, 0, 589 3, 4, 17, 202, 40, 1, 590 43, 6, 1, 4, 1, 2, 3, 4, 591 43, 6, 1, 4, 1, 2, 3, 4, 592 255, 593}; 594 595char test_no_end[] = { 596 0x63, 0x82, 0x53, 0x63, 0x35, 0x01, 0x05, 0x36, 597 0x04, 0xc0, 0xa8, 0x01, 0x01, 0x33, 0x04, 0x80, 598 0x00, 0x80, 0x00, 0x01, 0x04, 0xff, 0xff, 0xff, 599 0x00, 0x03, 0x04, 0xc0, 0xa8, 0x01, 0x01, 0x06, 600 0x0c, 0x18, 0x1a, 0xa3, 0x21, 0x18, 0x1a, 0xa3, 601 0x20, 0x18, 0x5e, 0xa3, 0x21, 0x00, 0x00, 0x00, 602 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 603 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 604}; 605 606char test_too_short[] = { 607 0x1 608}; 609struct test { 610 char * name; 611 char * data; 612 int len; 613 boolean_t result; 614}; 615 616struct test tests[] = { 617 { "empty", test_empty, sizeof(test_empty), TRUE }, 618 { "simple", test_simple, sizeof(test_simple), TRUE }, 619 { "vendor", test_vendor, sizeof(test_vendor), TRUE }, 620 { "no_end", test_no_end, sizeof(test_no_end), TRUE }, 621 { "too_short", test_too_short, sizeof(test_too_short), FALSE }, 622 { NULL, NULL, 0, FALSE }, 623}; 624 625 626static char buf[2048]; 627 628int 629main() 630{ 631 int i; 632 dhcpol_t options; 633 struct dhcp * pkt = (struct dhcp *)buf; 634 635 dhcpol_init(&options); 636 637 for (i = 0; tests[i].name; i++) { 638 printf("\nTest %d: ", i); 639 bcopy(tests[i].data, pkt->dp_options, tests[i].len); 640 if (dhcpol_parse_packet(&options, pkt, 641 sizeof(*pkt) + tests[i].len) 642 != tests[i].result) { 643 printf("test '%s' FAILED\n", tests[i].name); 644 } 645 else { 646 printf("test '%s' PASSED\n", tests[i].name); 647 } 648 dhcpol_free(&options); 649 } 650 exit(0); 651} 652#endif /* TEST_DHCP_OPTIONS */ 653