X-Git-Url: https://git.saurik.com/apple/xnu.git/blobdiff_plain/d52fe63fc81f7e44faaae711812a211a78434976..9bccf70c0258c7cac2dcb80011b2a964d884c552:/bsd/netinet/dhcp_options.c diff --git a/bsd/netinet/dhcp_options.c b/bsd/netinet/dhcp_options.c new file mode 100644 index 000000000..f6114a058 --- /dev/null +++ b/bsd/netinet/dhcp_options.c @@ -0,0 +1,543 @@ +/* + * dhcp_options.c + * - routines to parse and access dhcp options + * and create new dhcp option areas + * - handles overloaded areas as well as vendor-specific options + * that are encoded using the RFC 2132 encoding + */ + +/* + * Modification History + * + * March 15, 2002 Dieter Siegmund (dieter@apple) + * - imported from bootp project + */ + +#include +#include +#include +#include + +#include +#include + +static __inline__ void +my_free(void * ptr) +{ + _FREE(ptr, M_TEMP); +} + +static __inline__ void * +my_malloc(int size) +{ + void * data; + MALLOC(data, void *, size, M_TEMP, M_WAITOK); + return (data); +} + +static __inline__ void * +my_realloc(void * oldptr, int oldsize, int newsize) +{ + void * data; + + MALLOC(data, void *, newsize, M_TEMP, M_WAITOK); + bcopy(oldptr, data, oldsize); + my_free(oldptr); + return (data); +} + +/* + * Functions: ptrlist_* + * Purpose: + * A dynamically growable array of pointers. + */ + +#define PTRLIST_NUMBER 16 + +static void +ptrlist_init(ptrlist_t * list) +{ + bzero(list, sizeof(*list)); + return; +} + +static void +ptrlist_free(ptrlist_t * list) +{ + if (list->array) + my_free(list->array); + ptrlist_init(list); + return; +} + +static int +ptrlist_count(ptrlist_t * list) +{ + if (list == NULL || list->array == NULL) + return (0); + + return (list->count); +} + +static void * +ptrlist_element(ptrlist_t * list, int i) +{ + if (list->array == NULL) + return (NULL); + if (i < list->count) + return (list->array[i]); + return (NULL); +} + + +static boolean_t +ptrlist_grow(ptrlist_t * list) +{ + if (list->array == NULL) { + if (list->size == 0) + list->size = PTRLIST_NUMBER; + list->count = 0; + list->array = my_malloc(sizeof(*list->array) * list->size); + } + else if (list->size == list->count) { +#ifdef DEBUG + printf("doubling %d to %d\n", list->size, list->size * 2); +#endif DEBUG + list->array = my_realloc(list->array, + sizeof(*list->array) * list->size, + sizeof(*list->array) * list->size * 2); + list->size *= 2; + } + if (list->array == NULL) + return (FALSE); + return (TRUE); +} + +static boolean_t +ptrlist_add(ptrlist_t * list, void * element) +{ + if (ptrlist_grow(list) == FALSE) + return (FALSE); + + list->array[list->count++] = element; + return (TRUE); +} + +/* concatenates extra onto list */ +static boolean_t +ptrlist_concat(ptrlist_t * list, ptrlist_t * extra) +{ + if (extra->count == 0) + return (TRUE); + + if ((extra->count + list->count) > list->size) { + int old_size = list->size; + + list->size = extra->count + list->count; + if (list->array == NULL) + list->array = my_malloc(sizeof(*list->array) * list->size); + else + list->array = my_realloc(list->array, old_size, + sizeof(*list->array) * list->size); + } + if (list->array == NULL) + return (FALSE); + bcopy(extra->array, list->array + list->count, + extra->count * sizeof(*list->array)); + list->count += extra->count; + return (TRUE); +} + + +/* + * Functions: dhcpol_* + * + * Purpose: + * Routines to parse/access existing options buffers. + */ +boolean_t +dhcpol_add(dhcpol_t * list, void * element) +{ + return (ptrlist_add((ptrlist_t *)list, element)); +} + +int +dhcpol_count(dhcpol_t * list) +{ + return (ptrlist_count((ptrlist_t *)list)); +} + +void * +dhcpol_element(dhcpol_t * list, int i) +{ + return (ptrlist_element((ptrlist_t *)list, i)); +} + +void +dhcpol_init(dhcpol_t * list) +{ + ptrlist_init((ptrlist_t *)list); +} + +void +dhcpol_free(dhcpol_t * list) +{ + ptrlist_free((ptrlist_t *)list); +} + +boolean_t +dhcpol_concat(dhcpol_t * list, dhcpol_t * extra) +{ + return (ptrlist_concat((ptrlist_t *)list, (ptrlist_t *)extra)); +} + +/* + * Function: dhcpol_parse_buffer + * + * Purpose: + * Parse the given buffer into DHCP options, returning the + * list of option pointers in the given dhcpol_t. + * Parsing continues until we hit the end of the buffer or + * the end tag. + */ +boolean_t +dhcpol_parse_buffer(dhcpol_t * list, void * buffer, int length, + unsigned char * err) +{ + int len; + unsigned char * scan; + unsigned char tag; + + if (err) + err[0] = '\0'; + + dhcpol_init(list); + + len = length; + tag = dhcptag_pad_e; + for (scan = (unsigned char *)buffer; tag != dhcptag_end_e && len > 0; ) { + + tag = scan[DHCP_TAG_OFFSET]; + + switch (tag) { + case dhcptag_end_e: + dhcpol_add(list, scan); /* remember that it was terminated */ + scan++; + len--; + break; + case dhcptag_pad_e: /* ignore pad */ + scan++; + len--; + break; + default: { + unsigned char option_len = scan[DHCP_LEN_OFFSET]; + + dhcpol_add(list, scan); + len -= (option_len + 2); + scan += (option_len + 2); + break; + } + } + } + if (len < 0) { + /* ran off the end */ + if (err) + sprintf(err, "parse failed near tag %d", tag); + dhcpol_free(list); + return (FALSE); + } + return (TRUE); +} + +/* + * Function: dhcpol_find + * + * Purpose: + * Finds the first occurence of the given option, and returns its + * length and the option data pointer. + * + * The optional start parameter allows this function to + * return the next start point so that successive + * calls will retrieve the next occurence of the option. + * Before the first call, *start should be set to 0. + */ +void * +dhcpol_find(dhcpol_t * list, int tag, int * len_p, int * start) +{ + int i = 0; + + if (tag == dhcptag_end_e || tag == dhcptag_pad_e) + return (NULL); + + if (start) + i = *start; + + for (; i < dhcpol_count(list); i++) { + unsigned char * option = dhcpol_element(list, i); + + if (option[DHCP_TAG_OFFSET] == tag) { + if (len_p) + *len_p = option[DHCP_LEN_OFFSET]; + if (start) + *start = i + 1; + return (option + DHCP_OPTION_OFFSET); + } + } + return (NULL); +} + +/* + * Function: dhcpol_get + * + * Purpose: + * Accumulate all occurences of the given option into a + * malloc'd buffer, and return its length. Used to get + * all occurrences of a particular option in a single + * data area. + * Note: + * Use _FREE(val, M_TEMP) to free the returned data area. + */ +void * +dhcpol_get(dhcpol_t * list, int tag, int * len_p) +{ + int i; + char * data = NULL; + int data_len = 0; + + if (tag == dhcptag_end_e || tag == dhcptag_pad_e) + return (NULL); + + for (i = 0; i < dhcpol_count(list); i++) { + unsigned char * option = dhcpol_element(list, i); + + if (option[DHCP_TAG_OFFSET] == tag) { + int len = option[DHCP_LEN_OFFSET]; + + if (data_len == 0) { + data = my_malloc(len); + } + else { + data = my_realloc(data, data_len, data_len + len); + } + bcopy(option + DHCP_OPTION_OFFSET, data + data_len, len); + data_len += len; + } + } + *len_p = data_len; + return (data); +} + +/* + * Function: dhcpol_parse_packet + * + * Purpose: + * Parse the option areas in the DHCP packet. + * Verifies that the packet has the right magic number, + * then parses and accumulates the option areas. + * First the pkt->dp_options is parsed. If that contains + * the overload option, it parses pkt->dp_file if specified, + * then parses pkt->dp_sname if specified. + */ +boolean_t +dhcpol_parse_packet(dhcpol_t * options, struct dhcp * pkt, int len, + unsigned char * err) +{ + char rfc_magic[4] = RFC_OPTIONS_MAGIC; + + dhcpol_init(options); /* make sure it's empty */ + + if (err) + err[0] = '\0'; + + if (len < (sizeof(*pkt) + RFC_MAGIC_SIZE)) { + if (err) { + sprintf(err, "packet is too short: %d < %d", + len, (int)sizeof(*pkt) + RFC_MAGIC_SIZE); + } + return (FALSE); + } + if (bcmp(pkt->dp_options, rfc_magic, RFC_MAGIC_SIZE)) { + if (err) + sprintf(err, "missing magic number"); + return (FALSE); + } + if (dhcpol_parse_buffer(options, pkt->dp_options + RFC_MAGIC_SIZE, + len - sizeof(*pkt) - RFC_MAGIC_SIZE, err) == FALSE) + return (FALSE); + { /* get overloaded options */ + unsigned char * overload; + int overload_len; + + overload = (unsigned char *) + dhcpol_find(options, dhcptag_option_overload_e, + &overload_len, NULL); + if (overload && overload_len == 1) { /* has overloaded options */ + dhcpol_t extra; + + dhcpol_init(&extra); + if (*overload == DHCP_OVERLOAD_FILE + || *overload == DHCP_OVERLOAD_BOTH) { + if (dhcpol_parse_buffer(&extra, pkt->dp_file, + sizeof(pkt->dp_file), NULL)) { + dhcpol_concat(options, &extra); + dhcpol_free(&extra); + } + } + if (*overload == DHCP_OVERLOAD_SNAME + || *overload == DHCP_OVERLOAD_BOTH) { + if (dhcpol_parse_buffer(&extra, pkt->dp_sname, + sizeof(pkt->dp_sname), NULL)) { + dhcpol_concat(options, &extra); + dhcpol_free(&extra); + } + } + } + } + return (TRUE); +} + +/* + * Function: dhcpol_parse_vendor + * + * Purpose: + * Given a set of options, find the vendor specific option(s) + * and parse all of them into a single option list. + * + * Return value: + * TRUE if vendor specific options existed and were parsed succesfully, + * FALSE otherwise. + */ +boolean_t +dhcpol_parse_vendor(dhcpol_t * vendor, dhcpol_t * options, + unsigned char * err) +{ + dhcpol_t extra; + boolean_t ret = FALSE; + int start = 0; + + if (err) + err[0] = '\0'; + + dhcpol_init(vendor); + dhcpol_init(&extra); + + for (;;) { + void * data; + int len; + + data = dhcpol_find(options, dhcptag_vendor_specific_e, &len, &start); + if (data == NULL) { + break; /* out of for */ + } + + if (dhcpol_parse_buffer(&extra, data, len, err) == FALSE) { + goto failed; + } + + if (dhcpol_concat(vendor, &extra) == FALSE) { + if (err) + sprintf(err, "dhcpol_concat() failed at %d\n", start); + goto failed; + } + dhcpol_free(&extra); + ret = TRUE; + } + if (ret == FALSE) { + if (err) + strcpy(err, "missing vendor specific options"); + } + return (ret); + + failed: + dhcpol_free(vendor); + dhcpol_free(&extra); + return (FALSE); +} + +#ifdef TEST_DHCP_OPTIONS +char test_empty[] = { + 99, 130, 83, 99, + 255, +}; + +char test_simple[] = { + 99, 130, 83, 99, + 1, 4, 255, 255, 252, 0, + 3, 4, 17, 202, 40, 1, + 255, +}; + +char test_vendor[] = { + 99, 130, 83, 99, + 1, 4, 255, 255, 252, 0, + 3, 4, 17, 202, 40, 1, + 43, 6, 1, 4, 1, 2, 3, 4, + 43, 6, 1, 4, 1, 2, 3, 4, + 255, +}; + +char test_no_end[] = { + 0x63, 0x82, 0x53, 0x63, 0x35, 0x01, 0x05, 0x36, + 0x04, 0xc0, 0xa8, 0x01, 0x01, 0x33, 0x04, 0x80, + 0x00, 0x80, 0x00, 0x01, 0x04, 0xff, 0xff, 0xff, + 0x00, 0x03, 0x04, 0xc0, 0xa8, 0x01, 0x01, 0x06, + 0x0c, 0x18, 0x1a, 0xa3, 0x21, 0x18, 0x1a, 0xa3, + 0x20, 0x18, 0x5e, 0xa3, 0x21, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 +}; + +char test_too_short[] = { + 0x1 +}; +struct test { + char * name; + char * data; + int len; + boolean_t result; +}; + +struct test tests[] = { + { "empty", test_empty, sizeof(test_empty), TRUE }, + { "simple", test_simple, sizeof(test_simple), TRUE }, + { "vendor", test_vendor, sizeof(test_vendor), TRUE }, + { "no_end", test_no_end, sizeof(test_no_end), TRUE }, + { "too_short", test_too_short, sizeof(test_too_short), FALSE }, + { NULL, NULL, 0, FALSE }, +}; + + +static char buf[2048]; + +int +main() +{ + int i; + dhcpol_t options; + char error[256]; + struct dhcp * pkt = (struct dhcp *)buf; + + dhcpol_init(&options); + + for (i = 0; tests[i].name; i++) { + printf("\nTest %d: ", i); + bcopy(tests[i].data, pkt->dp_options, tests[i].len); + if (dhcpol_parse_packet(&options, pkt, + sizeof(*pkt) + tests[i].len, + error) != tests[i].result) { + printf("test '%s' FAILED\n", tests[i].name); + if (tests[i].result == TRUE) { + printf("error message returned was %s\n", error); + } + } + else { + printf("test '%s' PASSED\n", tests[i].name); + if (tests[i].result == FALSE) { + printf("error message returned was %s\n", error); + } + } + dhcpol_free(&options); + } + exit(0); +} +#endif TEST_DHCP_OPTIONS