1 /*
2 * CDDL HEADER START
3 *
4 * The contents of this file are subject to the terms of the
5 * Common Development and Distribution License (the "License").
6 * You may not use this file except in compliance with the License.
7 *
8 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
9 * or http://www.opensolaris.org/os/licensing.
10 * See the License for the specific language governing permissions
11 * and limitations under the License.
12 *
13 * When distributing Covered Code, include this CDDL HEADER in each
14 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
15 * If applicable, add the following below this CDDL HEADER, with the
16 * fields enclosed by brackets "[]" replaced with your own identifying
17 * information: Portions Copyright [yyyy] [name of copyright owner]
18 *
19 * CDDL HEADER END
20 */
21 /*
22 * Copyright 2007 Sun Microsystems, Inc. All rights reserved.
23 * Use is subject to license terms.
24 *
25 * Routines used to extract/insert DHCP options. Must be kept MT SAFE,
26 * as they are called from different threads.
27 */
28
29 #pragma ident "%Z%%M% %I% %E% SMI"
30
31 #include <sys/types.h>
32 #include "dhcp_impl.h"
33 #if defined(_KERNEL) && !defined(_BOOT)
34 #include <sys/sunddi.h>
35 #else
36 #include <strings.h>
37 #endif /* _KERNEL && !_BOOT */
38
39 static uint8_t bootmagic[] = BOOTMAGIC;
40
41 /*
42 * Scan field for options.
43 */
44 static void
field_scan(uint8_t * start,uint8_t * end,DHCP_OPT ** options,uint8_t last_option)45 field_scan(uint8_t *start, uint8_t *end, DHCP_OPT **options,
46 uint8_t last_option)
47 {
48 uint8_t *current;
49
50 while (start < end) {
51 if (*start == CD_PAD) {
52 start++;
53 continue;
54 }
55 if (*start == CD_END)
56 break; /* done */
57 if (*start > last_option) {
58 if (++start < end)
59 start += *start + 1;
60 continue; /* unrecognized option */
61 }
62
63 current = start;
64 if (++start < end)
65 start += *start + 1; /* advance to next option */
66
67 /* all options besides CD_END and CD_PAD should have a len */
68 if ((current + 1) >= end)
69 continue;
70
71 /* Ignores duplicate options. */
72 if (options[*current] == NULL) {
73
74 options[*current] = (DHCP_OPT *)current;
75
76 /* verify that len won't go beyond end */
77 if ((current + options[*current]->len + 1) >= end) {
78 options[*current] = NULL;
79 continue;
80 }
81 }
82 }
83 }
84
85 /*
86 * Scan Vendor field for options.
87 */
88 static void
vendor_scan(PKT_LIST * pl)89 vendor_scan(PKT_LIST *pl)
90 {
91 uint8_t *start, *end, len;
92
93 if (pl->opts[CD_VENDOR_SPEC] == NULL)
94 return;
95 len = pl->opts[CD_VENDOR_SPEC]->len;
96 start = pl->opts[CD_VENDOR_SPEC]->value;
97
98 /* verify that len won't go beyond the end of the packet */
99 if (((start - (uint8_t *)pl->pkt) + len) > pl->len)
100 return;
101
102 end = start + len;
103 field_scan(start, end, pl->vs, VS_OPTION_END);
104 }
105
106 /*
107 * Load opts table in PKT_LIST entry with PKT's options.
108 * Returns 0 if no fatal errors occur, otherwise...
109 */
110 int
dhcp_options_scan(PKT_LIST * pl,boolean_t scan_vendor)111 dhcp_options_scan(PKT_LIST *pl, boolean_t scan_vendor)
112 {
113 PKT *pkt = pl->pkt;
114 uint_t opt_size = pl->len - BASE_PKT_SIZE;
115
116 /*
117 * bcmp() is used here instead of memcmp() since kernel/standalone
118 * doesn't have a memcmp().
119 */
120 if (pl->len < BASE_PKT_SIZE ||
121 bcmp(pl->pkt->cookie, bootmagic, sizeof (pl->pkt->cookie)) != 0) {
122 pl->rfc1048 = 0;
123 return (0);
124 }
125
126 pl->rfc1048 = 1;
127
128 /* check the options field */
129 field_scan(pkt->options, &pkt->options[opt_size], pl->opts,
130 DHCP_LAST_OPT);
131
132 /*
133 * process vendor specific options. We look at the vendor options
134 * here, simply because a BOOTP server could fake DHCP vendor
135 * options. This increases our interoperability with BOOTP.
136 */
137 if (scan_vendor && (pl->opts[CD_VENDOR_SPEC] != NULL))
138 vendor_scan(pl);
139
140 if (pl->opts[CD_DHCP_TYPE] == NULL)
141 return (0);
142
143 if (pl->opts[CD_DHCP_TYPE]->len != 1)
144 return (DHCP_GARBLED_MSG_TYPE);
145
146 if (*pl->opts[CD_DHCP_TYPE]->value < DISCOVER ||
147 *pl->opts[CD_DHCP_TYPE]->value > INFORM)
148 return (DHCP_WRONG_MSG_TYPE);
149
150 if (pl->opts[CD_OPTION_OVERLOAD]) {
151 if (pl->opts[CD_OPTION_OVERLOAD]->len != 1) {
152 pl->opts[CD_OPTION_OVERLOAD] = NULL;
153 return (DHCP_BAD_OPT_OVLD);
154 }
155 switch (*pl->opts[CD_OPTION_OVERLOAD]->value) {
156 case 1:
157 field_scan(pkt->file, &pkt->cookie[0], pl->opts,
158 DHCP_LAST_OPT);
159 break;
160 case 2:
161 field_scan(pkt->sname, &pkt->file[0], pl->opts,
162 DHCP_LAST_OPT);
163 break;
164 case 3:
165 field_scan(pkt->file, &pkt->cookie[0], pl->opts,
166 DHCP_LAST_OPT);
167 field_scan(pkt->sname, &pkt->file[0], pl->opts,
168 DHCP_LAST_OPT);
169 break;
170 default:
171 pl->opts[CD_OPTION_OVERLOAD] = NULL;
172 return (DHCP_BAD_OPT_OVLD);
173 }
174 }
175 return (0);
176 }
177
178 /*
179 * Locate a DHCPv6 option or suboption within a buffer. DHCPv6 uses nested
180 * options within options, and this function is designed to work with both
181 * primary options and the suboptions contained within.
182 *
183 * The 'oldopt' is a previous option pointer, and is typically used to iterate
184 * over options of the same code number. The 'codenum' is in host byte order
185 * for simplicity. 'retlenp' may be NULL, and if present gets the _entire_
186 * option length (including header).
187 *
188 * Warning: the returned pointer has no particular alignment because DHCPv6
189 * defines options without alignment. The caller must deal with unaligned
190 * pointers carefully.
191 */
192 dhcpv6_option_t *
dhcpv6_find_option(const void * buffer,size_t buflen,const dhcpv6_option_t * oldopt,uint16_t codenum,uint_t * retlenp)193 dhcpv6_find_option(const void *buffer, size_t buflen,
194 const dhcpv6_option_t *oldopt, uint16_t codenum, uint_t *retlenp)
195 {
196 const uchar_t *bp;
197 dhcpv6_option_t d6o;
198 uint_t olen;
199
200 codenum = htons(codenum);
201 bp = buffer;
202 while (buflen >= sizeof (dhcpv6_option_t)) {
203 (void) memcpy(&d6o, bp, sizeof (d6o));
204 olen = ntohs(d6o.d6o_len) + sizeof (d6o);
205 if (olen > buflen)
206 break;
207 if (d6o.d6o_code != codenum ||
208 (oldopt != NULL && bp <= (const uchar_t *)oldopt)) {
209 bp += olen;
210 buflen -= olen;
211 continue;
212 }
213 if (retlenp != NULL)
214 *retlenp = olen;
215 /* LINTED: alignment */
216 return ((dhcpv6_option_t *)bp);
217 }
218 return (NULL);
219 }
220
221 /*
222 * Locate a DHCPv6 option within the top level of a PKT_LIST entry. DHCPv6
223 * uses nested options within options, and this function returns only the
224 * primary options. Use dhcpv6_find_option to traverse suboptions.
225 *
226 * See dhcpv6_find_option for usage details and warnings.
227 */
228 dhcpv6_option_t *
dhcpv6_pkt_option(const PKT_LIST * plp,const dhcpv6_option_t * oldopt,uint16_t codenum,uint_t * retlenp)229 dhcpv6_pkt_option(const PKT_LIST *plp, const dhcpv6_option_t *oldopt,
230 uint16_t codenum, uint_t *retlenp)
231 {
232 const dhcpv6_message_t *d6m;
233
234 if (plp == NULL || plp->pkt == NULL || plp->len < sizeof (*d6m))
235 return (NULL);
236 d6m = (const dhcpv6_message_t *)plp->pkt;
237 return (dhcpv6_find_option(d6m + 1, plp->len - sizeof (*d6m), oldopt,
238 codenum, retlenp));
239 }
240