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 #include <sys/types.h>
30 #include "dhcp_impl.h"
31 #if defined(_KERNEL) && !defined(_BOOT)
32 #include <sys/sunddi.h>
33 #else
34 #include <strings.h>
35 #endif /* _KERNEL && !_BOOT */
36
37 static uint8_t bootmagic[] = BOOTMAGIC;
38
39 /*
40 * Scan field for options.
41 */
42 static void
field_scan(uint8_t * start,uint8_t * end,DHCP_OPT ** options,uint8_t last_option)43 field_scan(uint8_t *start, uint8_t *end, DHCP_OPT **options,
44 uint8_t last_option)
45 {
46 uint8_t *current;
47
48 while (start < end) {
49 if (*start == CD_PAD) {
50 start++;
51 continue;
52 }
53 if (*start == CD_END)
54 break; /* done */
55 if (*start > last_option) {
56 if (++start < end)
57 start += *start + 1;
58 continue; /* unrecognized option */
59 }
60
61 current = start;
62 if (++start < end)
63 start += *start + 1; /* advance to next option */
64
65 /* all options besides CD_END and CD_PAD should have a len */
66 if ((current + 1) >= end)
67 continue;
68
69 /* Ignores duplicate options. */
70 if (options[*current] == NULL) {
71
72 options[*current] = (DHCP_OPT *)current;
73
74 /* verify that len won't go beyond end */
75 if ((current + options[*current]->len + 1) >= end) {
76 options[*current] = NULL;
77 continue;
78 }
79 }
80 }
81 }
82
83 /*
84 * Scan Vendor field for options.
85 */
86 static void
vendor_scan(PKT_LIST * pl)87 vendor_scan(PKT_LIST *pl)
88 {
89 uint8_t *start, *end, len;
90
91 if (pl->opts[CD_VENDOR_SPEC] == NULL)
92 return;
93 len = pl->opts[CD_VENDOR_SPEC]->len;
94 start = pl->opts[CD_VENDOR_SPEC]->value;
95
96 /* verify that len won't go beyond the end of the packet */
97 if (((start - (uint8_t *)pl->pkt) + len) > pl->len)
98 return;
99
100 end = start + len;
101 field_scan(start, end, pl->vs, VS_OPTION_END);
102 }
103
104 /*
105 * Load opts table in PKT_LIST entry with PKT's options.
106 * Returns 0 if no fatal errors occur, otherwise...
107 */
108 int
dhcp_options_scan(PKT_LIST * pl,boolean_t scan_vendor)109 dhcp_options_scan(PKT_LIST *pl, boolean_t scan_vendor)
110 {
111 PKT *pkt = pl->pkt;
112 uint_t opt_size = pl->len - BASE_PKT_SIZE;
113
114 /*
115 * bcmp() is used here instead of memcmp() since kernel/standalone
116 * doesn't have a memcmp().
117 */
118 if (pl->len < BASE_PKT_SIZE ||
119 bcmp(pl->pkt->cookie, bootmagic, sizeof (pl->pkt->cookie)) != 0) {
120 pl->rfc1048 = 0;
121 return (0);
122 }
123
124 pl->rfc1048 = 1;
125
126 /* check the options field */
127 field_scan(pkt->options, &pkt->options[opt_size], pl->opts,
128 DHCP_LAST_OPT);
129
130 /*
131 * process vendor specific options. We look at the vendor options
132 * here, simply because a BOOTP server could fake DHCP vendor
133 * options. This increases our interoperability with BOOTP.
134 */
135 if (scan_vendor && (pl->opts[CD_VENDOR_SPEC] != NULL))
136 vendor_scan(pl);
137
138 if (pl->opts[CD_DHCP_TYPE] == NULL)
139 return (0);
140
141 if (pl->opts[CD_DHCP_TYPE]->len != 1)
142 return (DHCP_GARBLED_MSG_TYPE);
143
144 if (*pl->opts[CD_DHCP_TYPE]->value < DISCOVER ||
145 *pl->opts[CD_DHCP_TYPE]->value > INFORM)
146 return (DHCP_WRONG_MSG_TYPE);
147
148 if (pl->opts[CD_OPTION_OVERLOAD]) {
149 if (pl->opts[CD_OPTION_OVERLOAD]->len != 1) {
150 pl->opts[CD_OPTION_OVERLOAD] = NULL;
151 return (DHCP_BAD_OPT_OVLD);
152 }
153 switch (*pl->opts[CD_OPTION_OVERLOAD]->value) {
154 case 1:
155 field_scan(pkt->file, &pkt->cookie[0], pl->opts,
156 DHCP_LAST_OPT);
157 break;
158 case 2:
159 field_scan(pkt->sname, &pkt->file[0], pl->opts,
160 DHCP_LAST_OPT);
161 break;
162 case 3:
163 field_scan(pkt->file, &pkt->cookie[0], pl->opts,
164 DHCP_LAST_OPT);
165 field_scan(pkt->sname, &pkt->file[0], pl->opts,
166 DHCP_LAST_OPT);
167 break;
168 default:
169 pl->opts[CD_OPTION_OVERLOAD] = NULL;
170 return (DHCP_BAD_OPT_OVLD);
171 }
172 }
173 return (0);
174 }
175
176 /*
177 * Locate a DHCPv6 option or suboption within a buffer. DHCPv6 uses nested
178 * options within options, and this function is designed to work with both
179 * primary options and the suboptions contained within.
180 *
181 * The 'oldopt' is a previous option pointer, and is typically used to iterate
182 * over options of the same code number. The 'codenum' is in host byte order
183 * for simplicity. 'retlenp' may be NULL, and if present gets the _entire_
184 * option length (including header).
185 *
186 * Warning: the returned pointer has no particular alignment because DHCPv6
187 * defines options without alignment. The caller must deal with unaligned
188 * pointers carefully.
189 */
190 dhcpv6_option_t *
dhcpv6_find_option(const void * buffer,size_t buflen,const dhcpv6_option_t * oldopt,uint16_t codenum,uint_t * retlenp)191 dhcpv6_find_option(const void *buffer, size_t buflen,
192 const dhcpv6_option_t *oldopt, uint16_t codenum, uint_t *retlenp)
193 {
194 const uchar_t *bp;
195 dhcpv6_option_t d6o;
196 uint_t olen;
197
198 codenum = htons(codenum);
199 bp = buffer;
200 while (buflen >= sizeof (dhcpv6_option_t)) {
201 (void) memcpy(&d6o, bp, sizeof (d6o));
202 olen = ntohs(d6o.d6o_len) + sizeof (d6o);
203 if (olen > buflen)
204 break;
205 if (d6o.d6o_code != codenum ||
206 (oldopt != NULL && bp <= (const uchar_t *)oldopt)) {
207 bp += olen;
208 buflen -= olen;
209 continue;
210 }
211 if (retlenp != NULL)
212 *retlenp = olen;
213 /* LINTED: alignment */
214 return ((dhcpv6_option_t *)bp);
215 }
216 return (NULL);
217 }
218
219 /*
220 * Locate a DHCPv6 option within the top level of a PKT_LIST entry. DHCPv6
221 * uses nested options within options, and this function returns only the
222 * primary options. Use dhcpv6_find_option to traverse suboptions.
223 *
224 * See dhcpv6_find_option for usage details and warnings.
225 */
226 dhcpv6_option_t *
dhcpv6_pkt_option(const PKT_LIST * plp,const dhcpv6_option_t * oldopt,uint16_t codenum,uint_t * retlenp)227 dhcpv6_pkt_option(const PKT_LIST *plp, const dhcpv6_option_t *oldopt,
228 uint16_t codenum, uint_t *retlenp)
229 {
230 const dhcpv6_message_t *d6m;
231
232 if (plp == NULL || plp->pkt == NULL || plp->len < sizeof (*d6m))
233 return (NULL);
234 d6m = (const dhcpv6_message_t *)plp->pkt;
235 return (dhcpv6_find_option(d6m + 1, plp->len - sizeof (*d6m), oldopt,
236 codenum, retlenp));
237 }
238