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		FIX ME: test data NULL
353	    bcopy(option + DHCP_OPTION_OFFSET, data + data_len, len);
354	    data_len += len;
355	}
356    }
357    *len_p = data_len;
358    return (data);
359}
360#endif
361
362/*
363 * Function: dhcpol_parse_packet
364 *
365 * Purpose:
366 *    Parse the option areas in the DHCP packet.
367 *    Verifies that the packet has the right magic number,
368 *    then parses and accumulates the option areas.
369 *    First the pkt->dp_options is parsed.  If that contains
370 *    the overload option, it parses pkt->dp_file if specified,
371 *    then parses pkt->dp_sname if specified.
372 */
373boolean_t
374dhcpol_parse_packet(dhcpol_t * options, const struct dhcp * pkt, int len)
375{
376    char		rfc_magic[4] = RFC_OPTIONS_MAGIC;
377
378    dhcpol_init(options);	/* make sure it's empty */
379
380    if (len < (sizeof(*pkt) + RFC_MAGIC_SIZE)) {
381	dprintf(("dhcp_options: packet is too short: %d < %d\n",
382		 len, (int)sizeof(*pkt) + RFC_MAGIC_SIZE));
383	return (FALSE);
384    }
385    if (bcmp(pkt->dp_options, rfc_magic, RFC_MAGIC_SIZE)) {
386	dprintf(("dhcp_options: missing magic number\n"));
387	return (FALSE);
388    }
389    if (dhcpol_parse_buffer(options, pkt->dp_options + RFC_MAGIC_SIZE,
390			    len - sizeof(*pkt) - RFC_MAGIC_SIZE) == FALSE)
391	return (FALSE);
392    { /* get overloaded options */
393	const uint8_t *	overload;
394	int		overload_len;
395
396	overload = dhcpol_find(options, dhcptag_option_overload_e,
397			       &overload_len, NULL);
398	if (overload && overload_len == 1) { /* has overloaded options */
399	    dhcpol_t	extra;
400
401	    dhcpol_init(&extra);
402	    if (*overload == DHCP_OVERLOAD_FILE
403		|| *overload == DHCP_OVERLOAD_BOTH) {
404		if (dhcpol_parse_buffer(&extra, pkt->dp_file,
405					sizeof(pkt->dp_file))) {
406		    dhcpol_concat(options, &extra);
407		    dhcpol_free(&extra);
408		}
409	    }
410	    if (*overload == DHCP_OVERLOAD_SNAME
411		|| *overload == DHCP_OVERLOAD_BOTH) {
412		if (dhcpol_parse_buffer(&extra, pkt->dp_sname,
413					sizeof(pkt->dp_sname))) {
414		    dhcpol_concat(options, &extra);
415		    dhcpol_free(&extra);
416		}
417	    }
418	}
419    }
420    return (TRUE);
421}
422
423/*
424 * Module: dhcpoa
425 *
426 * Purpose:
427 *   Types and functions to create new dhcp option areas.
428 */
429
430/*
431 * Function: dhcpoa_{init_common, init_no_end, init}
432 *
433 * Purpose:
434 *   Initialize an option area structure so that it can be used
435 *   in calling the dhcpoa_* routines.
436 */
437static void
438dhcpoa_init_common(dhcpoa_t * oa_p, void * buffer, int size, int reserve)
439{
440    bzero(oa_p, sizeof(*oa_p));
441    oa_p->oa_buffer = buffer;
442    oa_p->oa_size = size;
443    oa_p->oa_reserve = reserve;
444}
445
446void
447dhcpoa_init_no_end(dhcpoa_t * oa_p, void * buffer, int size)
448{
449    dhcpoa_init_common(oa_p, buffer, size, 0);
450    return;
451}
452
453int
454dhcpoa_size(dhcpoa_t * oa_p)
455{
456    return (oa_p->oa_size);
457}
458
459void
460dhcpoa_init(dhcpoa_t * oa_p, void * buffer, int size)
461{
462    /* initialize the area, reserve space for the end tag */
463    dhcpoa_init_common(oa_p, buffer, size, 1);
464    return;
465}
466/*
467 * Function: dhcpoa_add
468 *
469 * Purpose:
470 *   Add an option to the option area.
471 */
472dhcpoa_ret_t
473dhcpoa_add(dhcpoa_t * oa_p, dhcptag_t tag, int len, const void * option)
474{
475    if (len > DHCP_OPTION_SIZE_MAX) {
476	dprintf(("tag %d option %d > %d\n", tag, len, DHCP_OPTION_SIZE_MAX));
477	return (dhcpoa_failed_e);
478    }
479
480    if (oa_p->oa_end_tag) {
481	dprintf(("attempt to add data after end tag\n"));
482	return (dhcpoa_failed_e);
483    }
484
485    switch (tag) {
486      case dhcptag_end_e:
487	if ((oa_p->oa_offset + 1) > oa_p->oa_size) {
488	    /* this can't happen since we're careful to leave space */
489	    dprintf(("can't add end tag %d > %d\n",
490		     oa_p->oa_offset + oa_p->oa_reserve, oa_p->oa_size));
491	    return (dhcpoa_failed_e);
492	}
493	((uint8_t *)oa_p->oa_buffer)[oa_p->oa_offset + DHCP_TAG_OFFSET] = tag;
494	oa_p->oa_offset++;
495	oa_p->oa_end_tag = 1;
496	break;
497
498      case dhcptag_pad_e:
499	/* 1 for pad tag */
500	if ((oa_p->oa_offset + oa_p->oa_reserve + 1) > oa_p->oa_size) {
501	    dprintf(("can't add pad tag %d > %d\n",
502		     oa_p->oa_offset + oa_p->oa_reserve + 1, oa_p->oa_size));
503	    return (dhcpoa_full_e);
504	}
505	((uint8_t *)oa_p->oa_buffer)[oa_p->oa_offset + DHCP_TAG_OFFSET] = tag;
506	oa_p->oa_offset++;
507	break;
508
509      default:
510	/* 2 for tag/len */
511	if ((oa_p->oa_offset + len + 2 + oa_p->oa_reserve) > oa_p->oa_size) {
512	    dprintf(("can't add tag %d (%d > %d)\n", tag,
513		     oa_p->oa_offset + len + 2 + oa_p->oa_reserve,
514		     oa_p->oa_size));
515	    return (dhcpoa_full_e);
516	}
517	((uint8_t *)oa_p->oa_buffer)[oa_p->oa_offset + DHCP_TAG_OFFSET] = tag;
518	((uint8_t *)oa_p->oa_buffer)[oa_p->oa_offset + DHCP_LEN_OFFSET] = (uint8_t)len;
519	if (len) {
520	    memcpy(oa_p->oa_buffer + (DHCP_OPTION_OFFSET + oa_p->oa_offset),
521		   option, len);
522	}
523	oa_p->oa_offset += len + DHCP_OPTION_OFFSET;
524	break;
525    }
526    oa_p->oa_option_count++;
527    return (dhcpoa_success_e);
528}
529
530/*
531 * Function: dhcpoa_add_dhcpmsg
532 *
533 * Purpose:
534 *   Add a dhcp message option to the option area.
535 */
536dhcpoa_ret_t
537dhcpoa_add_dhcpmsg(dhcpoa_t * oa_p, dhcp_msgtype_t msgtype)
538{
539    return (dhcpoa_add(oa_p, dhcptag_dhcp_message_type_e,
540		       sizeof(msgtype), &msgtype));
541}
542
543int
544dhcpoa_used(dhcpoa_t * oa_p)
545{
546    return (oa_p->oa_offset);
547}
548
549int
550dhcpoa_freespace(dhcpoa_t * oa_p)
551{
552    int	freespace;
553
554    freespace = oa_p->oa_size - oa_p->oa_offset - oa_p->oa_reserve;
555    if (freespace < 0) {
556	freespace = 0;
557    }
558    return (freespace);
559}
560
561int
562dhcpoa_count(dhcpoa_t * oa_p)
563{
564    return (oa_p->oa_option_count);
565}
566
567void *
568dhcpoa_buffer(dhcpoa_t * oa_p)
569{
570    return (oa_p->oa_buffer);
571}
572
573
574#ifdef TEST_DHCP_OPTIONS
575char test_empty[] = {
576    99, 130, 83, 99,
577    255,
578};
579
580char test_simple[] = {
581    99, 130, 83, 99,
582    1, 4, 255, 255, 252, 0,
583    3, 4, 17, 202, 40, 1,
584    255,
585};
586
587char test_vendor[] = {
588    99, 130, 83, 99,
589    1, 4, 255, 255, 252, 0,
590    3, 4, 17, 202, 40, 1,
591    43, 6, 1, 4, 1, 2, 3, 4,
592    43, 6, 1, 4, 1, 2, 3, 4,
593    255,
594};
595
596char test_no_end[] = {
597    0x63, 0x82, 0x53, 0x63, 0x35, 0x01, 0x05, 0x36,
598    0x04, 0xc0, 0xa8, 0x01, 0x01, 0x33, 0x04, 0x80,
599    0x00, 0x80, 0x00, 0x01, 0x04, 0xff, 0xff, 0xff,
600    0x00, 0x03, 0x04, 0xc0, 0xa8, 0x01, 0x01, 0x06,
601    0x0c, 0x18, 0x1a, 0xa3, 0x21, 0x18, 0x1a, 0xa3,
602    0x20, 0x18, 0x5e, 0xa3, 0x21, 0x00, 0x00, 0x00,
603    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
604    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
605};
606
607char test_too_short[] = {
608    0x1
609};
610struct test {
611    char * 		name;
612    char *		data;
613    int			len;
614    boolean_t		result;
615};
616
617struct test tests[] = {
618    { "empty", test_empty, sizeof(test_empty), TRUE },
619    { "simple", test_simple, sizeof(test_simple), TRUE },
620    { "vendor", test_vendor, sizeof(test_vendor), TRUE },
621    { "no_end", test_no_end, sizeof(test_no_end), TRUE },
622    { "too_short", test_too_short, sizeof(test_too_short), FALSE },
623    { NULL, NULL, 0, FALSE },
624};
625
626
627static char buf[2048];
628
629int
630main()
631{
632    int 	i;
633    dhcpol_t 	options;
634    struct dhcp * pkt = (struct dhcp *)buf;
635
636    dhcpol_init(&options);
637
638    for (i = 0; tests[i].name; i++) {
639	printf("\nTest %d: ", i);
640	bcopy(tests[i].data, pkt->dp_options, tests[i].len);
641	if (dhcpol_parse_packet(&options, pkt,
642				sizeof(*pkt) + tests[i].len)
643	    != tests[i].result) {
644	    printf("test '%s' FAILED\n", tests[i].name);
645	}
646	else {
647	    printf("test '%s' PASSED\n", tests[i].name);
648	}
649	dhcpol_free(&options);
650    }
651    exit(0);
652}
653#endif /* TEST_DHCP_OPTIONS */
654