diff options
Diffstat (limited to 'contrib/isc-dhcp/common/options.c')
-rw-r--r-- | contrib/isc-dhcp/common/options.c | 609 |
1 files changed, 609 insertions, 0 deletions
diff --git a/contrib/isc-dhcp/common/options.c b/contrib/isc-dhcp/common/options.c new file mode 100644 index 000000000000..9d6b7f46ab46 --- /dev/null +++ b/contrib/isc-dhcp/common/options.c @@ -0,0 +1,609 @@ +/* options.c + + DHCP options parsing and reassembly. */ + +/* + * Copyright (c) 1995, 1996, 1997, 1998 The Internet Software Consortium. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of The Internet Software Consortium nor the names + * of its contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE INTERNET SOFTWARE CONSORTIUM AND + * CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE INTERNET SOFTWARE CONSORTIUM OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * This software has been written for the Internet Software Consortium + * by Ted Lemon <mellon@fugue.com> in cooperation with Vixie + * Enterprises. To learn more about the Internet Software Consortium, + * see ``http://www.vix.com/isc''. To learn more about Vixie + * Enterprises, see ``http://www.vix.com''. + */ + +#ifndef lint +static char copyright[] = +"$Id: options.c,v 1.26.2.3 1998/06/25 21:11:30 mellon Exp $ Copyright (c) 1995, 1996, 1997, 1998 The Internet Software Consortium. All rights reserved.\n"; +#endif /* not lint */ + +#define DHCP_OPTION_DATA +#include "dhcpd.h" + +/* Parse all available options out of the specified packet. */ + +void parse_options (packet) + struct packet *packet; +{ + /* Initially, zero all option pointers. */ + memset (packet -> options, 0, sizeof (packet -> options)); + + /* If we don't see the magic cookie, there's nothing to parse. */ + if (memcmp (packet -> raw -> options, DHCP_OPTIONS_COOKIE, 4)) { + packet -> options_valid = 0; + return; + } + + /* Go through the options field, up to the end of the packet + or the End field. */ + parse_option_buffer (packet, &packet -> raw -> options [4], + packet -> packet_length - DHCP_FIXED_NON_UDP - 4); + /* If we parsed a DHCP Option Overload option, parse more + options out of the buffer(s) containing them. */ + if (packet -> options_valid + && packet -> options [DHO_DHCP_OPTION_OVERLOAD].data) { + if (packet -> options [DHO_DHCP_OPTION_OVERLOAD].data [0] & 1) + parse_option_buffer (packet, + (unsigned char *) + packet -> raw -> file, + sizeof packet -> raw -> file); + if (packet -> options [DHO_DHCP_OPTION_OVERLOAD].data [0] & 2) + parse_option_buffer (packet, + (unsigned char *) + packet -> raw -> sname, + sizeof packet -> raw -> sname); + } +} + +/* Parse options out of the specified buffer, storing addresses of option + values in packet -> options and setting packet -> options_valid if no + errors are encountered. */ + +void parse_option_buffer (packet, buffer, length) + struct packet *packet; + unsigned char *buffer; + int length; +{ + unsigned char *s, *t; + unsigned char *end = buffer + length; + int len; + int code; + + for (s = buffer; *s != DHO_END && s < end; ) { + code = s [0]; + /* Pad options don't have a length - just skip them. */ + if (code == DHO_PAD) { + ++s; + continue; + } + /* All other fields (except end, see above) have a + one-byte length. */ + len = s [1]; + + /* If the length is outrageous, the options are bad. */ + if (s + len + 2 > end) { + warn ("Option %s length %d overflows input buffer.", + dhcp_options [code].name, + len); + packet -> options_valid = 0; + return; + } + /* If we haven't seen this option before, just make + space for it and copy it there. */ + if (!packet -> options [code].data) { + if (!(t = (unsigned char *)malloc (len + 1))) + error ("Can't allocate storage for option %s.", + dhcp_options [code].name); + /* Copy and NUL-terminate the option (in case it's an + ASCII string. */ + memcpy (t, &s [2], len); + t [len] = 0; + packet -> options [code].len = len; + packet -> options [code].data = t; + } else { + /* If it's a repeat, concatenate it to whatever + we last saw. This is really only required + for clients, but what the heck... */ + t = (unsigned char *) + malloc (len + + packet -> options [code].len + + 1); + if (!t) + error ("Can't expand storage for option %s.", + dhcp_options [code].name); + memcpy (t, packet -> options [code].data, + packet -> options [code].len); + memcpy (t + packet -> options [code].len, + &s [2], len); + packet -> options [code].len += len; + t [packet -> options [code].len] = 0; + free (packet -> options [code].data); + packet -> options [code].data = t; + } + s += len + 2; + } + packet -> options_valid = 1; +} + +/* cons options into a big buffer, and then split them out into the + three seperate buffers if needed. This allows us to cons up a set + of vendor options using the same routine. */ + +int cons_options (inpacket, outpacket, options, overload, terminate, bootpp) + struct packet *inpacket; + struct dhcp_packet *outpacket; + struct tree_cache **options; + int overload; /* Overload flags that may be set. */ + int terminate; + int bootpp; +{ + unsigned char priority_list [300]; + int priority_len; + unsigned char buffer [4096]; /* Really big buffer... */ + int main_buffer_size; + int mainbufix, bufix; + int option_size; + int length; + + /* If the client has provided a maximum DHCP message size, + use that; otherwise, if it's BOOTP, only 64 bytes; otherwise + use up to the minimum IP MTU size (576 bytes). */ + /* XXX if a BOOTP client specifies a max message size, we will + honor it. */ + if (inpacket && inpacket -> options [DHO_DHCP_MAX_MESSAGE_SIZE].data) { + main_buffer_size = + (getUShort (inpacket -> options + [DHO_DHCP_MAX_MESSAGE_SIZE].data) + - DHCP_FIXED_LEN); + /* Enforce a minimum packet size... */ + if (main_buffer_size < (576 - DHCP_FIXED_LEN)) + main_buffer_size = 576 - DHCP_FIXED_LEN; + if (main_buffer_size > sizeof buffer) + main_buffer_size = sizeof buffer; + } else if (bootpp) + main_buffer_size = 64; + else + main_buffer_size = 576 - DHCP_FIXED_LEN; + + /* Preload the option priority list with mandatory options. */ + priority_len = 0; + priority_list [priority_len++] = DHO_DHCP_MESSAGE_TYPE; + priority_list [priority_len++] = DHO_DHCP_SERVER_IDENTIFIER; + priority_list [priority_len++] = DHO_DHCP_LEASE_TIME; + priority_list [priority_len++] = DHO_DHCP_MESSAGE; + + /* If the client has provided a list of options that it wishes + returned, use it to prioritize. Otherwise, prioritize + based on the default priority list. */ + + if (inpacket && + inpacket -> options [DHO_DHCP_PARAMETER_REQUEST_LIST].data) { + int prlen = (inpacket -> + options [DHO_DHCP_PARAMETER_REQUEST_LIST].len); + if (prlen + priority_len > sizeof priority_list) + prlen = (sizeof priority_list) - priority_len; + + memcpy (&priority_list [priority_len], + inpacket -> options + [DHO_DHCP_PARAMETER_REQUEST_LIST].data, prlen); + priority_len += prlen; + } else { + memcpy (&priority_list [priority_len], + dhcp_option_default_priority_list, + sizeof_dhcp_option_default_priority_list); + priority_len += sizeof_dhcp_option_default_priority_list; + } + + /* Copy the options into the big buffer... */ + option_size = store_options (buffer, + (main_buffer_size - 7 + + ((overload & 1) ? DHCP_FILE_LEN : 0) + + ((overload & 2) ? DHCP_SNAME_LEN : 0)), + options, priority_list, priority_len, + main_buffer_size, + (main_buffer_size + + ((overload & 1) ? DHCP_FILE_LEN : 0)), + terminate); + + /* Put the cookie up front... */ + memcpy (outpacket -> options, DHCP_OPTIONS_COOKIE, 4); + mainbufix = 4; + + /* If we're going to have to overload, store the overload + option at the beginning. If we can, though, just store the + whole thing in the packet's option buffer and leave it at + that. */ + if (option_size <= main_buffer_size - mainbufix) { + memcpy (&outpacket -> options [mainbufix], + buffer, option_size); + mainbufix += option_size; + if (mainbufix < main_buffer_size) + outpacket -> options [mainbufix++] + = DHO_END; + length = DHCP_FIXED_NON_UDP + mainbufix; + } else { + outpacket -> options [mainbufix++] = + DHO_DHCP_OPTION_OVERLOAD; + outpacket -> options [mainbufix++] = 1; + if (option_size > main_buffer_size - mainbufix + DHCP_FILE_LEN) + outpacket -> options [mainbufix++] = 3; + else + outpacket -> options [mainbufix++] = 1; + + memcpy (&outpacket -> options [mainbufix], + buffer, main_buffer_size - mainbufix); + bufix = main_buffer_size - mainbufix; + length = DHCP_FIXED_NON_UDP + mainbufix; + if (overload & 1) { + if (option_size - bufix <= DHCP_FILE_LEN) { + memcpy (outpacket -> file, + &buffer [bufix], option_size - bufix); + mainbufix = option_size - bufix; + if (mainbufix < DHCP_FILE_LEN) + outpacket -> file [mainbufix++] + = DHO_END; + while (mainbufix < DHCP_FILE_LEN) + outpacket -> file [mainbufix++] + = DHO_PAD; + } else { + memcpy (outpacket -> file, + &buffer [bufix], DHCP_FILE_LEN); + bufix += DHCP_FILE_LEN; + } + } + if ((overload & 2) && option_size < bufix) { + memcpy (outpacket -> sname, + &buffer [bufix], option_size - bufix); + + mainbufix = option_size - bufix; + if (mainbufix < DHCP_SNAME_LEN) + outpacket -> file [mainbufix++] + = DHO_END; + while (mainbufix < DHCP_SNAME_LEN) + outpacket -> file [mainbufix++] + = DHO_PAD; + } + } + return length; +} + +/* Store all the requested options into the requested buffer. */ + +int store_options (buffer, buflen, options, priority_list, priority_len, + first_cutoff, second_cutoff, terminate) + unsigned char *buffer; + int buflen; + struct tree_cache **options; + unsigned char *priority_list; + int priority_len; + int first_cutoff, second_cutoff; + int terminate; +{ + int bufix = 0; + int option_stored [256]; + int i; + int ix; + int tto; + + /* Zero out the stored-lengths array. */ + memset (option_stored, 0, sizeof option_stored); + + /* Copy out the options in the order that they appear in the + priority list... */ + for (i = 0; i < priority_len; i++) { + /* Code for next option to try to store. */ + int code = priority_list [i]; + int optstart; + + /* Number of bytes left to store (some may already + have been stored by a previous pass). */ + int length; + + /* If no data is available for this option, skip it. */ + if (!options [code]) { + continue; + } + + /* The client could ask for things that are mandatory, + in which case we should avoid storing them twice... */ + if (option_stored [code]) + continue; + option_stored [code] = 1; + + /* Find the value of the option... */ + if (!tree_evaluate (options [code])) { + continue; + } + + /* We should now have a constant length for the option. */ + length = options [code] -> len; + + /* Do we add a NUL? */ + if (terminate && dhcp_options [code].format [0] == 't') { + length++; + tto = 1; + } else { + tto = 0; + } + + /* Try to store the option. */ + + /* If the option's length is more than 255, we must store it + in multiple hunks. Store 255-byte hunks first. However, + in any case, if the option data will cross a buffer + boundary, split it across that boundary. */ + + ix = 0; + + optstart = bufix; + while (length) { + unsigned char incr = length > 255 ? 255 : length; + + /* If this hunk of the buffer will cross a + boundary, only go up to the boundary in this + pass. */ + if (bufix < first_cutoff && + bufix + incr > first_cutoff) + incr = first_cutoff - bufix; + else if (bufix < second_cutoff && + bufix + incr > second_cutoff) + incr = second_cutoff - bufix; + + /* If this option is going to overflow the buffer, + skip it. */ + if (bufix + 2 + incr > buflen) { + bufix = optstart; + break; + } + + /* Everything looks good - copy it in! */ + buffer [bufix] = code; + buffer [bufix + 1] = incr; + if (tto && incr == length) { + memcpy (buffer + bufix + 2, + options [code] -> value + ix, + incr - 1); + buffer [bufix + 2 + incr - 1] = 0; + } else { + memcpy (buffer + bufix + 2, + options [code] -> value + ix, incr); + } + length -= incr; + ix += incr; + bufix += 2 + incr; + } + } + return bufix; +} + +/* Format the specified option so that a human can easily read it. */ + +char *pretty_print_option (code, data, len, emit_commas, emit_quotes) + unsigned int code; + unsigned char *data; + int len; + int emit_commas; + int emit_quotes; +{ + static char optbuf [32768]; /* XXX */ + int hunksize = 0; + int numhunk = -1; + int numelem = 0; + char fmtbuf [32]; + int i, j; + char *op = optbuf; + unsigned char *dp = data; + struct in_addr foo; + char comma; + + /* Code should be between 0 and 255. */ + if (code > 255) + error ("pretty_print_option: bad code %d\n", code); + + if (emit_commas) + comma = ','; + else + comma = ' '; + + /* Figure out the size of the data. */ + for (i = 0; dhcp_options [code].format [i]; i++) { + if (!numhunk) { + warn ("%s: Excess information in format string: %s\n", + dhcp_options [code].name, + &(dhcp_options [code].format [i])); + break; + } + numelem++; + fmtbuf [i] = dhcp_options [code].format [i]; + switch (dhcp_options [code].format [i]) { + case 'A': + --numelem; + fmtbuf [i] = 0; + numhunk = 0; + break; + case 'X': + fmtbuf [i] = 'x'; + fmtbuf [i + 1] = 0; + hunksize++; + numhunk = 0; + comma = ':'; + break; + case 't': + fmtbuf [i] = 't'; + fmtbuf [i + 1] = 0; + numhunk = -2; + break; + case 'I': + case 'l': + case 'L': + hunksize += 4; + break; + case 's': + case 'S': + hunksize += 2; + break; + case 'b': + case 'B': + case 'f': + hunksize++; + break; + case 'e': + break; + default: + warn ("%s: garbage in format string: %s\n", + dhcp_options [code].name, + &(dhcp_options [code].format [i])); + break; + } + } + + /* Check for too few bytes... */ + if (hunksize > len) { + warn ("%s: expecting at least %d bytes; got %d", + dhcp_options [code].name, + hunksize, len); + return "<error>"; + } + /* Check for too many bytes... */ + if (numhunk == -1 && hunksize < len) + warn ("%s: %d extra bytes", + dhcp_options [code].name, + len - hunksize); + + /* If this is an array, compute its size. */ + if (!numhunk) + numhunk = len / hunksize; + /* See if we got an exact number of hunks. */ + if (numhunk > 0 && numhunk * hunksize < len) + warn ("%s: %d extra bytes at end of array\n", + dhcp_options [code].name, + len - numhunk * hunksize); + + /* A one-hunk array prints the same as a single hunk. */ + if (numhunk < 0) + numhunk = 1; + + /* Cycle through the array (or hunk) printing the data. */ + for (i = 0; i < numhunk; i++) { + for (j = 0; j < numelem; j++) { + switch (fmtbuf [j]) { + case 't': + if (emit_quotes) + *op++ = '"'; + strcpy (op, (char *)dp); + op += strlen ((char *)dp); + if (emit_quotes) + *op++ = '"'; + *op = 0; + break; + case 'I': + foo.s_addr = htonl (getULong (dp)); + strcpy (op, inet_ntoa (foo)); + dp += 4; + break; + case 'l': + sprintf (op, "%ld", (long)getLong (dp)); + dp += 4; + break; + case 'L': + sprintf (op, "%ld", + (unsigned long)getULong (dp)); + dp += 4; + break; + case 's': + sprintf (op, "%d", getShort (dp)); + dp += 2; + break; + case 'S': + sprintf (op, "%d", getUShort (dp)); + dp += 2; + break; + case 'b': + sprintf (op, "%d", *(char *)dp++); + break; + case 'B': + sprintf (op, "%d", *dp++); + break; + case 'x': + sprintf (op, "%x", *dp++); + break; + case 'f': + strcpy (op, *dp++ ? "true" : "false"); + break; + default: + warn ("Unexpected format code %c", fmtbuf [j]); + } + op += strlen (op); + if (j + 1 < numelem && comma != ':') + *op++ = ' '; + } + if (i + 1 < numhunk) { + *op++ = comma; + } + + } + return optbuf; +} + +void do_packet (interface, packet, len, from_port, from, hfrom) + struct interface_info *interface; + struct dhcp_packet *packet; + int len; + unsigned int from_port; + struct iaddr from; + struct hardware *hfrom; +{ + struct packet tp; + + if (packet -> hlen > sizeof packet -> chaddr) { + note ("Discarding packet with invalid hlen."); + return; + } + + memset (&tp, 0, sizeof tp); + tp.raw = packet; + tp.packet_length = len; + tp.client_port = from_port; + tp.client_addr = from; + tp.interface = interface; + tp.haddr = hfrom; + + parse_options (&tp); + if (tp.options_valid && + tp.options [DHO_DHCP_MESSAGE_TYPE].data) + tp.packet_type = + tp.options [DHO_DHCP_MESSAGE_TYPE].data [0]; + if (tp.packet_type) + dhcp (&tp); + else + bootp (&tp); +} + |