diff options
Diffstat (limited to 'contrib/isc-dhcp/common/options.c')
-rw-r--r-- | contrib/isc-dhcp/common/options.c | 2106 |
1 files changed, 1830 insertions, 276 deletions
diff --git a/contrib/isc-dhcp/common/options.c b/contrib/isc-dhcp/common/options.c index b840716..b7a5a04 100644 --- a/contrib/isc-dhcp/common/options.c +++ b/contrib/isc-dhcp/common/options.c @@ -3,7 +3,7 @@ DHCP options parsing and reassembly. */ /* - * Copyright (c) 1995, 1996, 1997, 1998 The Internet Software Consortium. + * Copyright (c) 1995-2001 Internet Software Consortium. * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -34,176 +34,470 @@ * 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''. + * by Ted Lemon in cooperation with Vixie Enterprises and Nominum, Inc. + * To learn more about the Internet Software Consortium, see + * ``http://www.isc.org/''. To learn more about Vixie Enterprises, + * see ``http://www.vix.com''. To learn more about Nominum, Inc., see + * ``http://www.nominum.com''. */ #ifndef lint static char copyright[] = -"$Id: options.c,v 1.26.2.11 2000/06/24 07:24:02 mellon Exp $ Copyright (c) 1995, 1996, 1997, 1998 The Internet Software Consortium. All rights reserved.\n"; +"$Id: options.c,v 1.85.2.6 2001/10/18 20:11:38 mellon Exp $ Copyright (c) 1995-2001 The Internet Software Consortium. All rights reserved.\n"; #endif /* not lint */ #define DHCP_OPTION_DATA #include "dhcpd.h" -#include <ctype.h> +#include <omapip/omapip_p.h> + +struct option *vendor_cfg_option; + +static void do_option_set PROTO ((pair *, + struct option_cache *, + enum statement_op)); /* Parse all available options out of the specified packet. */ -void parse_options (packet) +int parse_options (packet) struct packet *packet; { - /* Initially, zero all option pointers. */ - memset (packet -> options, 0, sizeof (packet -> options)); + int i; + struct option_cache *op = (struct option_cache *)0; + + /* Allocate a new option state. */ + if (!option_state_allocate (&packet -> options, MDL)) { + packet -> options_valid = 0; + return 0; + } /* 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; + return 1; } /* 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 (!parse_option_buffer (packet -> options, + &packet -> raw -> options [4], + (packet -> packet_length - + DHCP_FIXED_NON_UDP - 4), + &dhcp_universe)) + return 0; + /* 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); + if (packet -> options_valid && + (op = lookup_option (&dhcp_universe, packet -> options, + DHO_DHCP_OPTION_OVERLOAD))) { + if (op -> data.data [0] & 1) { + if (!parse_option_buffer + (packet -> options, + (unsigned char *)packet -> raw -> file, + sizeof packet -> raw -> file, + &dhcp_universe)) + return 0; + } + if (op -> data.data [0] & 2) { + if (!parse_option_buffer + (packet -> options, + (unsigned char *)packet -> raw -> sname, + sizeof packet -> raw -> sname, + &dhcp_universe)) + return 0; + } } + packet -> options_valid = 1; + return 1; } /* 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; +int parse_option_buffer (options, buffer, length, universe) + struct option_state *options; + const unsigned char *buffer; + unsigned length; + struct universe *universe; { - unsigned char *s, *t; - unsigned char *end = buffer + length; - int len; + unsigned char *t; + const unsigned char *end = buffer + length; + unsigned len, offset; int code; + struct option_cache *op = (struct option_cache *)0; + struct buffer *bp = (struct buffer *)0; - for (s = buffer; *s != DHO_END && s < end; ) { - code = s [0]; + if (!buffer_allocate (&bp, length, MDL)) { + log_error ("no memory for option buffer."); + return 0; + } + memcpy (bp -> data, buffer, length); + + for (offset = 0; buffer [offset] != DHO_END && offset < length; ) { + code = buffer [offset]; /* Pad options don't have a length - just skip them. */ if (code == DHO_PAD) { - ++s; + ++offset; continue; } + + /* Don't look for length if the buffer isn't that big. */ + if (offset + 2 > length) { + len = 65536; + goto bogus; + } + /* All other fields (except end, see above) have a one-byte length. */ - len = s [1]; + len = buffer [offset + 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 (offset + len + 2 > length) { + bogus: + log_error ("parse_option_buffer: option %s (%d) %s.", + dhcp_options [code].name, len, + "larger than buffer"); + buffer_dereference (&bp, MDL); + return 0; } - /* 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 *) - dmalloc (len + 1, "parse_option_buffer")))) - 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 *) - dmalloc (len + packet -> options [code].len + 1, - "parse_option_buffer")); - 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; - dfree (packet -> options [code].data, - "parse_option_buffer"); - packet -> options [code].data = t; - } - s += len + 2; + + /* If the option contains an encapsulation, parse it. If + the parse fails, or the option isn't an encapsulation (by + far the most common case), or the option isn't entirely + an encapsulation, keep the raw data as well. */ + if (!((universe -> options [code] -> format [0] == 'e' || + universe -> options [code] -> format [0] == 'E') && + (parse_encapsulated_suboptions + (options, universe -> options [code], + buffer + offset + 2, len, + universe, (const char *)0)))) { + save_option_buffer (universe, options, bp, + &bp -> data [offset + 2], len, + universe -> options [code], 1); + } + offset += len + 2; } - packet -> options_valid = 1; + buffer_dereference (&bp, MDL); + return 1; +} + +/* If an option in an option buffer turns out to be an encapsulation, + figure out what to do. If we don't know how to de-encapsulate it, + or it's not well-formed, return zero; otherwise, return 1, indicating + that we succeeded in de-encapsulating it. */ + +struct universe *find_option_universe (struct option *eopt, const char *uname) +{ + int i; + char *s, *t; + struct universe *universe = (struct universe *)0; + + /* Look for the E option in the option format. */ + s = strchr (eopt -> format, 'E'); + if (!s) { + log_error ("internal encapsulation format error 1."); + return 0; + } + /* Look for the universe name in the option format. */ + t = strchr (++s, '.'); + /* If there was no trailing '.', or there's something after the + trailing '.', the option is bogus and we can't use it. */ + if (!t || t [1]) { + log_error ("internal encapsulation format error 2."); + return 0; + } + if (t == s && uname) { + for (i = 0; i < universe_count; i++) { + if (!strcmp (universes [i] -> name, uname)) { + universe = universes [i]; + break; + } + } + } else if (t != s) { + for (i = 0; i < universe_count; i++) { + if (strlen (universes [i] -> name) == t - s && + !memcmp (universes [i] -> name, + s, (unsigned)(t - s))) { + universe = universes [i]; + break; + } + } + } + return universe; +} + +/* If an option in an option buffer turns out to be an encapsulation, + figure out what to do. If we don't know how to de-encapsulate it, + or it's not well-formed, return zero; otherwise, return 1, indicating + that we succeeded in de-encapsulating it. */ + +int parse_encapsulated_suboptions (struct option_state *options, + struct option *eopt, + const unsigned char *buffer, + unsigned len, struct universe *eu, + const char *uname) +{ + int i; + struct universe *universe = find_option_universe (eopt, uname); + + /* If we didn't find the universe, we can't do anything with it + right now (e.g., we can't decode vendor options until we've + decoded the packet and executed the scopes that it matches). */ + if (!universe) + return 0; + + /* If we don't have a decoding function for it, we can't decode + it. */ + if (!universe -> decode) + return 0; + + i = (*universe -> decode) (options, buffer, len, universe); + + /* If there is stuff before the suboptions, we have to keep it. */ + if (eopt -> format [0] != 'E') + return 0; + /* Otherwise, return the status of the decode function. */ + return i; +} + +int fqdn_universe_decode (struct option_state *options, + const unsigned char *buffer, + unsigned length, struct universe *u) +{ + char *name; + struct buffer *bp = (struct buffer *)0; + + /* FQDN options have to be at least four bytes long. */ + if (length < 3) + return 0; + + /* Save the contents of the option in a buffer. */ + if (!buffer_allocate (&bp, length + 4, MDL)) { + log_error ("no memory for option buffer."); + return 0; + } + memcpy (&bp -> data [3], buffer + 1, length - 1); + + if (buffer [0] & 4) /* encoded */ + bp -> data [0] = 1; + else + bp -> data [0] = 0; + if (!save_option_buffer (&fqdn_universe, options, bp, + &bp -> data [0], 1, + &fqdn_options [FQDN_ENCODED], 0)) { + bad: + buffer_dereference (&bp, MDL); + return 0; + } + + if (buffer [0] & 1) /* server-update */ + bp -> data [2] = 1; + else + bp -> data [2] = 0; + if (buffer [0] & 2) /* no-client-update */ + bp -> data [1] = 1; + else + bp -> data [1] = 0; + + /* XXX Ideally we should store the name in DNS format, so if the + XXX label isn't in DNS format, we convert it to DNS format, + XXX rather than converting labels specified in DNS format to + XXX the plain ASCII representation. But that's hard, so + XXX not now. */ + + /* Not encoded using DNS format? */ + if (!bp -> data [0]) { + unsigned i; + + /* Some broken clients NUL-terminate this option. */ + if (buffer [length - 1] == 0) { + --length; + bp -> data [1] = 1; + } + + /* Determine the length of the hostname component of the + name. If the name contains no '.' character, it + represents a non-qualified label. */ + for (i = 3; i < length && buffer [i] != '.'; i++); + i -= 3; + + /* Note: If the client sends a FQDN, the first '.' will + be used as a NUL terminator for the hostname. */ + if (i) + if (!save_option_buffer (&fqdn_universe, options, bp, + &bp -> data[5], i, + &fqdn_options [FQDN_HOSTNAME], + 0)) + goto bad; + /* Note: If the client sends a single label, the + FQDN_DOMAINNAME option won't be set. */ + if (length > 4 + i && + !save_option_buffer (&fqdn_universe, options, bp, + &bp -> data[6 + i], length - 4 - i, + &fqdn_options [FQDN_DOMAINNAME], 1)) + goto bad; + /* Also save the whole name. */ + if (length > 3) + if (!save_option_buffer (&fqdn_universe, options, bp, + &bp -> data [5], length - 3, + &fqdn_options [FQDN_FQDN], 1)) + goto bad; + } else { + unsigned len; + unsigned total_len = 0; + unsigned first_len = 0; + int terminated = 0; + unsigned char *s; + + s = &bp -> data[5]; + + while (s < &bp -> data[0] + length + 2) { + len = *s; + if (len > 63) { + log_info ("fancy bits in fqdn option"); + return 0; + } + if (len == 0) { + terminated = 1; + break; + } + if (s + len > &bp -> data [0] + length + 3) { + log_info ("fqdn tag longer than buffer"); + return 0; + } + + if (first_len == 0) { + first_len = len; + } + + *s = '.'; + s += len + 1; + total_len += len; + } + + if (!terminated) { + first_len = total_len; + } + + if (first_len > 0 && + !save_option_buffer (&fqdn_universe, options, bp, + &bp -> data[6], first_len, + &fqdn_options [FQDN_HOSTNAME], 0)) + goto bad; + if (total_len > 0 && first_len != total_len) { + if (!save_option_buffer + (&fqdn_universe, options, bp, + &bp -> data[6 + first_len], total_len - first_len, + &fqdn_options [FQDN_DOMAINNAME], 1)) + goto bad; + } + if (total_len > 0) + if (!save_option_buffer (&fqdn_universe, options, bp, + &bp -> data [6], total_len, + &fqdn_options [FQDN_FQDN], 1)) + goto bad; + } + + if (!save_option_buffer (&fqdn_universe, options, bp, + &bp -> data [1], 1, + &fqdn_options [FQDN_NO_CLIENT_UPDATE], 0)) + goto bad; + if (!save_option_buffer (&fqdn_universe, options, bp, + &bp -> data [2], 1, + &fqdn_options [FQDN_SERVER_UPDATE], 0)) + goto bad; + + if (!save_option_buffer (&fqdn_universe, options, bp, + &bp -> data [3], 1, + &fqdn_options [FQDN_RCODE1], 0)) + goto bad; + if (!save_option_buffer (&fqdn_universe, options, bp, + &bp -> data [4], 1, + &fqdn_options [FQDN_RCODE2], 0)) + goto bad; + + buffer_dereference (&bp, MDL); + return 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, mms, - options, overload, terminate, bootpp, prl, prl_len) +int cons_options (inpacket, outpacket, lease, client_state, + mms, in_options, cfg_options, + scope, overload, terminate, bootpp, prl, vuname) struct packet *inpacket; struct dhcp_packet *outpacket; + struct lease *lease; + struct client_state *client_state; int mms; - struct tree_cache **options; + struct option_state *in_options; + struct option_state *cfg_options; + struct binding_scope **scope; int overload; /* Overload flags that may be set. */ int terminate; int bootpp; - u_int8_t *prl; - int prl_len; + struct data_string *prl; + const char *vuname; { - unsigned char priority_list [300]; +#define PRIORITY_COUNT 300 + unsigned priority_list [PRIORITY_COUNT]; int priority_len; unsigned char buffer [4096]; /* Really big buffer... */ - int main_buffer_size; - int mainbufix, bufix; - int option_size; - int length; + unsigned main_buffer_size; + unsigned mainbufix, bufix, agentix; + unsigned option_size; + unsigned length; + int i; + struct option_cache *op; + struct data_string ds; + pair pp, *hash; + int need_endopt = 0; + int have_sso = 0; - /* 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 (!mms && - inpacket && - inpacket -> options [DHO_DHCP_MAX_MESSAGE_SIZE].data && - (inpacket -> options [DHO_DHCP_MAX_MESSAGE_SIZE].len >= - sizeof (u_int16_t))) - mms = getUShort (inpacket -> options - [DHO_DHCP_MAX_MESSAGE_SIZE].data); + memset (&ds, 0, sizeof ds); + + /* If there's a Maximum Message Size option in the incoming packet + and no alternate maximum message size has been specified, take the + one in the packet. */ + + if (!mms && inpacket && + (op = lookup_option (&dhcp_universe, inpacket -> options, + DHO_DHCP_MAX_MESSAGE_SIZE))) { + evaluate_option_cache (&ds, inpacket, + lease, client_state, in_options, + cfg_options, scope, op, MDL); + if (ds.len >= sizeof (u_int16_t)) + mms = getUShort (ds.data); + data_string_forget (&ds, MDL); + } /* 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 (mms) + + if (mms) { main_buffer_size = mms - DHCP_FIXED_LEN; - else if (bootpp) - main_buffer_size = 64; - else + + /* Enforce a minimum packet size... */ + if (main_buffer_size < (576 - DHCP_FIXED_LEN)) + main_buffer_size = 576 - DHCP_FIXED_LEN; + } else if (bootpp) { + if (inpacket) { + main_buffer_size = + inpacket -> packet_length - DHCP_FIXED_LEN; + if (main_buffer_size < 64) + main_buffer_size = 64; + } else + main_buffer_size = 64; + } else main_buffer_size = 576 - DHCP_FIXED_LEN; + /* Set a hard limit at the size of the output buffer. */ if (main_buffer_size > sizeof buffer) main_buffer_size = sizeof buffer; @@ -213,35 +507,96 @@ int cons_options (inpacket, outpacket, mms, priority_list [priority_len++] = DHO_DHCP_SERVER_IDENTIFIER; priority_list [priority_len++] = DHO_DHCP_LEASE_TIME; priority_list [priority_len++] = DHO_DHCP_MESSAGE; + priority_list [priority_len++] = DHO_DHCP_REQUESTED_ADDRESS; - /* 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; - prl = priority_list; - } else if (prl) { - if (prl_len + priority_len > sizeof priority_list) - prl_len = (sizeof priority_list) - priority_len; - - memcpy (&priority_list [priority_len], prl, prl_len); - priority_len += prl_len; - prl = priority_list; + if (prl && prl -> len > 0) { + if ((op = lookup_option (&dhcp_universe, cfg_options, + DHO_SUBNET_SELECTION))) { + if (priority_len < PRIORITY_COUNT) + priority_list [priority_len++] = + DHO_SUBNET_SELECTION; + } + + data_string_truncate (prl, (PRIORITY_COUNT - priority_len)); + + for (i = 0; i < prl -> len; i++) { + /* Prevent client from changing order of delivery + of relay agent information option. */ + if (prl -> data [i] != DHO_DHCP_AGENT_OPTIONS) + priority_list [priority_len++] = + prl -> data [i]; + } } 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; + /* First, hardcode some more options that ought to be + sent first... */ + priority_list [priority_len++] = DHO_SUBNET_MASK; + priority_list [priority_len++] = DHO_ROUTERS; + priority_list [priority_len++] = DHO_DOMAIN_NAME_SERVERS; + priority_list [priority_len++] = DHO_HOST_NAME; + + /* Append a list of the standard DHCP options from the + standard DHCP option space. Actually, if a site + option space hasn't been specified, we wind up + treating the dhcp option space as the site option + space, and the first for loop is skipped, because + it's slightly more general to do it this way, + taking the 1Q99 DHCP futures work into account. */ + if (cfg_options -> site_code_min) { + for (i = 0; i < OPTION_HASH_SIZE; i++) { + hash = cfg_options -> universes [dhcp_universe.index]; + for (pp = hash [i]; pp; pp = pp -> cdr) { + op = (struct option_cache *)(pp -> car); + if (op -> option -> code < + cfg_options -> site_code_min && + priority_len < PRIORITY_COUNT && + (op -> option -> code != + DHO_DHCP_AGENT_OPTIONS)) + priority_list [priority_len++] = + op -> option -> code; + } + } + } + + /* Now cycle through the site option space, or if there + is no site option space, we'll be cycling through the + dhcp option space. */ + for (i = 0; i < OPTION_HASH_SIZE; i++) { + hash = (cfg_options -> universes + [cfg_options -> site_universe]); + for (pp = hash [i]; pp; pp = pp -> cdr) { + op = (struct option_cache *)(pp -> car); + if (op -> option -> code >= + cfg_options -> site_code_min && + priority_len < PRIORITY_COUNT && + (op -> option -> code != + DHO_DHCP_AGENT_OPTIONS)) + priority_list [priority_len++] = + op -> option -> code; + } + } + + /* Now go through all the universes for which options + were set and see if there are encapsulations for + them; if there are, put the encapsulation options + on the priority list as well. */ + for (i = 0; i < cfg_options -> universe_count; i++) { + if (cfg_options -> universes [i] && + universes [i] -> enc_opt && + priority_len < PRIORITY_COUNT && + universes [i] -> enc_opt -> universe == &dhcp_universe) + { + if (universes [i] -> enc_opt -> code != + DHO_DHCP_AGENT_OPTIONS) + priority_list [priority_len++] = + universes [i] -> enc_opt -> code; + } + } + + /* The vendor option space can't stand on its own, so always + add it to the list. */ + if (priority_len < PRIORITY_COUNT) + priority_list [priority_len++] = + DHO_VENDOR_ENCAPSULATED_OPTIONS; } /* Copy the options into the big buffer... */ @@ -249,11 +604,13 @@ int cons_options (inpacket, outpacket, mms, (main_buffer_size - 7 + ((overload & 1) ? DHCP_FILE_LEN : 0) + ((overload & 2) ? DHCP_SNAME_LEN : 0)), - options, priority_list, priority_len, + inpacket, lease, client_state, + in_options, cfg_options, scope, + priority_list, priority_len, main_buffer_size, (main_buffer_size + ((overload & 1) ? DHCP_FILE_LEN : 0)), - terminate); + terminate, vuname); /* Put the cookie up front... */ memcpy (outpacket -> options, DHCP_OPTIONS_COOKIE, 4); @@ -267,13 +624,12 @@ int cons_options (inpacket, outpacket, mms, memcpy (&outpacket -> options [mainbufix], buffer, option_size); mainbufix += option_size; + agentix = mainbufix; if (mainbufix < main_buffer_size) - outpacket -> options [mainbufix++] - = DHO_END; + need_endopt = 1; length = DHCP_FIXED_NON_UDP + mainbufix; } else { - outpacket -> options [mainbufix++] = - DHO_DHCP_OPTION_OVERLOAD; + 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; @@ -282,8 +638,10 @@ int cons_options (inpacket, outpacket, mms, memcpy (&outpacket -> options [mainbufix], buffer, main_buffer_size - mainbufix); + length = DHCP_FIXED_NON_UDP + main_buffer_size; + agentix = main_buffer_size; + bufix = main_buffer_size - mainbufix; - length = DHCP_FIXED_NON_UDP + mainbufix; if (overload & 1) { if (option_size - bufix <= DHCP_FILE_LEN) { memcpy (outpacket -> file, @@ -314,220 +672,400 @@ int cons_options (inpacket, outpacket, mms, = DHO_PAD; } } + + /* Now hack in the agent options if there are any. */ + priority_list [0] = DHO_DHCP_AGENT_OPTIONS; + priority_len = 1; + agentix += + store_options (&outpacket -> options [agentix], + 1500 - DHCP_FIXED_LEN - agentix, + inpacket, lease, client_state, + in_options, cfg_options, scope, + priority_list, priority_len, + 1500 - DHCP_FIXED_LEN - agentix, + 1500 - DHCP_FIXED_LEN - agentix, 0, (char *)0); + + /* Tack a DHO_END option onto the packet if we need to. */ + if (agentix < 1500 - DHCP_FIXED_LEN && need_endopt) + outpacket -> options [agentix++] = DHO_END; + + /* Figure out the length. */ + length = DHCP_FIXED_NON_UDP + agentix; 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) +int store_options (buffer, buflen, packet, lease, client_state, + in_options, cfg_options, scope, priority_list, priority_len, + first_cutoff, second_cutoff, terminate, vuname) unsigned char *buffer; - int buflen; - struct tree_cache **options; - unsigned char *priority_list; + unsigned buflen; + struct packet *packet; + struct lease *lease; + struct client_state *client_state; + struct option_state *in_options; + struct option_state *cfg_options; + struct binding_scope **scope; + unsigned *priority_list; int priority_len; - int first_cutoff, second_cutoff; + unsigned first_cutoff, second_cutoff; int terminate; + const char *vuname; { int bufix = 0; - int option_stored [256]; int i; int ix; int tto; + struct data_string od; + struct option_cache *oc; + unsigned code; + int optstart; + + memset (&od, 0, sizeof od); - /* Zero out the stored-lengths array. */ - memset (option_stored, 0, sizeof option_stored); + /* Eliminate duplicate options in the parameter request list. + There's got to be some clever knuthian way to do this: + Eliminate all but the first occurance of a value in an array + of values without otherwise disturbing the order of the array. */ + for (i = 0; i < priority_len - 1; i++) { + tto = 0; + for (ix = i + 1; ix < priority_len + tto; ix++) { + if (tto) + priority_list [ix - tto] = + priority_list [ix]; + if (priority_list [i] == priority_list [ix]) { + tto++; + priority_len--; + } + } + } /* 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). */ + unsigned length; + int optstart; + struct universe *u; + int have_encapsulation = 0; + struct data_string encapsulation; - /* Number of bytes left to store (some may already - have been stored by a previous pass). */ - int length; + memset (&encapsulation, 0, sizeof encapsulation); - /* If no data is available for this option, skip it. */ - if (!options [code]) { - continue; - } + /* Code for next option to try to store. */ + code = priority_list [i]; + + /* Look up the option in the site option space if the code + is above the cutoff, otherwise in the DHCP option space. */ + if (code >= cfg_options -> site_code_min) + u = universes [cfg_options -> site_universe]; + else + u = &dhcp_universe; - /* 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; + oc = lookup_option (u, cfg_options, code); - /* Find the value of the option... */ - if (!tree_evaluate (options [code])) { - continue; - } + /* It's an encapsulation, try to find the universe + to be encapsulated first, except that if it's a straight + encapsulation and the user has provided a value for the + encapsulation option, use the user-provided value. */ + if ((u -> options [code] -> format [0] == 'E' && !oc) || + u -> options [code] -> format [0] == 'e') { + int uix; + static char *s, *t; + struct option_cache *tmp; + struct data_string name; - /* We should now have a constant length for the option. */ - length = options [code] -> len; + s = strchr (u -> options [code] -> format, 'E'); + if (s) + t = strchr (++s, '.'); + if (s && t) { + memset (&name, 0, sizeof name); - /* Do we add a NUL? */ - if (terminate && dhcp_options [code].format [0] == 't') { - length++; - tto = 1; - } else { - tto = 0; + /* A zero-length universe name means the vendor + option space, if one is defined. */ + if (t == s) { + if (vendor_cfg_option) { + tmp = lookup_option (vendor_cfg_option -> universe, + cfg_options, + vendor_cfg_option -> code); + if (tmp) + evaluate_option_cache (&name, packet, lease, + client_state, + in_options, + cfg_options, + scope, tmp, MDL); + } else if (vuname) { + name.data = (unsigned char *)s; + name.len = strlen (s); + } + } else { + name.data = (unsigned char *)s; + name.len = t - s; + } + + /* If we found a universe, and there are options configured + for that universe, try to encapsulate it. */ + if (name.len) { + have_encapsulation = + (option_space_encapsulate + (&encapsulation, packet, lease, client_state, + in_options, cfg_options, scope, &name)); + data_string_forget (&name, MDL); + } } + } - /* 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. */ + /* In order to avoid memory leaks, we have to get to here + with any option cache that we allocated in tmp not being + referenced by tmp, and whatever option cache is referenced + by oc being an actual reference. lookup_option doesn't + generate a reference (this needs to be fixed), so the + preceding goop ensures that if we *didn't* generate a new + option cache, oc still winds up holding an actual reference. */ - ix = 0; + /* If no data is available for this option, skip it. */ + if (!oc && !have_encapsulation) { + continue; + } + + /* Find the value of the option... */ + if (oc) { + evaluate_option_cache (&od, packet, + lease, client_state, in_options, + cfg_options, scope, oc, MDL); + if (!od.len) { + data_string_forget (&encapsulation, MDL); + data_string_forget (&od, MDL); + have_encapsulation = 0; + continue; + } + } - optstart = bufix; - while (length) { - unsigned char incr = length > 255 ? 255 : length; + /* We should now have a constant length for the option. */ + length = od.len; + if (have_encapsulation) { + length += encapsulation.len; + if (!od.len) { + data_string_copy (&od, &encapsulation, MDL); + data_string_forget (&encapsulation, MDL); + } else { + struct buffer *bp = (struct buffer *)0; + if (!buffer_allocate (&bp, length, MDL)) { + option_cache_dereference (&oc, MDL); + data_string_forget (&od, MDL); + data_string_forget (&encapsulation, MDL); + continue; + } + memcpy (&bp -> data [0], od.data, od.len); + memcpy (&bp -> data [od.len], encapsulation.data, + encapsulation.len); + data_string_forget (&od, MDL); + data_string_forget (&encapsulation, MDL); + od.data = &bp -> data [0]; + buffer_reference (&od.buffer, bp, MDL); + buffer_dereference (&bp, MDL); + od.len = length; + od.terminated = 0; + } + } - /* 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; + /* Do we add a NUL? */ + if (terminate && dhcp_options [code].format [0] == 't') { + length++; + tto = 1; + } else { + tto = 0; + } - /* If this option is going to overflow the buffer, - skip it. */ - if (bufix + 2 + incr > buflen) { - bufix = optstart; - break; - } + /* 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. */ - /* 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; - } + ix = 0; + optstart = bufix; + while (length) { + unsigned char incr = length > 255 ? 255 : length; + int consumed = 0; + + /* 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, + od.data + ix, (unsigned)(incr - 1)); + buffer [bufix + 2 + incr - 1] = 0; + } else { + memcpy (buffer + bufix + 2, + od.data + ix, (unsigned)incr); + } + length -= incr; + ix += incr; + bufix += 2 + incr; + } + data_string_forget (&od, MDL); } + 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; +const char *pretty_print_option (option, data, len, emit_commas, emit_quotes) + struct option *option; + const unsigned char *data; + unsigned len; int emit_commas; int emit_quotes; { static char optbuf [32768]; /* XXX */ int hunksize = 0; + int opthunk = 0; + int hunkinc = 0; int numhunk = -1; int numelem = 0; char fmtbuf [32]; - int i, j, k; + struct enumeration *enumbuf [32]; + int i, j, k, l; char *op = optbuf; - unsigned char *dp = data; + const 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); + unsigned long tval; if (emit_commas) comma = ','; else comma = ' '; + memset (enumbuf, 0, sizeof enumbuf); + /* Figure out the size of the data. */ - for (i = 0; dhcp_options [code].format [i]; i++) { + for (l = i = 0; option -> format [i]; i++, l++) { if (!numhunk) { - warn ("%s: Excess information in format string: %s\n", - dhcp_options [code].name, - &(dhcp_options [code].format [i])); + log_error ("%s: Extra codes in format string: %s", + option -> name, + &(option -> format [i])); break; } numelem++; - fmtbuf [i] = dhcp_options [code].format [i]; - switch (dhcp_options [code].format [i]) { + fmtbuf [l] = option -> format [i]; + switch (option -> format [i]) { + case 'a': + --numelem; + fmtbuf [l] = 0; + numhunk = 0; + break; case 'A': --numelem; - fmtbuf [i] = 0; + fmtbuf [l] = 0; numhunk = 0; break; + case 'E': + /* Skip the universe name. */ + while (option -> format [i] && + option -> format [i] != '.') + i++; case 'X': for (k = 0; k < len; k++) { if (!isascii (data [k]) || !isprint (data [k])) break; } - if (k == len) { - fmtbuf [i] = 't'; + /* If we found no bogus characters, or the bogus + character we found is a trailing NUL, it's + okay to print this option as text. */ + if (k == len || (k + 1 == len && data [k] == 0)) { + fmtbuf [l] = 't'; numhunk = -2; } else { - fmtbuf [i] = 'x'; + fmtbuf [l] = 'x'; hunksize++; comma = ':'; numhunk = 0; } - fmtbuf [i + 1] = 0; + fmtbuf [l + 1] = 0; break; + case 'd': case 't': - fmtbuf [i] = 't'; - fmtbuf [i + 1] = 0; + fmtbuf [l] = 't'; + fmtbuf [l + 1] = 0; numhunk = -2; break; + case 'N': + k = i; + while (option -> format [i] && + option -> format [i] != '.') + i++; + enumbuf [l] = + find_enumeration (&option -> format [k] + 1, + i - k - 1); + hunksize += 1; + hunkinc = 1; + break; case 'I': case 'l': case 'L': + case 'T': hunksize += 4; + hunkinc = 4; break; case 's': case 'S': hunksize += 2; + hunkinc = 2; break; case 'b': case 'B': case 'f': hunksize++; + hunkinc = 1; break; case 'e': break; + case 'o': + opthunk += hunkinc; + break; default: - warn ("%s: garbage in format string: %s\n", - dhcp_options [code].name, - &(dhcp_options [code].format [i])); + log_error ("%s: garbage in format string: %s", + option -> name, + &(option -> format [i])); break; } } /* Check for too few bytes... */ - if (hunksize > len) { - warn ("%s: expecting at least %d bytes; got %d", - dhcp_options [code].name, + if (hunksize - opthunk > len) { + log_error ("%s: expecting at least %d bytes; got %d", + option -> name, hunksize, len); return "<error>"; } /* Check for too many bytes... */ if (numhunk == -1 && hunksize < len) - warn ("%s: %d extra bytes", - dhcp_options [code].name, + log_error ("%s: %d extra bytes", + option -> name, len - hunksize); /* If this is an array, compute its size. */ @@ -535,8 +1073,8 @@ char *pretty_print_option (code, data, len, emit_commas, emit_quotes) 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, + log_error ("%s: %d extra bytes at end of array\n", + option -> name, len - numhunk * hunksize); /* A one-hunk array prints the same as a single hunk. */ @@ -574,6 +1112,21 @@ char *pretty_print_option (code, data, len, emit_commas, emit_quotes) *op++ = '"'; *op = 0; break; + /* pretty-printing an array of enums is + going to get ugly. */ + case 'N': + if (!enumbuf [j]) + goto enum_as_num; + for (i = 0; ;i++) { + if (!enumbuf [j] -> values [i].name) + goto enum_as_num; + if (enumbuf [j] -> values [i].value == + *dp) + break; + } + strcpy (op, enumbuf [j] -> values [i].name); + op += strlen (op); + break; case 'I': foo.s_addr = htonl (getULong (dp)); strcpy (op, inet_ntoa (foo)); @@ -583,23 +1136,31 @@ char *pretty_print_option (code, data, len, emit_commas, emit_quotes) sprintf (op, "%ld", (long)getLong (dp)); dp += 4; break; + case 'T': + tval = getULong (dp); + if (tval == -1) + sprintf (op, "%s", "infinite"); + else + sprintf (op, "%ld", tval); + break; case 'L': sprintf (op, "%ld", (unsigned long)getULong (dp)); dp += 4; break; case 's': - sprintf (op, "%d", getShort (dp)); + sprintf (op, "%d", (int)getShort (dp)); dp += 2; break; case 'S': - sprintf (op, "%d", getUShort (dp)); + sprintf (op, "%d", (unsigned)getUShort (dp)); dp += 2; break; case 'b': - sprintf (op, "%d", *(char *)dp++); + sprintf (op, "%d", *(const char *)dp++); break; case 'B': + enum_as_num: sprintf (op, "%d", *dp++); break; case 'x': @@ -609,58 +1170,1051 @@ char *pretty_print_option (code, data, len, emit_commas, emit_quotes) strcpy (op, *dp++ ? "true" : "false"); break; default: - warn ("Unexpected format code %c", fmtbuf [j]); + log_error ("Unexpected format code %c", + fmtbuf [j]); } op += strlen (op); + if (dp == data + len) + break; if (j + 1 < numelem && comma != ':') *op++ = ' '; } if (i + 1 < numhunk) { *op++ = comma; } - + if (dp == data + len) + break; } return optbuf; } +int get_option (result, universe, packet, lease, client_state, + in_options, cfg_options, options, scope, code, file, line) + struct data_string *result; + struct universe *universe; + struct packet *packet; + struct lease *lease; + struct client_state *client_state; + struct option_state *in_options; + struct option_state *cfg_options; + struct option_state *options; + struct binding_scope **scope; + unsigned code; + const char *file; + int line; +{ + struct option_cache *oc; + + if (!universe -> lookup_func) + return 0; + oc = ((*universe -> lookup_func) (universe, options, code)); + if (!oc) + return 0; + if (!evaluate_option_cache (result, packet, lease, client_state, + in_options, cfg_options, scope, oc, + file, line)) + return 0; + return 1; +} + +void set_option (universe, options, option, op) + struct universe *universe; + struct option_state *options; + struct option_cache *option; + enum statement_op op; +{ + struct option_cache *oc, *noc; + + switch (op) { + case if_statement: + case add_statement: + case eval_statement: + case break_statement: + default: + log_error ("bogus statement type in do_option_set."); + break; + + case default_option_statement: + oc = lookup_option (universe, options, + option -> option -> code); + if (oc) + break; + save_option (universe, options, option); + break; + + case supersede_option_statement: + case send_option_statement: + /* Install the option, replacing any existing version. */ + save_option (universe, options, option); + break; + + case append_option_statement: + case prepend_option_statement: + oc = lookup_option (universe, options, + option -> option -> code); + if (!oc) { + save_option (universe, options, option); + break; + } + /* If it's not an expression, make it into one. */ + if (!oc -> expression && oc -> data.len) { + if (!expression_allocate (&oc -> expression, MDL)) { + log_error ("Can't allocate const expression."); + break; + } + oc -> expression -> op = expr_const_data; + data_string_copy + (&oc -> expression -> data.const_data, + &oc -> data, MDL); + data_string_forget (&oc -> data, MDL); + } + noc = (struct option_cache *)0; + if (!option_cache_allocate (&noc, MDL)) + break; + if (op == append_option_statement) { + if (!make_concat (&noc -> expression, + oc -> expression, + option -> expression)) { + option_cache_dereference (&noc, MDL); + break; + } + } else { + if (!make_concat (&noc -> expression, + option -> expression, + oc -> expression)) { + option_cache_dereference (&noc, MDL); + break; + } + } + noc -> option = oc -> option; + save_option (universe, options, noc); + option_cache_dereference (&noc, MDL); + break; + } +} + +struct option_cache *lookup_option (universe, options, code) + struct universe *universe; + struct option_state *options; + unsigned code; +{ + if (!options) + return (struct option_cache *)0; + if (universe -> lookup_func) + return (*universe -> lookup_func) (universe, options, code); + else + log_error ("can't look up options in %s space.", + universe -> name); + return (struct option_cache *)0; +} + +struct option_cache *lookup_hashed_option (universe, options, code) + struct universe *universe; + struct option_state *options; + unsigned code; +{ + int hashix; + pair bptr; + pair *hash; + + /* Make sure there's a hash table. */ + if (universe -> index >= options -> universe_count || + !(options -> universes [universe -> index])) + return (struct option_cache *)0; + + hash = options -> universes [universe -> index]; + + hashix = compute_option_hash (code); + for (bptr = hash [hashix]; bptr; bptr = bptr -> cdr) { + if (((struct option_cache *)(bptr -> car)) -> option -> code == + code) + return (struct option_cache *)(bptr -> car); + } + return (struct option_cache *)0; +} + +int save_option_buffer (struct universe *universe, + struct option_state *options, + struct buffer *bp, + unsigned char *buffer, unsigned length, + struct option *option, int tp) +{ + struct buffer *lbp = (struct buffer *)0; + struct option_cache *op = (struct option_cache *)0; + + if (!option_cache_allocate (&op, MDL)) { + log_error ("No memory for option %s.%s.", + universe -> name, + option -> name); + return 0; + } + + /* If we weren't passed a buffer in which the data are saved and + refcounted, allocate one now. */ + if (!bp) { + if (!buffer_allocate (&lbp, length, MDL)) { + log_error ("no memory for option buffer."); + + option_cache_dereference (&op, MDL); + return 0; + } + memcpy (lbp -> data, buffer, length + tp); + bp = lbp; + buffer = &bp -> data [0]; /* Refer to saved buffer. */ + } + + /* Reference buffer copy to option cache. */ + op -> data.buffer = (struct buffer *)0; + buffer_reference (&op -> data.buffer, bp, MDL); + + /* Point option cache into buffer. */ + op -> data.data = buffer; + op -> data.len = length; + + if (tp) { + /* NUL terminate (we can get away with this because we (or + the caller!) allocated one more than the buffer size, and + because the byte following the end of an option is always + the code of the next option, which the caller is getting + out of the *original* buffer. */ + buffer [length] = 0; + op -> data.terminated = 1; + } else + op -> data.terminated = 0; + + op -> option = option; + + /* Now store the option. */ + save_option (universe, options, op); + + /* And let go of our reference. */ + option_cache_dereference (&op, MDL); + + return 1; +} + +void save_option (struct universe *universe, + struct option_state *options, struct option_cache *oc) +{ + if (universe -> save_func) + (*universe -> save_func) (universe, options, oc); + else + log_error ("can't store options in %s space.", + universe -> name); +} + +void save_hashed_option (universe, options, oc) + struct universe *universe; + struct option_state *options; + struct option_cache *oc; +{ + int hashix; + pair bptr; + pair *hash = options -> universes [universe -> index]; + + if (oc -> refcnt == 0) + abort (); + + /* Compute the hash. */ + hashix = compute_option_hash (oc -> option -> code); + + /* If there's no hash table, make one. */ + if (!hash) { + hash = (pair *)dmalloc (OPTION_HASH_SIZE * sizeof *hash, MDL); + if (!hash) { + log_error ("no memory to store %s.%s", + universe -> name, oc -> option -> name); + return; + } + memset (hash, 0, OPTION_HASH_SIZE * sizeof *hash); + options -> universes [universe -> index] = (VOIDPTR)hash; + } else { + /* Try to find an existing option matching the new one. */ + for (bptr = hash [hashix]; bptr; bptr = bptr -> cdr) { + if (((struct option_cache *) + (bptr -> car)) -> option -> code == + oc -> option -> code) + break; + } + + /* If we find one, dereference it and put the new one + in its place. */ + if (bptr) { + option_cache_dereference + ((struct option_cache **)&bptr -> car, MDL); + option_cache_reference + ((struct option_cache **)&bptr -> car, + oc, MDL); + return; + } + } + + /* Otherwise, just put the new one at the head of the list. */ + bptr = new_pair (MDL); + if (!bptr) { + log_error ("No memory for option_cache reference."); + return; + } + bptr -> cdr = hash [hashix]; + bptr -> car = 0; + option_cache_reference ((struct option_cache **)&bptr -> car, oc, MDL); + hash [hashix] = bptr; +} + +void delete_option (universe, options, code) + struct universe *universe; + struct option_state *options; + int code; +{ + if (universe -> delete_func) + (*universe -> delete_func) (universe, options, code); + else + log_error ("can't delete options from %s space.", + universe -> name); +} + +void delete_hashed_option (universe, options, code) + struct universe *universe; + struct option_state *options; + int code; +{ + int hashix; + pair bptr, prev = (pair)0; + pair *hash = options -> universes [universe -> index]; + + /* There may not be any options in this space. */ + if (!hash) + return; + + /* Try to find an existing option matching the new one. */ + hashix = compute_option_hash (code); + for (bptr = hash [hashix]; bptr; bptr = bptr -> cdr) { + if (((struct option_cache *)(bptr -> car)) -> option -> code + == code) + break; + prev = bptr; + } + /* If we found one, wipe it out... */ + if (bptr) { + if (prev) + prev -> cdr = bptr -> cdr; + else + hash [hashix] = bptr -> cdr; + option_cache_dereference + ((struct option_cache **)(&bptr -> car), MDL); + free_pair (bptr, MDL); + } +} + +extern struct option_cache *free_option_caches; /* XXX */ + +int option_cache_dereference (ptr, file, line) + struct option_cache **ptr; + const char *file; + int line; +{ + if (!ptr || !*ptr) { + log_error ("Null pointer in option_cache_dereference: %s(%d)", + file, line); +#if defined (POINTER_DEBUG) + abort (); +#else + return 0; +#endif + } + + (*ptr) -> refcnt--; + rc_register (file, line, ptr, *ptr, (*ptr) -> refcnt, 1, RC_MISC); + if (!(*ptr) -> refcnt) { + if ((*ptr) -> data.buffer) + data_string_forget (&(*ptr) -> data, file, line); + if ((*ptr) -> expression) + expression_dereference (&(*ptr) -> expression, + file, line); + if ((*ptr) -> next) + option_cache_dereference (&((*ptr) -> next), + file, line); + /* Put it back on the free list... */ + (*ptr) -> expression = (struct expression *)free_option_caches; + free_option_caches = *ptr; + dmalloc_reuse (free_option_caches, (char *)0, 0, 0); + } + if ((*ptr) -> refcnt < 0) { + log_error ("%s(%d): negative refcnt!", file, line); +#if defined (DEBUG_RC_HISTORY) + dump_rc_history (*ptr); +#endif +#if defined (POINTER_DEBUG) + abort (); +#else + *ptr = (struct option_cache *)0; + return 0; +#endif + } + *ptr = (struct option_cache *)0; + return 1; + +} + +int hashed_option_state_dereference (universe, state, file, line) + struct universe *universe; + struct option_state *state; + const char *file; + int line; +{ + pair *heads; + pair cp, next; + int i; + + /* Get the pointer to the array of hash table bucket heads. */ + heads = (pair *)(state -> universes [universe -> index]); + if (!heads) + return 0; + + /* For each non-null head, loop through all the buckets dereferencing + the attached option cache structures and freeing the buckets. */ + for (i = 0; i < OPTION_HASH_SIZE; i++) { + for (cp = heads [i]; cp; cp = next) { + next = cp -> cdr; + option_cache_dereference + ((struct option_cache **)&cp -> car, + file, line); + free_pair (cp, file, line); + } + } + + dfree (heads, file, line); + state -> universes [universe -> index] = (void *)0; + return 1; +} + +int store_option (result, universe, packet, lease, client_state, + in_options, cfg_options, scope, oc) + struct data_string *result; + struct universe *universe; + struct packet *packet; + struct lease *lease; + struct client_state *client_state; + struct option_state *in_options; + struct option_state *cfg_options; + struct binding_scope **scope; + struct option_cache *oc; +{ + struct data_string d1, d2; + + memset (&d1, 0, sizeof d1); + memset (&d2, 0, sizeof d2); + + if (evaluate_option_cache (&d2, packet, lease, client_state, + in_options, cfg_options, scope, oc, MDL)) { + if (!buffer_allocate (&d1.buffer, + (result -> len + + universe -> length_size + + universe -> tag_size + d2.len), MDL)) { + data_string_forget (result, MDL); + data_string_forget (&d2, MDL); + return 0; + } + d1.data = &d1.buffer -> data [0]; + if (result -> len) + memcpy (d1.buffer -> data, + result -> data, result -> len); + d1.len = result -> len; + (*universe -> store_tag) (&d1.buffer -> data [d1.len], + oc -> option -> code); + d1.len += universe -> tag_size; + (*universe -> store_length) (&d1.buffer -> data [d1.len], + d2.len); + d1.len += universe -> length_size; + memcpy (&d1.buffer -> data [d1.len], d2.data, d2.len); + d1.len += d2.len; + data_string_forget (&d2, MDL); + data_string_forget (result, MDL); + data_string_copy (result, &d1, MDL); + data_string_forget (&d1, MDL); + return 1; + } + return 0; +} + +int option_space_encapsulate (result, packet, lease, client_state, + in_options, cfg_options, scope, name) + struct data_string *result; + struct packet *packet; + struct lease *lease; + struct client_state *client_state; + struct option_state *in_options; + struct option_state *cfg_options; + struct binding_scope **scope; + struct data_string *name; +{ + struct universe *u; + + u = (struct universe *)0; + universe_hash_lookup (&u, universe_hash, + (const char *)name -> data, name -> len, MDL); + if (!u) + return 0; + + if (u -> encapsulate) + return (*u -> encapsulate) (result, packet, lease, + client_state, + in_options, cfg_options, scope, u); + log_error ("encapsulation requested for %s with no support.", + name -> data); + return 0; +} + +int hashed_option_space_encapsulate (result, packet, lease, client_state, + in_options, cfg_options, scope, universe) + struct data_string *result; + struct packet *packet; + struct lease *lease; + struct client_state *client_state; + struct option_state *in_options; + struct option_state *cfg_options; + struct binding_scope **scope; + struct universe *universe; +{ + pair p, *hash; + int status; + int i; + + if (universe -> index >= cfg_options -> universe_count) + return 0; + + hash = cfg_options -> universes [universe -> index]; + if (!hash) + return 0; + + status = 0; + for (i = 0; i < OPTION_HASH_SIZE; i++) { + for (p = hash [i]; p; p = p -> cdr) { + if (store_option (result, universe, packet, + lease, client_state, in_options, + cfg_options, scope, + (struct option_cache *)p -> car)) + status = 1; + } + } + + return status; +} + +int nwip_option_space_encapsulate (result, packet, lease, client_state, + in_options, cfg_options, scope, universe) + struct data_string *result; + struct packet *packet; + struct lease *lease; + struct client_state *client_state; + struct option_state *in_options; + struct option_state *cfg_options; + struct binding_scope **scope; + struct universe *universe; +{ + pair ocp; + int status; + int i; + static struct option_cache *no_nwip; + struct data_string ds; + struct option_chain_head *head; + + if (universe -> index >= cfg_options -> universe_count) + return 0; + head = ((struct option_chain_head *) + cfg_options -> universes [fqdn_universe.index]); + if (!head) + return 0; + + status = 0; + for (ocp = head -> first; ocp; ocp = ocp -> cdr) { + struct option_cache *oc = (struct option_cache *)(ocp -> car); + if (store_option (result, universe, packet, + lease, client_state, in_options, + cfg_options, scope, + (struct option_cache *)ocp -> car)) + status = 1; + } + + /* If there's no data, the nwip suboption is supposed to contain + a suboption saying there's no data. */ + if (!status) { + if (!no_nwip) { + static unsigned char nni [] = { 1, 0 }; + memset (&ds, 0, sizeof ds); + ds.data = nni; + ds.len = 2; + if (option_cache_allocate (&no_nwip, MDL)) + data_string_copy (&no_nwip -> data, &ds, MDL); + no_nwip -> option = nwip_universe.options [1]; + } + if (no_nwip) { + if (store_option (result, universe, packet, lease, + client_state, in_options, + cfg_options, scope, no_nwip)) + status = 1; + } + } else { + memset (&ds, 0, sizeof ds); + + /* If we have nwip options, the first one has to be the + nwip-exists-in-option-area option. */ + if (!buffer_allocate (&ds.buffer, result -> len + 2, MDL)) { + data_string_forget (result, MDL); + return 0; + } + ds.data = &ds.buffer -> data [0]; + ds.buffer -> data [0] = 2; + ds.buffer -> data [1] = 0; + memcpy (&ds.buffer -> data [2], result -> data, result -> len); + data_string_forget (result, MDL); + data_string_copy (result, &ds, MDL); + data_string_forget (&ds, MDL); + } + + return status; +} + +int fqdn_option_space_encapsulate (result, packet, lease, client_state, + in_options, cfg_options, scope, universe) + struct data_string *result; + struct packet *packet; + struct lease *lease; + struct client_state *client_state; + struct option_state *in_options; + struct option_state *cfg_options; + struct binding_scope **scope; + struct universe *universe; +{ + pair ocp; + struct data_string results [FQDN_SUBOPTION_COUNT + 1]; + unsigned i; + unsigned len; + struct buffer *bp = (struct buffer *)0; + struct option_chain_head *head; + + /* If there's no FQDN universe, don't encapsulate. */ + if (fqdn_universe.index >= cfg_options -> universe_count) + return 0; + head = ((struct option_chain_head *) + cfg_options -> universes [fqdn_universe.index]); + if (!head) + return 0; + + /* Figure out the values of all the suboptions. */ + memset (results, 0, sizeof results); + for (ocp = head -> first; ocp; ocp = ocp -> cdr) { + struct option_cache *oc = (struct option_cache *)(ocp -> car); + if (oc -> option -> code > FQDN_SUBOPTION_COUNT) + continue; + evaluate_option_cache (&results [oc -> option -> code], + packet, lease, client_state, in_options, + cfg_options, scope, oc, MDL); + } + len = 4 + results [FQDN_FQDN].len; + /* Save the contents of the option in a buffer. */ + if (!buffer_allocate (&bp, len, MDL)) { + log_error ("no memory for option buffer."); + return 0; + } + buffer_reference (&result -> buffer, bp, MDL); + result -> len = 3; + result -> data = &bp -> data [0]; + + memset (&bp -> data [0], 0, len); + if (results [FQDN_NO_CLIENT_UPDATE].len && + results [FQDN_NO_CLIENT_UPDATE].data [0]) + bp -> data [0] |= 2; + if (results [FQDN_SERVER_UPDATE].len && + results [FQDN_SERVER_UPDATE].data [0]) + bp -> data [0] |= 1; + if (results [FQDN_RCODE1].len) + bp -> data [1] = results [FQDN_RCODE1].data [0]; + if (results [FQDN_RCODE2].len) + bp -> data [2] = results [FQDN_RCODE2].data [0]; + + if (results [FQDN_ENCODED].len && + results [FQDN_ENCODED].data [0]) { + unsigned char *out; + int i; + bp -> data [0] |= 4; + out = &bp -> data [3]; + if (results [FQDN_FQDN].len) { + i = 0; + while (i < results [FQDN_FQDN].len) { + int j; + for (j = i; ('.' != + results [FQDN_FQDN].data [j]) && + j < results [FQDN_FQDN].len; j++) + ; + *out++ = j - i; + memcpy (out, &results [FQDN_FQDN].data [i], + (unsigned)(j - i)); + out += j - i; + i = j; + if (results [FQDN_FQDN].data [j] == '.') + i++; + } + if ((results [FQDN_FQDN].data + [results [FQDN_FQDN].len - 1] == '.')) + *out++ = 0; + result -> len = out - result -> data; + result -> terminated = 0; + } + } else { + if (results [FQDN_FQDN].len) { + memcpy (&bp -> data [3], results [FQDN_FQDN].data, + results [FQDN_FQDN].len); + result -> len += results [FQDN_FQDN].len; + result -> terminated = 0; + } + } + for (i = 1; i <= FQDN_SUBOPTION_COUNT; i++) { + if (results [i].len) + data_string_forget (&results [i], MDL); + } + buffer_dereference (&bp, MDL); + return 1; +} + +void option_space_foreach (struct packet *packet, struct lease *lease, + struct client_state *client_state, + struct option_state *in_options, + struct option_state *cfg_options, + struct binding_scope **scope, + struct universe *u, void *stuff, + void (*func) (struct option_cache *, + struct packet *, + struct lease *, struct client_state *, + struct option_state *, + struct option_state *, + struct binding_scope **, + struct universe *, void *)) +{ + if (u -> foreach) + (*u -> foreach) (packet, lease, client_state, in_options, + cfg_options, scope, u, stuff, func); +} + +void suboption_foreach (struct packet *packet, struct lease *lease, + struct client_state *client_state, + struct option_state *in_options, + struct option_state *cfg_options, + struct binding_scope **scope, + struct universe *u, void *stuff, + void (*func) (struct option_cache *, + struct packet *, + struct lease *, struct client_state *, + struct option_state *, + struct option_state *, + struct binding_scope **, + struct universe *, void *), + struct option_cache *oc, + const char *vsname) +{ + struct universe *universe = find_option_universe (oc -> option, + vsname); + int i; + + if (universe -> foreach) + (*universe -> foreach) (packet, lease, client_state, + in_options, cfg_options, + scope, universe, stuff, func); +} + +void hashed_option_space_foreach (struct packet *packet, struct lease *lease, + struct client_state *client_state, + struct option_state *in_options, + struct option_state *cfg_options, + struct binding_scope **scope, + struct universe *u, void *stuff, + void (*func) (struct option_cache *, + struct packet *, + struct lease *, + struct client_state *, + struct option_state *, + struct option_state *, + struct binding_scope **, + struct universe *, void *)) +{ + pair *hash; + int i; + struct option_cache *oc; + + if (cfg_options -> universe_count <= u -> index) + return; + + hash = cfg_options -> universes [u -> index]; + if (!hash) + return; + for (i = 0; i < OPTION_HASH_SIZE; i++) { + pair p; + /* XXX save _all_ options! XXX */ + for (p = hash [i]; p; p = p -> cdr) { + oc = (struct option_cache *)p -> car; + (*func) (oc, packet, lease, client_state, + in_options, cfg_options, scope, u, stuff); + } + } +} + +void save_linked_option (universe, options, oc) + struct universe *universe; + struct option_state *options; + struct option_cache *oc; +{ + pair *tail; + pair np = (pair )0; + struct option_chain_head *head; + + if (universe -> index >= options -> universe_count) + return; + head = ((struct option_chain_head *) + options -> universes [universe -> index]); + if (!head) { + if (!option_chain_head_allocate (((struct option_chain_head **) + &options -> universes + [universe -> index]), MDL)) + return; + head = ((struct option_chain_head *) + options -> universes [universe -> index]); + } + + /* Find the tail of the list. */ + for (tail = &head -> first; *tail; tail = &((*tail) -> cdr)) { + if (oc -> option == + ((struct option_cache *)((*tail) -> car)) -> option) { + option_cache_dereference ((struct option_cache **) + (&(*tail) -> car), MDL); + option_cache_reference ((struct option_cache **) + (&(*tail) -> car), oc, MDL); + return; + } + } + + *tail = cons (0, 0); + if (*tail) { + option_cache_reference ((struct option_cache **) + (&(*tail) -> car), oc, MDL); + } +} + +int linked_option_space_encapsulate (result, packet, lease, client_state, + in_options, cfg_options, scope, universe) + struct data_string *result; + struct packet *packet; + struct lease *lease; + struct client_state *client_state; + struct option_state *in_options; + struct option_state *cfg_options; + struct binding_scope **scope; + struct universe *universe; +{ + int status; + pair oc; + struct option_chain_head *head; + + if (universe -> index >= cfg_options -> universe_count) + return 0; + head = ((struct option_chain_head *) + cfg_options -> universes [universe -> index]); + if (!head) + return 0; + + status = 0; + for (oc = head -> first; oc; oc = oc -> cdr) { + if (store_option (result, universe, packet, + lease, client_state, in_options, cfg_options, + scope, (struct option_cache *)(oc -> car))) + status = 1; + } + + return status; +} + +void delete_linked_option (universe, options, code) + struct universe *universe; + struct option_state *options; + int code; +{ + pair *tail, tmp = (pair)0; + struct option_chain_head *head; + + if (universe -> index >= options -> universe_count) + return; + head = ((struct option_chain_head *) + options -> universes [universe -> index]); + if (!head) + return; + + for (tail = &head -> first; *tail; tail = &((*tail) -> cdr)) { + if (code == + ((struct option_cache *)(*tail) -> car) -> option -> code) + { + tmp = (*tail) -> cdr; + option_cache_dereference ((struct option_cache **) + (&(*tail) -> car), MDL); + dfree (*tail, MDL); + (*tail) = tmp; + break; + } + } +} + +struct option_cache *lookup_linked_option (universe, options, code) + struct universe *universe; + struct option_state *options; + unsigned code; +{ + pair oc; + struct option_chain_head *head; + + if (universe -> index >= options -> universe_count) + return 0; + head = ((struct option_chain_head *) + options -> universes [universe -> index]); + if (!head) + return 0; + + for (oc = head -> first; oc; oc = oc -> cdr) { + if (code == + ((struct option_cache *)(oc -> car)) -> option -> code) { + return (struct option_cache *)(oc -> car); + } + } + + return (struct option_cache *)0; +} + +int linked_option_state_dereference (universe, state, file, line) + struct universe *universe; + struct option_state *state; + const char *file; + int line; +{ + return (option_chain_head_dereference + ((struct option_chain_head **) + (&state -> universes [universe -> index]), MDL)); +} + +void linked_option_space_foreach (struct packet *packet, struct lease *lease, + struct client_state *client_state, + struct option_state *in_options, + struct option_state *cfg_options, + struct binding_scope **scope, + struct universe *u, void *stuff, + void (*func) (struct option_cache *, + struct packet *, + struct lease *, + struct client_state *, + struct option_state *, + struct option_state *, + struct binding_scope **, + struct universe *, void *)) +{ + pair car; + struct option_chain_head *head; + + if (u -> index >= cfg_options -> universe_count) + return; + head = ((struct option_chain_head *) + cfg_options -> universes [u -> index]); + if (!head) + return; + for (car = head -> first; car; car = car -> cdr) { + (*func) ((struct option_cache *)(car -> car), + packet, lease, client_state, + in_options, cfg_options, scope, u, stuff); + } +} + void do_packet (interface, packet, len, from_port, from, hfrom) struct interface_info *interface; struct dhcp_packet *packet; - int len; + unsigned len; unsigned int from_port; struct iaddr from; struct hardware *hfrom; { - struct packet tp; int i; + struct option_cache *op; + struct packet *decoded_packet; +#if defined (DEBUG_MEMORY_LEAKAGE) + unsigned long previous_outstanding = dmalloc_outstanding; +#endif + +#if defined (TRACING) + trace_inpacket_stash (interface, packet, len, from_port, from, hfrom); +#endif + decoded_packet = (struct packet *)0; + if (!packet_allocate (&decoded_packet, MDL)) { + log_error ("do_packet: no memory for incoming packet!"); + return; + } + decoded_packet -> raw = packet; + decoded_packet -> packet_length = len; + decoded_packet -> client_port = from_port; + decoded_packet -> client_addr = from; + interface_reference (&decoded_packet -> interface, interface, MDL); + decoded_packet -> haddr = hfrom; + if (packet -> hlen > sizeof packet -> chaddr) { - note ("Discarding packet with invalid hlen."); + packet_dereference (&decoded_packet, MDL); + log_info ("Discarding packet with bogus 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); + /* If there's an option buffer, try to parse it. */ + if (decoded_packet -> packet_length >= DHCP_FIXED_NON_UDP + 4) { + if (!parse_options (decoded_packet)) { + if (decoded_packet -> options) + option_state_dereference + (&decoded_packet -> options, MDL); + packet_dereference (&decoded_packet, MDL); + return; + } - /* Free the data associated with the options. */ - for (i = 0; i < 256; i++) { - if (tp.options [i].len && tp.options [i].data) - dfree (tp.options [i].data, "do_packet"); + if (decoded_packet -> options_valid && + (op = lookup_option (&dhcp_universe, + decoded_packet -> options, + DHO_DHCP_MESSAGE_TYPE))) { + struct data_string dp; + memset (&dp, 0, sizeof dp); + evaluate_option_cache (&dp, decoded_packet, + (struct lease *)0, + (struct client_state *)0, + decoded_packet -> options, + (struct option_state *)0, + (struct binding_scope **)0, + op, MDL); + if (dp.len > 0) + decoded_packet -> packet_type = dp.data [0]; + else + decoded_packet -> packet_type = 0; + data_string_forget (&dp, MDL); + } } + + if (decoded_packet -> packet_type) + dhcp (decoded_packet); + else + bootp (decoded_packet); + + /* If the caller kept the packet, they'll have upped the refcnt. */ + packet_dereference (&decoded_packet, MDL); + +#if defined (DEBUG_MEMORY_LEAKAGE) + log_info ("generation %ld: %ld new, %ld outstanding, %ld long-term", + dmalloc_generation, + dmalloc_outstanding - previous_outstanding, + dmalloc_outstanding, dmalloc_longterm); +#endif +#if defined (DEBUG_MEMORY_LEAKAGE) + dmalloc_dump_outstanding (); +#endif +#if defined (DEBUG_RC_HISTORY_EXHAUSTIVELY) + dump_rc_history (0); +#endif } |