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