xref: /reactos/base/services/dhcpcsvc/dhcp/options.c (revision 8a978a17)
1 /*	$OpenBSD: options.c,v 1.15 2004/12/26 03:17:07 deraadt Exp $	*/
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 #define DHCP_OPTION_DATA
44 #include <rosdhcp.h>
45 
46 int bad_options = 0;
47 int bad_options_max = 5;
48 
49 void	parse_options(struct packet *);
50 void	parse_option_buffer(struct packet *, unsigned char *, int);
51 int	store_options(unsigned char *, int, struct tree_cache **,
52 	    unsigned char *, int, int, int);
53 
54 
55 /*
56  * Parse all available options out of the specified packet.
57  */
58 void
59 parse_options(struct packet *packet)
60 {
61 	/* Initially, zero all option pointers. */
62 	memset(packet->options, 0, sizeof(packet->options));
63 
64 	/* If we don't see the magic cookie, there's nothing to parse. */
65 	if (memcmp(packet->raw->options, DHCP_OPTIONS_COOKIE, 4)) {
66 		packet->options_valid = 0;
67 		return;
68 	}
69 
70 	/*
71 	 * Go through the options field, up to the end of the packet or
72 	 * the End field.
73 	 */
74 	parse_option_buffer(packet, &packet->raw->options[4],
75 	    packet->packet_length - DHCP_FIXED_NON_UDP - 4);
76 
77 	/*
78 	 * If we parsed a DHCP Option Overload option, parse more
79 	 * options out of the buffer(s) containing them.
80 	 */
81 	if (packet->options_valid &&
82 	    packet->options[DHO_DHCP_OPTION_OVERLOAD].data) {
83 		if (packet->options[DHO_DHCP_OPTION_OVERLOAD].data[0] & 1)
84 			parse_option_buffer(packet,
85 			    (unsigned char *)packet->raw->file,
86 			    sizeof(packet->raw->file));
87 		if (packet->options[DHO_DHCP_OPTION_OVERLOAD].data[0] & 2)
88 			parse_option_buffer(packet,
89 			    (unsigned char *)packet->raw->sname,
90 			    sizeof(packet->raw->sname));
91 	}
92 }
93 
94 /*
95  * Parse options out of the specified buffer, storing addresses of
96  * option values in packet->options and setting packet->options_valid if
97  * no errors are encountered.
98  */
99 void
100 parse_option_buffer(struct packet *packet,
101     unsigned char *buffer, int length)
102 {
103 	unsigned char *s, *t, *end = buffer + length;
104 	int len, code;
105 
106 	for (s = buffer; *s != DHO_END && s < end; ) {
107 		code = s[0];
108 
109 		/* Pad options don't have a length - just skip them. */
110 		if (code == DHO_PAD) {
111 			s++;
112 			continue;
113 		}
114 		if (s + 2 > end) {
115 			len = 65536;
116 			goto bogus;
117 		}
118 
119 		/*
120 		 * All other fields (except end, see above) have a
121 		 * one-byte length.
122 		 */
123 		len = s[1];
124 
125 		/*
126 		 * If the length is outrageous, silently skip the rest,
127 		 * and mark the packet bad. Unfortunately some crappy
128 		 * dhcp servers always seem to give us garbage on the
129 		 * end of a packet. so rather than keep refusing, give
130 		 * up and try to take one after seeing a few without
131 		 * anything good.
132 		 */
133 		if (s + len + 2 > end) {
134 		    bogus:
135 			bad_options++;
136 			warning("option %s (%d) %s.",
137 			    dhcp_options[code].name, len,
138 			    "larger than buffer");
139 			if (bad_options == bad_options_max) {
140 				packet->options_valid = 1;
141 				bad_options = 0;
142 				warning("Many bogus options seen in offers. "
143 				    "Taking this offer in spite of bogus "
144 				    "options - hope for the best!");
145 			} else {
146 				warning("rejecting bogus offer.");
147 				packet->options_valid = 0;
148 			}
149 			return;
150 		}
151 		/*
152 		 * If we haven't seen this option before, just make
153 		 * space for it and copy it there.
154 		 */
155 		if (!packet->options[code].data) {
156 			if (!(t = calloc(1, len + 1)))
157 				error("Can't allocate storage for option %s.",
158 				    dhcp_options[code].name);
159 			/*
160 			 * Copy and NUL-terminate the option (in case
161 			 * it's an ASCII string.
162 			 */
163 			memcpy(t, &s[2], len);
164 			t[len] = 0;
165 			packet->options[code].len = len;
166 			packet->options[code].data = t;
167 		} else {
168 			/*
169 			 * If it's a repeat, concatenate it to whatever
170 			 * we last saw.   This is really only required
171 			 * for clients, but what the heck...
172 			 */
173 			t = calloc(1, len + packet->options[code].len + 1);
174 			if (!t) {
175 				error("Can't expand storage for option %s.",
176 				    dhcp_options[code].name);
177                                 return;
178                         }
179 			memcpy(t, packet->options[code].data,
180 				packet->options[code].len);
181 			memcpy(t + packet->options[code].len,
182 				&s[2], len);
183 			packet->options[code].len += len;
184 			t[packet->options[code].len] = 0;
185 			free(packet->options[code].data);
186 			packet->options[code].data = t;
187 		}
188 		s += len + 2;
189 	}
190 	packet->options_valid = 1;
191 }
192 
193 /*
194  * cons options into a big buffer, and then split them out into the
195  * three separate buffers if needed.  This allows us to cons up a set of
196  * vendor options using the same routine.
197  */
198 int
199 cons_options(struct packet *inpacket, struct dhcp_packet *outpacket,
200     int mms, struct tree_cache **options)
201 {
202 	unsigned char priority_list[300], buffer[4096];
203 	int priority_len, main_buffer_size, mainbufix;
204 	int option_size, length;
205 
206 	/*
207 	 * If the client has provided a maximum DHCP message size, use
208 	 * that; otherwise, if it's BOOTP, only 64 bytes; otherwise use
209 	 * up to the minimum IP MTU size (576 bytes).
210 	 *
211 	 * XXX if a BOOTP client specifies a max message size, we will
212 	 * honor it.
213 	 */
214 	if (!mms &&
215 	    inpacket &&
216 	    inpacket->options[DHO_DHCP_MAX_MESSAGE_SIZE].data &&
217 	    (inpacket->options[DHO_DHCP_MAX_MESSAGE_SIZE].len >=
218 	    sizeof(u_int16_t)))
219 		mms = getUShort(
220 		    inpacket->options[DHO_DHCP_MAX_MESSAGE_SIZE].data);
221 
222 	if (mms)
223 		main_buffer_size = mms - DHCP_FIXED_LEN;
224 	else
225 		main_buffer_size = 576 - DHCP_FIXED_LEN;
226 
227 	if (main_buffer_size > sizeof(buffer))
228 		main_buffer_size = sizeof(buffer);
229 
230 	/* Preload the option priority list with mandatory options. */
231 	priority_len = 0;
232 	priority_list[priority_len++] = DHO_DHCP_MESSAGE_TYPE;
233 	priority_list[priority_len++] = DHO_DHCP_SERVER_IDENTIFIER;
234 	priority_list[priority_len++] = DHO_DHCP_LEASE_TIME;
235 	priority_list[priority_len++] = DHO_DHCP_MESSAGE;
236 
237 	/*
238 	 * If the client has provided a list of options that it wishes
239 	 * returned, use it to prioritize.  Otherwise, prioritize based
240 	 * on the default priority list.
241 	 */
242 	if (inpacket &&
243 	    inpacket->options[DHO_DHCP_PARAMETER_REQUEST_LIST].data) {
244 		int prlen =
245 		    inpacket->options[DHO_DHCP_PARAMETER_REQUEST_LIST].len;
246 		if (prlen + priority_len > sizeof(priority_list))
247 			prlen = sizeof(priority_list) - priority_len;
248 
249 		memcpy(&priority_list[priority_len],
250 		    inpacket->options[DHO_DHCP_PARAMETER_REQUEST_LIST].data,
251 		    prlen);
252 		priority_len += prlen;
253 	} else {
254 		memcpy(&priority_list[priority_len],
255 		    dhcp_option_default_priority_list,
256 		    sizeof_dhcp_option_default_priority_list);
257 		priority_len += sizeof_dhcp_option_default_priority_list;
258 	}
259 
260 	/* Copy the options into the big buffer... */
261 	option_size = store_options(
262 	    buffer,
263 	    main_buffer_size - 7,
264 	    options, priority_list, priority_len, main_buffer_size,
265 	    main_buffer_size);
266 
267 	/* Put the cookie up front... */
268 	memcpy(outpacket->options, DHCP_OPTIONS_COOKIE, 4);
269 	mainbufix = 4;
270 
271 	/*
272 	 * If we're going to have to overload, store the overload option
273 	 * at the beginning.  If we can, though, just store the whole
274 	 * thing in the packet's option buffer and leave it at that.
275 	 */
276 	if (option_size <= main_buffer_size - mainbufix) {
277 		memcpy(&outpacket->options[mainbufix],
278 		    buffer, option_size);
279 		mainbufix += option_size;
280 		if (mainbufix < main_buffer_size)
281 			outpacket->options[mainbufix++] = DHO_END;
282 		length = DHCP_FIXED_NON_UDP + mainbufix;
283 	} else {
284 		outpacket->options[mainbufix++] = DHO_DHCP_OPTION_OVERLOAD;
285 		outpacket->options[mainbufix++] = 1;
286 		if (option_size >
287 		    main_buffer_size - mainbufix + DHCP_FILE_LEN)
288 			outpacket->options[mainbufix++] = 3;
289 		else
290 			outpacket->options[mainbufix++] = 1;
291 
292 		memcpy(&outpacket->options[mainbufix],
293 		    buffer, main_buffer_size - mainbufix);
294 		length = DHCP_FIXED_NON_UDP + mainbufix;
295 	}
296 	return (length);
297 }
298 
299 /*
300  * Store all the requested options into the requested buffer.
301  */
302 int
303 store_options(unsigned char *buffer, int buflen, struct tree_cache **options,
304     unsigned char *priority_list, int priority_len, int first_cutoff,
305     int second_cutoff)
306 {
307 	int bufix = 0, option_stored[256], i, ix;
308 
309 	/* Zero out the stored-lengths array. */
310 	memset(option_stored, 0, sizeof(option_stored));
311 
312 	/*
313 	 * Copy out the options in the order that they appear in the
314 	 * priority list...
315 	 */
316 	for (i = 0; i < priority_len; i++) {
317 		/* Code for next option to try to store. */
318 		int code = priority_list[i];
319 		int optstart;
320 
321 		/*
322 		 * Number of bytes left to store (some may already have
323 		 * been stored by a previous pass).
324 		 */
325 		int length;
326 
327 		/* If no data is available for this option, skip it. */
328 		if (!options[code]) {
329 			continue;
330 		}
331 
332 		/*
333 		 * The client could ask for things that are mandatory,
334 		 * in which case we should avoid storing them twice...
335 		 */
336 		if (option_stored[code])
337 			continue;
338 		option_stored[code] = 1;
339 
340 		/* We should now have a constant length for the option. */
341 		length = options[code]->len;
342 
343 		/* Try to store the option. */
344 
345 		/*
346 		 * If the option's length is more than 255, we must
347 		 * store it in multiple hunks.   Store 255-byte hunks
348 		 * first.  However, in any case, if the option data will
349 		 * cross a buffer boundary, split it across that
350 		 * boundary.
351 		 */
352 		ix = 0;
353 
354 		optstart = bufix;
355 		while (length) {
356 			unsigned char incr = length > 255 ? 255 : length;
357 
358 			/*
359 			 * If this hunk of the buffer will cross a
360 			 * boundary, only go up to the boundary in this
361 			 * pass.
362 			 */
363 			if (bufix < first_cutoff &&
364 			    bufix + incr > first_cutoff)
365 				incr = first_cutoff - bufix;
366 			else if (bufix < second_cutoff &&
367 			    bufix + incr > second_cutoff)
368 				incr = second_cutoff - bufix;
369 
370 			/*
371 			 * If this option is going to overflow the
372 			 * buffer, skip it.
373 			 */
374 			if (bufix + 2 + incr > buflen) {
375 				bufix = optstart;
376 				break;
377 			}
378 
379 			/* Everything looks good - copy it in! */
380 			buffer[bufix] = code;
381 			buffer[bufix + 1] = incr;
382 			memcpy(buffer + bufix + 2,
383 			   options[code]->value + ix, incr);
384 			length -= incr;
385 			ix += incr;
386 			bufix += 2 + incr;
387 		}
388 	}
389 	return (bufix);
390 }
391 
392 /*
393  * Format the specified option so that a human can easily read it.
394  */
395 char *
396 pretty_print_option(unsigned int code, unsigned char *data, int len,
397     int emit_commas, int emit_quotes)
398 {
399 	static char optbuf[32768]; /* XXX */
400 	int hunksize = 0, numhunk = -1, numelem = 0;
401 	char fmtbuf[32], *op = optbuf;
402 	int i, j, k, opleft = sizeof(optbuf);
403 	unsigned char *dp = data;
404 	struct in_addr foo;
405 	char comma;
406 
407 	/* Code should be between 0 and 255. */
408 	if (code > 255)
409 		error("pretty_print_option: bad code %d", code);
410 
411 	if (emit_commas)
412 		comma = ',';
413 	else
414 		comma = ' ';
415 
416 	/* Figure out the size of the data. */
417 	for (i = 0; dhcp_options[code].format[i]; i++) {
418 		if (!numhunk) {
419 			warning("%s: Excess information in format string: %s",
420 			    dhcp_options[code].name,
421 			    &(dhcp_options[code].format[i]));
422 			break;
423 		}
424 		numelem++;
425 		fmtbuf[i] = dhcp_options[code].format[i];
426 		switch (dhcp_options[code].format[i]) {
427 		case 'A':
428 			--numelem;
429 			fmtbuf[i] = 0;
430 			numhunk = 0;
431 			break;
432 		case 'X':
433 			for (k = 0; k < len; k++)
434 				if (!isascii(data[k]) ||
435 				    !isprint(data[k]))
436 					break;
437 			if (k == len) {
438 				fmtbuf[i] = 't';
439 				numhunk = -2;
440 			} else {
441 				fmtbuf[i] = 'x';
442 				hunksize++;
443 				comma = ':';
444 				numhunk = 0;
445 			}
446 			fmtbuf[i + 1] = 0;
447 			break;
448 		case 't':
449 			fmtbuf[i] = 't';
450 			fmtbuf[i + 1] = 0;
451 			numhunk = -2;
452 			break;
453 		case 'I':
454 		case 'l':
455 		case 'L':
456 			hunksize += 4;
457 			break;
458 		case 's':
459 		case 'S':
460 			hunksize += 2;
461 			break;
462 		case 'b':
463 		case 'B':
464 		case 'f':
465 			hunksize++;
466 			break;
467 		case 'e':
468 			break;
469 		default:
470 			warning("%s: garbage in format string: %s",
471 			    dhcp_options[code].name,
472 			    &(dhcp_options[code].format[i]));
473 			break;
474 		}
475 	}
476 
477 	/* Check for too few bytes... */
478 	if (hunksize > len) {
479 		warning("%s: expecting at least %d bytes; got %d",
480 		    dhcp_options[code].name, hunksize, len);
481 		return ("<error>");
482 	}
483 	/* Check for too many bytes... */
484 	if (numhunk == -1 && hunksize < len)
485 		warning("%s: %d extra bytes",
486 		    dhcp_options[code].name, len - hunksize);
487 
488 	/* If this is an array, compute its size. */
489 	if (!numhunk)
490 		numhunk = len / hunksize;
491 	/* See if we got an exact number of hunks. */
492 	if (numhunk > 0 && numhunk * hunksize < len)
493 		warning("%s: %d extra bytes at end of array",
494 		    dhcp_options[code].name, len - numhunk * hunksize);
495 
496 	/* A one-hunk array prints the same as a single hunk. */
497 	if (numhunk < 0)
498 		numhunk = 1;
499 
500 	/* Cycle through the array (or hunk) printing the data. */
501 	for (i = 0; i < numhunk; i++) {
502 		for (j = 0; j < numelem; j++) {
503 			int opcount;
504 			switch (fmtbuf[j]) {
505 			case 't':
506 				if (emit_quotes) {
507 					*op++ = '"';
508 					opleft--;
509 				}
510 				for (; dp < data + len; dp++) {
511 					if (!isascii(*dp) ||
512 					    !isprint(*dp)) {
513 						if (dp + 1 != data + len ||
514 						    *dp != 0) {
515 							_snprintf(op, opleft,
516 							    "\\%03o", *dp);
517 							op += 4;
518 							opleft -= 4;
519 						}
520 					} else if (*dp == '"' ||
521 					    *dp == '\'' ||
522 					    *dp == '$' ||
523 					    *dp == '`' ||
524 					    *dp == '\\') {
525 						*op++ = '\\';
526 						*op++ = *dp;
527 						opleft -= 2;
528 					} else {
529 						*op++ = *dp;
530 						opleft--;
531 					}
532 				}
533 				if (emit_quotes) {
534 					*op++ = '"';
535 					opleft--;
536 				}
537 
538 				*op = 0;
539 				break;
540 			case 'I':
541 				foo.s_addr = htonl(getULong(dp));
542 				strncpy(op, inet_ntoa(foo), opleft - 1);
543 				op[opleft - 1] = ANSI_NULL;
544 				opcount = strlen(op);
545 				if (opcount >= opleft)
546 					goto toobig;
547 				opleft -= opcount;
548 				dp += 4;
549 				break;
550 			case 'l':
551 				opcount = _snprintf(op, opleft, "%ld",
552 				    (long)getLong(dp));
553 				if (opcount >= opleft || opcount == -1)
554 					goto toobig;
555 				opleft -= opcount;
556 				dp += 4;
557 				break;
558 			case 'L':
559 				opcount = _snprintf(op, opleft, "%ld",
560 				    (unsigned long)getULong(dp));
561 				if (opcount >= opleft || opcount == -1)
562 					goto toobig;
563 				opleft -= opcount;
564 				dp += 4;
565 				break;
566 			case 's':
567 				opcount = _snprintf(op, opleft, "%d",
568 				    getShort(dp));
569 				if (opcount >= opleft || opcount == -1)
570 					goto toobig;
571 				opleft -= opcount;
572 				dp += 2;
573 				break;
574 			case 'S':
575 				opcount = _snprintf(op, opleft, "%d",
576 				    getUShort(dp));
577 				if (opcount >= opleft || opcount == -1)
578 					goto toobig;
579 				opleft -= opcount;
580 				dp += 2;
581 				break;
582 			case 'b':
583 				opcount = _snprintf(op, opleft, "%d",
584 				    *(char *)dp++);
585 				if (opcount >= opleft || opcount == -1)
586 					goto toobig;
587 				opleft -= opcount;
588 				break;
589 			case 'B':
590 				opcount = _snprintf(op, opleft, "%d", *dp++);
591 				if (opcount >= opleft || opcount == -1)
592 					goto toobig;
593 				opleft -= opcount;
594 				break;
595 			case 'x':
596 				opcount = _snprintf(op, opleft, "%x", *dp++);
597 				if (opcount >= opleft || opcount == -1)
598 					goto toobig;
599 				opleft -= opcount;
600 				break;
601 			case 'f':
602 				opcount = (size_t) strncpy(op, *dp++ ? "true" : "false", opleft - 1);
603 				op[opleft - 1] = ANSI_NULL;
604 				if (opcount >= opleft)
605 					goto toobig;
606 				opleft -= opcount;
607 				break;
608 			default:
609 				warning("Unexpected format code %c", fmtbuf[j]);
610 			}
611 			op += strlen(op);
612 			opleft -= strlen(op);
613 			if (opleft < 1)
614 				goto toobig;
615 			if (j + 1 < numelem && comma != ':') {
616 				*op++ = ' ';
617 				opleft--;
618 			}
619 		}
620 		if (i + 1 < numhunk) {
621 			*op++ = comma;
622 			opleft--;
623 		}
624 		if (opleft < 1)
625 			goto toobig;
626 
627 	}
628 	return (optbuf);
629  toobig:
630 	warning("dhcp option too large");
631 	return ("<error>");
632 }
633 
634 void
635 do_packet(struct interface_info *interface, struct dhcp_packet *packet,
636     int len, unsigned int from_port, struct iaddr from, struct hardware *hfrom)
637 {
638 	struct packet tp;
639 	int i;
640 
641 	if (packet->hlen > sizeof(packet->chaddr)) {
642 		note("Discarding packet with invalid hlen.");
643 		return;
644 	}
645 
646 	memset(&tp, 0, sizeof(tp));
647 	tp.raw = packet;
648 	tp.packet_length = len;
649 	tp.client_port = from_port;
650 	tp.client_addr = from;
651 	tp.interface = interface;
652 	tp.haddr = hfrom;
653 
654 	parse_options(&tp);
655 	if (tp.options_valid &&
656 	    tp.options[DHO_DHCP_MESSAGE_TYPE].data)
657 		tp.packet_type = tp.options[DHO_DHCP_MESSAGE_TYPE].data[0];
658 	if (tp.packet_type)
659 		dhcp(&tp);
660 	else
661 		bootp(&tp);
662 
663 	/* Free the data associated with the options. */
664 	for (i = 0; i < 256; i++)
665 		if (tp.options[i].len && tp.options[i].data)
666 			free(tp.options[i].data);
667 }
668