Commit | Line | Data |
---|---|---|
dafcba85 | 1 | /* $OpenBSD: src/sbin/dhclient/options.c,v 1.42 2012/10/27 23:08:53 krw Exp $ */ |
846204b6 HT |
2 | |
3 | /* DHCP options parsing and reassembly. */ | |
4 | ||
5 | /* | |
6 | * Copyright (c) 1995, 1996, 1997, 1998 The Internet Software Consortium. | |
7 | * All rights reserved. | |
8 | * | |
9 | * Redistribution and use in source and binary forms, with or without | |
10 | * modification, are permitted provided that the following conditions | |
11 | * are met: | |
12 | * | |
13 | * 1. Redistributions of source code must retain the above copyright | |
14 | * notice, this list of conditions and the following disclaimer. | |
15 | * 2. Redistributions in binary form must reproduce the above copyright | |
16 | * notice, this list of conditions and the following disclaimer in the | |
17 | * documentation and/or other materials provided with the distribution. | |
18 | * 3. Neither the name of The Internet Software Consortium nor the names | |
19 | * of its contributors may be used to endorse or promote products derived | |
20 | * from this software without specific prior written permission. | |
21 | * | |
22 | * THIS SOFTWARE IS PROVIDED BY THE INTERNET SOFTWARE CONSORTIUM AND | |
23 | * CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, | |
24 | * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF | |
25 | * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE | |
26 | * DISCLAIMED. IN NO EVENT SHALL THE INTERNET SOFTWARE CONSORTIUM OR | |
27 | * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, | |
28 | * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT | |
29 | * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF | |
30 | * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND | |
31 | * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, | |
32 | * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT | |
33 | * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF | |
34 | * SUCH DAMAGE. | |
35 | * | |
36 | * This software has been written for the Internet Software Consortium | |
37 | * by Ted Lemon <mellon@fugue.com> in cooperation with Vixie | |
38 | * Enterprises. To learn more about the Internet Software Consortium, | |
39 | * see ``http://www.vix.com/isc''. To learn more about Vixie | |
40 | * Enterprises, see ``http://www.vix.com''. | |
41 | */ | |
42 | ||
43 | #include <ctype.h> | |
44 | ||
45 | #include "dhcpd.h" | |
46 | ||
47 | int parse_option_buffer(struct option_data *, unsigned char *, int); | |
48 | ||
49 | /* | |
50 | * Parse options out of the specified buffer, storing addresses of | |
51 | * option values in options and setting client->options_valid if | |
52 | * no errors are encountered. | |
53 | */ | |
54 | int | |
55 | parse_option_buffer(struct option_data *options, unsigned char *buffer, | |
56 | int length) | |
57 | { | |
58 | unsigned char *s, *t, *end = buffer + length; | |
59 | int len, code; | |
60 | ||
61 | for (s = buffer; *s != DHO_END && s < end; ) { | |
62 | code = s[0]; | |
63 | ||
64 | /* Pad options don't have a length - just skip them. */ | |
65 | if (code == DHO_PAD) { | |
66 | s++; | |
67 | continue; | |
68 | } | |
69 | ||
70 | /* | |
685fcbc8 AHJ |
71 | * All options other than DHO_PAD and DHO_END have a one-byte |
72 | * length field. It could be 0! Make sure that the length byte | |
73 | * is present, and all the data is available. | |
846204b6 | 74 | */ |
685fcbc8 | 75 | if (s + 1 < end) { |
846204b6 | 76 | len = s[1]; |
685fcbc8 AHJ |
77 | if (s + 1 + len < end) { |
78 | ; /* option data is all there. */ | |
79 | } else { | |
80 | warning("option %s (%d) larger than buffer.", | |
81 | dhcp_options[code].name, len); | |
82 | warning("rejecting bogus offer."); | |
83 | return (0); | |
84 | } | |
85 | } else { | |
86 | warning("option %s has no length field.", | |
87 | dhcp_options[code].name); | |
846204b6 HT |
88 | warning("rejecting bogus offer."); |
89 | return (0); | |
90 | } | |
f568cd1e AHJ |
91 | |
92 | /* | |
93 | * Strip trailing NULs from ascii ('t') options. They | |
94 | * will be treated as DHO_PAD options. i.e. ignored. RFC 2132 | |
95 | * says "Options containing NVT ASCII data SHOULD NOT include | |
96 | * a trailing NULL; however, the receiver of such options | |
97 | * MUST be prepared to delete trailing nulls if they exist." | |
98 | */ | |
99 | if (dhcp_options[code].format[0] == 't') { | |
685fcbc8 AHJ |
100 | while (len > 0 && s[len + 1] == '\0') |
101 | len--; | |
f568cd1e AHJ |
102 | } |
103 | ||
846204b6 HT |
104 | /* |
105 | * If we haven't seen this option before, just make | |
106 | * space for it and copy it there. | |
107 | */ | |
108 | if (!options[code].data) { | |
109 | if (!(t = calloc(1, len + 1))) | |
110 | error("Can't allocate storage for option %s.", | |
111 | dhcp_options[code].name); | |
112 | /* | |
113 | * Copy and NUL-terminate the option (in case | |
114 | * it's an ASCII string). | |
115 | */ | |
116 | memcpy(t, &s[2], len); | |
117 | t[len] = 0; | |
118 | options[code].len = len; | |
119 | options[code].data = t; | |
120 | } else { | |
121 | /* | |
122 | * If it's a repeat, concatenate it to whatever | |
123 | * we last saw. This is really only required | |
124 | * for clients, but what the heck... | |
125 | */ | |
126 | t = calloc(1, len + options[code].len + 1); | |
127 | if (!t) | |
128 | error("Can't expand storage for option %s.", | |
129 | dhcp_options[code].name); | |
130 | memcpy(t, options[code].data, options[code].len); | |
131 | memcpy(t + options[code].len, &s[2], len); | |
132 | options[code].len += len; | |
133 | t[options[code].len] = 0; | |
134 | free(options[code].data); | |
135 | options[code].data = t; | |
136 | } | |
137 | s += len + 2; | |
138 | } | |
139 | ||
140 | return (1); | |
141 | } | |
142 | ||
143 | /* | |
144 | * Copy as many options as fit in buflen bytes of buf. Return the | |
145 | * offset of the start of the last option copied. A caller can check | |
146 | * to see if it's DHO_END to decide if all the options were copied. | |
147 | */ | |
148 | int | |
741bbb9f | 149 | cons_options(struct option_data *options) |
846204b6 | 150 | { |
741bbb9f AHJ |
151 | unsigned char *buf = client->packet.options; |
152 | int buflen = 576 - DHCP_FIXED_LEN; | |
846204b6 HT |
153 | int ix, incr, length, bufix, code, lastopt = -1; |
154 | ||
155 | bzero(buf, buflen); | |
156 | ||
741bbb9f AHJ |
157 | memcpy(buf, DHCP_OPTIONS_COOKIE, 4); |
158 | if (options[DHO_DHCP_MESSAGE_TYPE].data) { | |
159 | memcpy(&buf[4], DHCP_OPTIONS_MESSAGE_TYPE, 3); | |
160 | buf[6] = options[DHO_DHCP_MESSAGE_TYPE].data[0]; | |
161 | bufix = 7; | |
162 | } else | |
163 | bufix = 4; | |
846204b6 HT |
164 | |
165 | for (code = DHO_SUBNET_MASK; code < DHO_END; code++) { | |
741bbb9f | 166 | if (!options[code].data || code == DHO_DHCP_MESSAGE_TYPE) |
846204b6 HT |
167 | continue; |
168 | ||
169 | length = options[code].len; | |
170 | if (bufix + length + 2*((length+254)/255) >= buflen) | |
171 | return (lastopt); | |
172 | ||
173 | lastopt = bufix; | |
174 | ix = 0; | |
175 | ||
176 | while (length) { | |
177 | incr = length > 255 ? 255 : length; | |
178 | ||
179 | buf[bufix++] = code; | |
180 | buf[bufix++] = incr; | |
181 | memcpy(buf + bufix, options[code].data + ix, incr); | |
182 | ||
183 | length -= incr; | |
184 | ix += incr; | |
185 | bufix += incr; | |
186 | } | |
187 | } | |
188 | ||
189 | if (bufix < buflen) { | |
190 | buf[bufix] = DHO_END; | |
191 | lastopt = bufix; | |
192 | } | |
193 | ||
194 | return (lastopt); | |
195 | } | |
196 | ||
197 | /* | |
198 | * Format the specified option so that a human can easily read it. | |
199 | */ | |
200 | char * | |
dafcba85 AHJ |
201 | pretty_print_option(unsigned int code, struct option_data *option, |
202 | int emit_punct) | |
846204b6 HT |
203 | { |
204 | static char optbuf[32768]; /* XXX */ | |
205 | int hunksize = 0, numhunk = -1, numelem = 0; | |
206 | char fmtbuf[32], *op = optbuf; | |
207 | int i, j, k, opleft = sizeof(optbuf); | |
dafcba85 | 208 | unsigned char *data = option->data; |
846204b6 | 209 | unsigned char *dp = data; |
dafcba85 | 210 | int len = option->len; |
846204b6 HT |
211 | struct in_addr foo; |
212 | char comma; | |
213 | ||
214 | /* Code should be between 0 and 255. */ | |
215 | if (code > 255) | |
216 | error("pretty_print_option: bad code %d", code); | |
217 | ||
dafcba85 | 218 | if (emit_punct) |
846204b6 HT |
219 | comma = ','; |
220 | else | |
221 | comma = ' '; | |
222 | ||
223 | /* Figure out the size of the data. */ | |
224 | for (i = 0; dhcp_options[code].format[i]; i++) { | |
225 | if (!numhunk) { | |
226 | warning("%s: Excess information in format string: %s", | |
227 | dhcp_options[code].name, | |
228 | &(dhcp_options[code].format[i])); | |
229 | break; | |
230 | } | |
231 | numelem++; | |
232 | fmtbuf[i] = dhcp_options[code].format[i]; | |
233 | switch (dhcp_options[code].format[i]) { | |
234 | case 'A': | |
235 | --numelem; | |
236 | fmtbuf[i] = 0; | |
237 | numhunk = 0; | |
1c6d9dd3 AHJ |
238 | if (hunksize == 0) { |
239 | warning("%s: no size indicator before A" | |
240 | " in format string: %s", | |
241 | dhcp_options[code].name, | |
242 | dhcp_options[code].format); | |
243 | return ("<fmt error>"); | |
244 | } | |
846204b6 HT |
245 | break; |
246 | case 'X': | |
247 | for (k = 0; k < len; k++) | |
248 | if (!isascii(data[k]) || | |
249 | !isprint(data[k])) | |
250 | break; | |
251 | if (k == len) { | |
252 | fmtbuf[i] = 't'; | |
253 | numhunk = -2; | |
254 | } else { | |
255 | fmtbuf[i] = 'x'; | |
256 | hunksize++; | |
257 | comma = ':'; | |
258 | numhunk = 0; | |
259 | } | |
260 | fmtbuf[i + 1] = 0; | |
261 | break; | |
262 | case 't': | |
263 | fmtbuf[i] = 't'; | |
264 | fmtbuf[i + 1] = 0; | |
265 | numhunk = -2; | |
266 | break; | |
267 | case 'I': | |
268 | case 'l': | |
269 | case 'L': | |
270 | hunksize += 4; | |
271 | break; | |
272 | case 's': | |
273 | case 'S': | |
274 | hunksize += 2; | |
275 | break; | |
276 | case 'b': | |
277 | case 'B': | |
278 | case 'f': | |
279 | hunksize++; | |
280 | break; | |
281 | case 'e': | |
282 | break; | |
283 | default: | |
284 | warning("%s: garbage in format string: %s", | |
285 | dhcp_options[code].name, | |
286 | &(dhcp_options[code].format[i])); | |
287 | break; | |
288 | } | |
289 | } | |
290 | ||
291 | /* Check for too few bytes... */ | |
292 | if (hunksize > len) { | |
293 | warning("%s: expecting at least %d bytes; got %d", | |
294 | dhcp_options[code].name, hunksize, len); | |
295 | return ("<error>"); | |
296 | } | |
297 | /* Check for too many bytes... */ | |
298 | if (numhunk == -1 && hunksize < len) | |
299 | warning("%s: %d extra bytes", | |
300 | dhcp_options[code].name, len - hunksize); | |
301 | ||
302 | /* If this is an array, compute its size. */ | |
303 | if (!numhunk) | |
304 | numhunk = len / hunksize; | |
305 | /* See if we got an exact number of hunks. */ | |
306 | if (numhunk > 0 && numhunk * hunksize < len) | |
307 | warning("%s: %d extra bytes at end of array", | |
308 | dhcp_options[code].name, len - numhunk * hunksize); | |
309 | ||
310 | /* A one-hunk array prints the same as a single hunk. */ | |
311 | if (numhunk < 0) | |
312 | numhunk = 1; | |
313 | ||
314 | /* Cycle through the array (or hunk) printing the data. */ | |
315 | for (i = 0; i < numhunk; i++) { | |
316 | for (j = 0; j < numelem; j++) { | |
317 | int opcount; | |
318 | size_t oplen; | |
319 | switch (fmtbuf[j]) { | |
320 | case 't': | |
dafcba85 | 321 | if (emit_punct) { |
846204b6 HT |
322 | *op++ = '"'; |
323 | opleft--; | |
324 | } | |
325 | for (; dp < data + len; dp++) { | |
326 | if (!isascii(*dp) || | |
327 | !isprint(*dp)) { | |
328 | if (dp + 1 != data + len || | |
329 | *dp != 0) { | |
330 | size_t oplen; | |
331 | snprintf(op, opleft, | |
332 | "\\%03o", *dp); | |
333 | oplen = strlen(op); | |
334 | op += oplen; | |
335 | opleft -= oplen; | |
336 | } | |
337 | } else if (*dp == '"' || | |
338 | *dp == '\'' || | |
339 | *dp == '$' || | |
340 | *dp == '`' || | |
341 | *dp == '\\') { | |
342 | *op++ = '\\'; | |
343 | *op++ = *dp; | |
344 | opleft -= 2; | |
345 | } else { | |
346 | *op++ = *dp; | |
347 | opleft--; | |
348 | } | |
349 | } | |
dafcba85 | 350 | if (emit_punct) { |
846204b6 HT |
351 | *op++ = '"'; |
352 | opleft--; | |
353 | } | |
354 | ||
355 | *op = 0; | |
356 | break; | |
357 | case 'I': | |
358 | foo.s_addr = htonl(getULong(dp)); | |
359 | opcount = strlcpy(op, inet_ntoa(foo), opleft); | |
360 | if (opcount >= opleft) | |
361 | goto toobig; | |
362 | opleft -= opcount; | |
363 | dp += 4; | |
364 | break; | |
365 | case 'l': | |
366 | opcount = snprintf(op, opleft, "%ld", | |
367 | (long)getLong(dp)); | |
368 | if (opcount >= opleft || opcount == -1) | |
369 | goto toobig; | |
370 | opleft -= opcount; | |
371 | dp += 4; | |
372 | break; | |
373 | case 'L': | |
374 | opcount = snprintf(op, opleft, "%ld", | |
375 | (unsigned long)getULong(dp)); | |
376 | if (opcount >= opleft || opcount == -1) | |
377 | goto toobig; | |
378 | opleft -= opcount; | |
379 | dp += 4; | |
380 | break; | |
381 | case 's': | |
382 | opcount = snprintf(op, opleft, "%d", | |
383 | getShort(dp)); | |
384 | if (opcount >= opleft || opcount == -1) | |
385 | goto toobig; | |
386 | opleft -= opcount; | |
387 | dp += 2; | |
388 | break; | |
389 | case 'S': | |
390 | opcount = snprintf(op, opleft, "%d", | |
391 | getUShort(dp)); | |
392 | if (opcount >= opleft || opcount == -1) | |
393 | goto toobig; | |
394 | opleft -= opcount; | |
395 | dp += 2; | |
396 | break; | |
397 | case 'b': | |
398 | opcount = snprintf(op, opleft, "%d", | |
399 | *(char *)dp++); | |
400 | if (opcount >= opleft || opcount == -1) | |
401 | goto toobig; | |
402 | opleft -= opcount; | |
403 | break; | |
404 | case 'B': | |
405 | opcount = snprintf(op, opleft, "%d", *dp++); | |
406 | if (opcount >= opleft || opcount == -1) | |
407 | goto toobig; | |
408 | opleft -= opcount; | |
409 | break; | |
410 | case 'x': | |
411 | opcount = snprintf(op, opleft, "%x", *dp++); | |
412 | if (opcount >= opleft || opcount == -1) | |
413 | goto toobig; | |
414 | opleft -= opcount; | |
415 | break; | |
416 | case 'f': | |
417 | opcount = strlcpy(op, | |
418 | *dp++ ? "true" : "false", opleft); | |
419 | if (opcount >= opleft) | |
420 | goto toobig; | |
421 | opleft -= opcount; | |
422 | break; | |
423 | default: | |
424 | warning("Unexpected format code %c", fmtbuf[j]); | |
425 | } | |
426 | oplen = strlen(op); | |
427 | op += oplen; | |
428 | opleft -= oplen; | |
429 | if (opleft < 1) | |
430 | goto toobig; | |
431 | if (j + 1 < numelem && comma != ':') { | |
432 | *op++ = ' '; | |
433 | opleft--; | |
434 | } | |
435 | } | |
436 | if (i + 1 < numhunk) { | |
437 | *op++ = comma; | |
438 | opleft--; | |
439 | } | |
440 | if (opleft < 1) | |
441 | goto toobig; | |
442 | ||
443 | } | |
444 | return (optbuf); | |
445 | toobig: | |
446 | warning("dhcp option too large"); | |
447 | return ("<error>"); | |
448 | } | |
449 | ||
450 | void | |
451 | do_packet(int len, unsigned int from_port, struct iaddr from, | |
452 | struct hardware *hfrom) | |
453 | { | |
454 | struct dhcp_packet *packet = &client->packet; | |
455 | struct option_data options[256]; | |
456 | struct iaddrlist *ap; | |
457 | void (*handler)(struct iaddr, struct option_data *); | |
458 | char *type; | |
459 | int i, options_valid = 1; | |
460 | ||
461 | if (packet->hlen > sizeof(packet->chaddr)) { | |
462 | note("Discarding packet with invalid hlen."); | |
463 | return; | |
464 | } | |
465 | ||
466 | /* | |
467 | * Silently drop the packet if the client hardware address in the | |
468 | * packet is not the hardware address of the interface being managed. | |
469 | */ | |
470 | if ((ifi->hw_address.hlen != packet->hlen) || | |
471 | (memcmp(ifi->hw_address.haddr, packet->chaddr, packet->hlen))) | |
472 | return; | |
473 | ||
474 | memset(options, 0, sizeof(options)); | |
475 | ||
476 | if (memcmp(&packet->options, DHCP_OPTIONS_COOKIE, 4) == 0) { | |
477 | /* Parse the BOOTP/DHCP options field. */ | |
478 | options_valid = parse_option_buffer(options, | |
479 | &packet->options[4], sizeof(packet->options) - 4); | |
480 | ||
481 | /* Only DHCP packets have overload areas for options. */ | |
482 | if (options_valid && | |
483 | options[DHO_DHCP_MESSAGE_TYPE].data && | |
484 | options[DHO_DHCP_OPTION_OVERLOAD].data) { | |
485 | if (options[DHO_DHCP_OPTION_OVERLOAD].data[0] & 1) | |
486 | options_valid = parse_option_buffer(options, | |
487 | (unsigned char *)packet->file, | |
488 | sizeof(packet->file)); | |
489 | if (options_valid && | |
490 | options[DHO_DHCP_OPTION_OVERLOAD].data[0] & 2) | |
491 | options_valid = parse_option_buffer(options, | |
492 | (unsigned char *)packet->sname, | |
493 | sizeof(packet->sname)); | |
494 | } | |
495 | } | |
496 | ||
497 | type = ""; | |
498 | handler = NULL; | |
499 | ||
500 | if (options[DHO_DHCP_MESSAGE_TYPE].data) { | |
501 | /* Always try a DHCP packet, even if a bad option was seen. */ | |
502 | switch (options[DHO_DHCP_MESSAGE_TYPE].data[0]) { | |
503 | case DHCPOFFER: | |
504 | handler = dhcpoffer; | |
505 | type = "DHCPOFFER"; | |
506 | break; | |
507 | case DHCPNAK: | |
508 | handler = dhcpnak; | |
509 | type = "DHCPNACK"; | |
510 | break; | |
511 | case DHCPACK: | |
512 | handler = dhcpack; | |
513 | type = "DHCPACK"; | |
514 | break; | |
515 | default: | |
516 | break; | |
517 | } | |
518 | } else if (options_valid && packet->op == BOOTREPLY) { | |
519 | handler = dhcpoffer; | |
520 | type = "BOOTREPLY"; | |
521 | } | |
522 | ||
bdf60627 AHJ |
523 | if (handler && client->xid == client->packet.xid) { |
524 | if (hfrom->hlen == 6) | |
525 | note("%s from %s (%s)", type, piaddr(from), | |
526 | ether_ntoa((struct ether_addr *)hfrom->haddr)); | |
527 | else | |
528 | note("%s from %s", type, piaddr(from)); | |
529 | } else | |
530 | handler = NULL; | |
531 | ||
846204b6 HT |
532 | for (ap = config->reject_list; ap && handler; ap = ap->next) |
533 | if (addr_eq(from, ap->addr)) { | |
534 | note("%s from %s rejected.", type, piaddr(from)); | |
535 | handler = NULL; | |
536 | } | |
537 | ||
538 | if (handler) | |
539 | (*handler)(from, options); | |
540 | ||
541 | for (i = 0; i < 256; i++) | |
542 | if (options[i].len && options[i].data) | |
543 | free(options[i].data); | |
544 | } |