/* $NetBSD: options.c,v 1.2.6.1 2024/02/29 11:39:17 martin Exp $ */ /* options.c DHCP options parsing and reassembly. */ /* * Copyright (C) 2004-2022 Internet Systems Consortium, Inc. ("ISC") * Copyright (c) 1995-2003 by Internet Software Consortium * * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. * * THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT * OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. * * Internet Systems Consortium, Inc. * PO Box 360 * Newmarket, NH 03857 USA * * https://www.isc.org/ * */ #include __RCSID("$NetBSD: options.c,v 1.2.6.1 2024/02/29 11:39:17 martin Exp $"); #define DHCP_OPTION_DATA #include "dhcpd.h" #include #include struct option *vendor_cfg_option; static int pretty_text(char **, char *, const unsigned char **, const unsigned char *, int); static int pretty_dname(char **, char *, const unsigned char *, const unsigned char *); static int pretty_domain(char **, char *, const unsigned char **, const unsigned char *); static int prepare_option_buffer(struct universe *universe, struct buffer *bp, unsigned char *buffer, unsigned length, unsigned code, int terminatep, struct option_cache **opp); /* Parse all available options out of the specified packet. */ /* Note, the caller is responsible for allocating packet->options. */ int parse_options (packet) struct packet *packet; { struct option_cache *op = NULL; /* 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 1; } /* Go through the options field, up to the end of the packet or the End field. */ if (!parse_option_buffer (packet -> options, &packet -> raw -> options [4], (packet -> packet_length - DHCP_FIXED_NON_UDP - 4), &dhcp_universe)) { /* STSN servers have a bug where they send a mangled domain-name option, and whatever is beyond that in the packet is junk. Microsoft clients accept this, which is probably why whoever implemented the STSN server isn't aware of the problem yet. To work around this, we will accept corrupt packets from the server if they contain a valid DHCP_MESSAGE_TYPE option, but will not accept any corrupt client packets (the ISC DHCP server is sufficiently widely used that it is probably beneficial for it to be picky) and will not accept packets whose type can't be determined. */ if ((op = lookup_option (&dhcp_universe, packet -> options, DHO_DHCP_MESSAGE_TYPE))) { if (!op -> data.data || (op -> data.data [0] != DHCPOFFER && op -> data.data [0] != DHCPACK && op -> data.data [0] != DHCPNAK)) return 0; } else return 0; } /* If we parsed a DHCP Option Overload option, parse more options out of the buffer(s) containing them. */ if ((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. */ int parse_option_buffer (options, buffer, length, universe) struct option_state *options; const unsigned char *buffer; unsigned length; struct universe *universe; { unsigned len, offset; unsigned code; struct option_cache *op = NULL, *nop = NULL; struct buffer *bp = NULL; struct option *option; char *reason = "general failure"; if (!buffer_allocate (&bp, length, MDL)) { log_error ("no memory for option buffer."); return 0; } memcpy (bp -> data, buffer, length); for (offset = 0; (offset + universe->tag_size) <= length && (code = universe->get_tag(buffer + offset)) != universe->end; ) { option = NULL; offset += universe->tag_size; /* Pad options don't have a length - just skip them. */ if (code == DHO_PAD) continue; /* Don't look for length if the buffer isn't that big. */ if ((offset + universe->length_size) > length) { reason = "code tag at end of buffer - missing " "length field"; goto bogus; } /* All other fields (except PAD and END handled above) * have a length field, unless it's a DHCPv6 zero-length * options space (eg any of the enterprise-id'd options). * * Zero-length-size option spaces basically consume the * entire options buffer, so have at it. */ if (universe->get_length != NULL) len = universe->get_length(buffer + offset); else if (universe->length_size == 0) len = length - universe->tag_size; else { log_fatal("Improperly configured option space(%s): " "may not have a nonzero length size " "AND a NULL get_length function.", universe->name); /* Silence compiler warnings. */ return 0; } offset += universe->length_size; if (!option_code_hash_lookup(&option, universe->code_hash, &code, 0, MDL)) { log_error("Can't find option with code %u", code); } /* If the length is outrageous, the options are bad. */ if (offset + len > length) { /* Avoid reference count overflow */ if (option) option_dereference(&option, MDL); reason = "option length exceeds option buffer length"; bogus: log_error("parse_option_buffer: malformed option " "%s.%s (code %u): %s.", universe->name, option ? option->name : "", code, reason); buffer_dereference (&bp, MDL); return 0; } /* If the option contains an encapsulation, parse it. In any case keep the raw data as well. (Previous to 4.4.0 we only kept the raw data if the parse failed, the option wasn't an encapsulation (by far the most common case), or the option wasn't entirely an encapsulation */ if (option && (option->format[0] == 'e' || option->format[0] == 'E')) { (void) parse_encapsulated_suboptions(options, option, bp->data + offset, len, universe, NULL); } if (universe == &dhcp_universe && code == DHO_HOST_NAME && len == 0) { /* non-compliant clients can send it * we'll just drop it and go on */ log_debug ("Ignoring empty DHO_HOST_NAME option"); if (option) option_dereference(&option, MDL); offset += len; continue; } op = lookup_option(universe, options, code); if (op == NULL) { /* If we don't have an option create one */ if (save_option_buffer(universe, options, bp, bp->data + offset, len, code, 1) == 0) { log_error("parse_option_buffer: " "save_option_buffer failed"); buffer_dereference(&bp, MDL); option_dereference(&option, MDL); return (0); } } else if (universe->concat_duplicates) { /* If we do have an option either concat with what is there ...*/ struct data_string new; memset(&new, 0, sizeof new); if (!buffer_allocate(&new.buffer, op->data.len + len, MDL)) { log_error("parse_option_buffer: No memory."); buffer_dereference(&bp, MDL); option_dereference(&option, MDL); return (0); } /* Copy old option to new data object. */ memcpy(new.buffer->data, op->data.data, op->data.len); /* Concat new option behind old. */ memcpy(new.buffer->data + op->data.len, bp->data + offset, len); new.len = op->data.len + len; new.data = new.buffer->data; /* Save new concat'd object. */ data_string_forget(&op->data, MDL); data_string_copy(&op->data, &new, MDL); data_string_forget(&new, MDL); } else { /* ... or we must append this statement onto the * end of the list. */ while (op->next != NULL) op = op->next; if (!option_cache_allocate(&nop, MDL)) { log_error("parse_option_buffer: No memory."); buffer_dereference(&bp, MDL); option_dereference(&option, MDL); return (0); } option_reference(&nop->option, op->option, MDL); nop->data.buffer = NULL; buffer_reference(&nop->data.buffer, bp, MDL); nop->data.data = bp->data + offset; nop->data.len = len; option_cache_reference(&op->next, nop, MDL); option_cache_dereference(&nop, MDL); } if (option) option_dereference(&option, MDL); offset += len; } 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) { 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, 1, 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 && (!save_option_buffer(&fqdn_universe, options, bp, &bp->data[5], i, 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_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_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 ("label length exceeds 63 in fqdn option"); goto bad; } if (len == 0) { terminated = 1; break; } if (s + len > &bp -> data [0] + length + 3) { log_info ("fqdn label longer than buffer"); goto bad; } if (first_len == 0) { first_len = len; } *s = '.'; s += len + 1; total_len += len + 1; } /* We wind up with a length that's one too many because we shouldn't increment for the last label, but there's no way to tell we're at the last label until we exit the loop. :'*/ if (total_len > 0) total_len--; if (!terminated) { first_len = total_len; } if (first_len > 0 && !save_option_buffer(&fqdn_universe, options, bp, &bp -> data[6], first_len, 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_DOMAINNAME, 1)) goto bad; } if (total_len > 0) if (!save_option_buffer (&fqdn_universe, options, bp, &bp -> data [6], total_len, FQDN_FQDN, 1)) goto bad; } if (!save_option_buffer (&fqdn_universe, options, bp, &bp -> data [1], 1, FQDN_NO_CLIENT_UPDATE, 0)) goto bad; if (!save_option_buffer (&fqdn_universe, options, bp, &bp -> data [2], 1, FQDN_SERVER_UPDATE, 0)) goto bad; if (!save_option_buffer (&fqdn_universe, options, bp, &bp -> data [3], 1, FQDN_RCODE1, 0)) goto bad; if (!save_option_buffer (&fqdn_universe, options, bp, &bp -> data [4], 1, FQDN_RCODE2, 0)) goto bad; buffer_dereference (&bp, MDL); return 1; } /* * Load all options into a buffer, and then split them out into the three * separate fields in the dhcp packet (options, file, and sname) where * options can be stored. * * returns 0 on error, length of packet on success */ int cons_options(struct packet *inpacket, struct dhcp_packet *outpacket, struct lease *lease, struct client_state *client_state, int mms, struct option_state *in_options, struct option_state *cfg_options, struct binding_scope **scope, int overload_avail, int terminate, int bootpp, struct data_string *prl, const char *vuname) { #define PRIORITY_COUNT 300 unsigned priority_list[PRIORITY_COUNT]; int priority_len; unsigned char buffer[4096], agentopts[1024]; unsigned index = 0; unsigned mb_size = 0, mb_max = 0; unsigned option_size = 0, agent_size = 0; unsigned length; int i; struct option_cache *op; struct data_string ds; pair pp, *hash; int overload_used = 0; int of1 = 0, of2 = 0; 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, or * if the one specified in the packet is shorter than the * alternative, take the one in the packet. */ if (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) != 0)) { if (ds.len >= sizeof (u_int16_t)) { i = getUShort(ds.data); if(!mms || (i < mms)) mms = i; } data_string_forget(&ds, MDL); } /* * If the client has provided a maximum DHCP message size, * use that, up to the MTU limit. 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 < DHCP_MTU_MIN) /* Enforce minimum packet size, per RFC 2132 */ mb_size = DHCP_MIN_OPTION_LEN; else if (mms > DHCP_MTU_MAX) /* * TODO: Packets longer than 1500 bytes really * should be allowed, but it requires upstream * changes to the way the packet is allocated. For * now, we forbid them. They won't be needed very * often anyway. */ mb_size = DHCP_MAX_OPTION_LEN; else mb_size = mms - DHCP_FIXED_LEN; } else if (bootpp) { mb_size = 64; if (inpacket != NULL && (inpacket->packet_length >= 64 + DHCP_FIXED_NON_UDP)) mb_size = inpacket->packet_length - DHCP_FIXED_NON_UDP; } else mb_size = DHCP_MIN_OPTION_LEN; /* * If answering a client message, see whether any relay agent * options were included with the message. If so, save them * to copy back in later, and make space in the main buffer * to accommodate them */ if (client_state == NULL) { priority_list[0] = DHO_DHCP_AGENT_OPTIONS; priority_len = 1; agent_size = store_options(NULL, agentopts, 0, sizeof(agentopts), inpacket, lease, client_state, in_options, cfg_options, scope, priority_list, priority_len, 0, 0, 0, NULL); mb_size += agent_size; if (mb_size > DHCP_MAX_OPTION_LEN) mb_size = DHCP_MAX_OPTION_LEN; } /* * Set offsets for buffer data to be copied into filename * and servername fields */ if (mb_size > agent_size) mb_max = mb_size - agent_size; else mb_max = mb_size; if (overload_avail & 1) { of1 = mb_max; mb_max += DHCP_FILE_LEN; } if (overload_avail & 2) { of2 = mb_max; mb_max += DHCP_SNAME_LEN; } /* * Preload the option priority list with protocol-mandatory options. * This effectively gives these options the highest priority. * This provides the order for any available options, the option * must be in the option cache in order to actually be included. */ 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_RENEWAL_TIME; priority_list[priority_len++] = DHO_DHCP_REBINDING_TIME; priority_list[priority_len++] = DHO_DHCP_MESSAGE; priority_list[priority_len++] = DHO_DHCP_REQUESTED_ADDRESS; priority_list[priority_len++] = DHO_ASSOCIATED_IP; if (prl != NULL && 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; } /* If echo-client-id is on, then we add client identifier to * the priority_list. This way we'll send it whether or not it * is in the PRL. */ if ((inpacket != NULL) && (priority_len < PRIORITY_COUNT) && (inpacket->sv_echo_client_id == ISC_TRUE)) { priority_list[priority_len++] = DHO_DHCP_CLIENT_IDENTIFIER; } data_string_truncate(prl, (PRIORITY_COUNT - priority_len)); /* * Copy the client's PRL onto the priority_list after our high * priority header. */ 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]; } /* * If the client doesn't request the FQDN option explicitly, * to indicate priority, consider it lowest priority. Fit * in the packet if there is space. Note that the option * may only be included if the client supplied one. */ if ((inpacket != NULL) && (priority_len < PRIORITY_COUNT) && (lookup_option(&fqdn_universe, inpacket->options, FQDN_ENCODED) != NULL)) priority_list[priority_len++] = DHO_FQDN; /* * Some DHCP Servers will give the subnet-mask option if * it is not on the parameter request list - so some client * implementations have come to rely on this - so we will * also make sure we supply this, at lowest priority. * * This is only done in response to DHCPDISCOVER or * DHCPREQUEST messages, to avoid providing the option on * DHCPINFORM or DHCPLEASEQUERY responses (if the client * didn't request it). */ if ((inpacket != NULL) && (priority_len < PRIORITY_COUNT) && ((inpacket->packet_type == DHCPDISCOVER) || (inpacket->packet_type == DHCPREQUEST))) priority_list[priority_len++] = DHO_SUBNET_MASK; } else { /* * First, hardcode some more options that ought to be * sent first...these are high priority to have in the * packet. */ 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; priority_list[priority_len++] = DHO_FQDN; /* * 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]; if (hash) { 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]; if (hash != NULL) 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; } } /* * Put any spaces that are encapsulated on the list, * sort out whether they contain values later. */ for (i = 0; i < cfg_options->universe_count; i++) { if (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; } /* Put the cookie up front... */ memcpy(buffer, DHCP_OPTIONS_COOKIE, 4); index += 4; /* Copy the options into the big buffer... */ option_size = store_options(&overload_used, buffer, index, mb_max, inpacket, lease, client_state, in_options, cfg_options, scope, priority_list, priority_len, of1, of2, terminate, vuname); /* If store_options() failed */ if (option_size == 0) return 0; /* How much was stored in the main buffer? */ index += option_size; /* * If we're going to have to overload, store the overload * option first. */ if (overload_used) { if (mb_size - agent_size - index < 3) return 0; buffer[index++] = DHO_DHCP_OPTION_OVERLOAD; buffer[index++] = 1; buffer[index++] = overload_used; if (overload_used & 1) memcpy(outpacket->file, &buffer[of1], DHCP_FILE_LEN); if (overload_used & 2) memcpy(outpacket->sname, &buffer[of2], DHCP_SNAME_LEN); } /* Now copy in preserved agent options, if any */ if (agent_size) { if (mb_size - index >= agent_size) { memcpy(&buffer[index], agentopts, agent_size); index += agent_size; } else log_error("Unable to store relay agent information " "in reply packet."); } /* Tack a DHO_END option onto the packet if we need to. */ if (index < mb_size) buffer[index++] = DHO_END; /* Copy main buffer into the options buffer of the packet */ memcpy(outpacket->options, buffer, index); /* Figure out the length. */ length = DHCP_FIXED_NON_UDP + index; return length; } /* * XXX: We currently special case collecting VSIO options. * We should be able to handle this in a more generic fashion, by * including any encapsulated options that are present and desired. * This will look something like the VSIO handling VSIO code. * We may also consider handling the ORO-like options within * encapsulated spaces. */ struct vsio_state { char *buf; int buflen; int bufpos; }; static void vsio_options(struct option_cache *oc, struct packet *packet, struct lease *dummy_lease, struct client_state *dummy_client_state, struct option_state *dummy_opt_state, struct option_state *opt_state, struct binding_scope **dummy_binding_scope, struct universe *universe, void *void_vsio_state) { struct vsio_state *vs = (struct vsio_state *)void_vsio_state; struct data_string ds; int total_len; memset(&ds, 0, sizeof(ds)); if (evaluate_option_cache(&ds, packet, NULL, NULL, opt_state, NULL, &global_scope, oc, MDL)) { total_len = ds.len + universe->tag_size + universe->length_size; if (total_len <= (vs->buflen - vs->bufpos)) { if (universe->tag_size == 1) { vs->buf[vs->bufpos++] = oc->option->code; } else if (universe->tag_size == 2) { putUShort((unsigned char *)vs->buf+vs->bufpos, oc->option->code); vs->bufpos += 2; } else if (universe->tag_size == 4) { putULong((unsigned char *)vs->buf+vs->bufpos, oc->option->code); vs->bufpos += 4; } if (universe->length_size == 1) { vs->buf[vs->bufpos++] = ds.len; } else if (universe->length_size == 2) { putUShort((unsigned char *)vs->buf+vs->bufpos, ds.len); vs->bufpos += 2; } else if (universe->length_size == 4) { putULong((unsigned char *)vs->buf+vs->bufpos, ds.len); vs->bufpos += 4; } memcpy(vs->buf + vs->bufpos, ds.data, ds.len); vs->bufpos += ds.len; } else { log_debug("No space for option %d in VSIO space %s.", oc->option->code, universe->name); } data_string_forget(&ds, MDL); } else { log_error("Error evaluating option %d in VSIO space %s.", oc->option->code, universe->name); } } /*! * * \brief Add a v6 option to the buffer * * Put the requested v6 option including tag, length and value * into the specified buffer. If there isn't enough space for * the entire option it is skipped. * * \param buf buffer to put the option * \param buflen total length of buffer * \param bufpos on input where to start putting the option * on output the starting point for the next option * \param code the option code number * \param ds the string to put into the option * * \return void */ static void add_option6_data(char *buf, int buflen, int* bufpos, uint16_t code, struct data_string* ds) { if ((ds->len + 4) > (buflen - *bufpos)) { log_debug("No space for option %d", code); } else { unsigned char* tmp = (unsigned char *)buf + *bufpos; /* option tag */ putUShort(tmp, code); /* option length */ putUShort(tmp+2, ds->len); /* option data */ memcpy(tmp+4, ds->data, ds->len); /* update position */ *bufpos += 4 + ds->len; } } /*! * * \brief Add a v6 encapsulated option to a buffer * * Find the universe for the requested option and if it exists * call it's encapsualtion routine to produce a data string which * can then be added to the current buffer. * * Note 1: currently we only do simple encapsulations, where the * entire value of the option is in the option universe. This is * the 'E' format, we don't handle the 'e' format as we haven't * defined any such universes yet. This means that if there is * a simple value for the option store_options6 should handle it * directly and not call this routine. * * \param buf buffer to put the option * \param buflen total length of buffer * \param bufpos on input where to start putting the option * on output the starting point for the next option * \param opt_state information about option values to use * \param packet structure containing what we know about the packet * \param encap_opt information about the structure of the option * \param code the option code number * * \return void */ static void store_encap6 (char *buf, int buflen, int* bufpos, struct option_state *opt_state, struct packet *packet, struct option* encap_opt, uint16_t code) { /* We need to extract the name of the universe * to use for this option. We expect a format string * of the form "Ename.". If we don't find a name we bail. */ struct data_string ds; struct data_string name; char* s = (char*)encap_opt->format; char* t; if ((s == NULL) || (*s != 'E') || (strlen(s) <= 2)) { return; } t = strchr(++s, '.'); if ((t == NULL) || (t == s)) { return; } memset(&ds, 0, sizeof(ds)); memset(&name, 0, sizeof(name)); name.data = (unsigned char *)s; name.len = t - s; /* Now we call the routine to find and encapsulate the requested * option/universe. A return of 0 means no option information was * available and nothing is added to the buffer */ if (option_space_encapsulate(&ds, packet, NULL, NULL, NULL, opt_state, &global_scope, &name) != 0) { add_option6_data(buf, buflen, bufpos, code, &ds); data_string_forget(&ds, MDL); } } /* * Stores the options from the DHCPv6 universe into the buffer given. * * Required options are given as a 0-terminated list of option codes. * Once those are added, the ORO is consulted. */ int store_options6(char *buf, int buflen, struct option_state *opt_state, struct packet *packet, const int *required_opts, struct data_string *oro) { int i, j; struct option_cache *oc; struct option *o; struct data_string ds; int bufpos; int oro_size; u_int16_t code; int in_required_opts; int vsio_option_code; int vsio_wanted; struct vsio_state vs; unsigned char *tmp; bufpos = 0; vsio_wanted = 0; /* * Find the option code for the VSIO universe. */ vsio_option_code = 0; o = vsio_universe.enc_opt; while (o != NULL) { if (o->universe == &dhcpv6_universe) { vsio_option_code = o->code; break; } o = o->universe->enc_opt; } if (vsio_option_code == 0) { log_fatal("No VSIO option code found."); } if (required_opts != NULL) { for (i=0; required_opts[i] != 0; i++) { if (required_opts[i] == vsio_option_code) { vsio_wanted = 1; } oc = lookup_option(&dhcpv6_universe, opt_state, required_opts[i]); if (oc == NULL) { continue; } memset(&ds, 0, sizeof(ds)); for (; oc != NULL ; oc = oc->next) { if (evaluate_option_cache(&ds, packet, NULL, NULL, opt_state, NULL, &global_scope, oc, MDL)) { add_option6_data(buf, buflen, &bufpos, (uint16_t)required_opts[i], &ds); data_string_forget(&ds, MDL); } else { log_error("Error evaluating option %d", required_opts[i]); } } } } if (oro == NULL) { oro_size = 0; } else { oro_size = oro->len / 2; } for (i=0; idata+(i*2), 2); code = ntohs(code); /* * See if we've already included this option because * it is required. */ in_required_opts = 0; if (required_opts != NULL) { for (j=0; required_opts[j] != 0; j++) { if (required_opts[j] == code) { in_required_opts = 1; break; } } } if (in_required_opts) { continue; } /* * If this is the VSIO option flag it so we'll know to * check the vsio space later on. However we still need * to check for the existence of any defined via * dhcp6.vendor-opts. Those are stored as simple values. */ if (code == vsio_option_code) { vsio_wanted = 1; } /* * Not already added, find this option. */ oc = lookup_option(&dhcpv6_universe, opt_state, code); memset(&ds, 0, sizeof(ds)); if (oc != NULL) { /* We have a simple value for the option */ for (; oc != NULL ; oc = oc->next) { if (evaluate_option_cache(&ds, packet, NULL, NULL, opt_state, NULL, &global_scope, oc, MDL)) { add_option6_data(buf, buflen, &bufpos, code, &ds); data_string_forget(&ds, MDL); } else { log_error("Error evaluating option %d", code); } } } else { /* * We don't have a simple value, check to see if we * have an universe to encapsulate into an option. */ struct option *encap_opt = NULL; unsigned int code_int = code; option_code_hash_lookup(&encap_opt, dhcpv6_universe.code_hash, &code_int, 0, MDL); if (encap_opt != NULL) { store_encap6(buf, buflen, &bufpos, opt_state, packet, encap_opt, code); option_dereference(&encap_opt, MDL); } } } if (vsio_wanted) { for (i=0; i < opt_state->universe_count; i++) { if (opt_state->universes[i] != NULL) { o = universes[i]->enc_opt; if ((o != NULL) && (o->universe == &vsio_universe)) { /* * Add the data from this VSIO option. */ vs.buf = buf; vs.buflen = buflen; vs.bufpos = bufpos+8; option_space_foreach(packet, NULL, NULL, NULL, opt_state, NULL, universes[i], (void *)&vs, vsio_options); /* * If there was actually data here, * add the "header". */ if (vs.bufpos > bufpos+8) { tmp = (unsigned char *)buf + bufpos; putUShort(tmp, vsio_option_code); putUShort(tmp+2, vs.bufpos-bufpos-4); putULong(tmp+4, o->code); bufpos = vs.bufpos; } } } } } return bufpos; } /* * Store all the requested options into the requested buffer. * XXX: ought to be static */ int store_options(int *ocount, unsigned char *buffer, unsigned index, 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, unsigned first_cutoff, int second_cutoff, int terminate, const char *vuname) { int bufix = 0, six = 0, tix = 0; int i; int ix; int tto; int bufend, sbufend; struct data_string od; struct option_cache *oc; struct option *option = NULL; unsigned code; /* * These arguments are relative to the start of the buffer, so * reduce them by the current buffer index, and advance the * buffer pointer to where we're going to start writing. */ buffer = &buffer[index]; buflen -= index; if (first_cutoff) first_cutoff -= index; if (second_cutoff) second_cutoff -= index; /* Calculate the start and end of each section of the buffer */ bufend = sbufend = buflen; if (first_cutoff) { if (first_cutoff >= buflen) log_fatal("%s:%d:store_options: Invalid first cutoff.", MDL); bufend = first_cutoff; if (second_cutoff) { if (second_cutoff >= buflen) log_fatal("%s:%d:store_options: Invalid second cutoff.", MDL); sbufend = second_cutoff; } } else if (second_cutoff) { if (second_cutoff >= buflen) log_fatal("%s:%d:store_options: Invalid second cutoff.", MDL); bufend = second_cutoff; } memset (&od, 0, sizeof od); /* Eliminate duplicate options from the parameter request list. * Enforce RFC-mandated ordering of options that are present. */ for (i = 0; i < priority_len; i++) { /* Eliminate duplicates. */ 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--; } } /* Enforce ordering of SUBNET_MASK options, according to * RFC2132 Section 3.3: * * If both the subnet mask and the router option are * specified in a DHCP reply, the subnet mask option MUST * be first. * * This guidance does not specify what to do if the client * PRL explicitly requests the options out of order, it is * a general statement. */ if (priority_list[i] == DHO_SUBNET_MASK) { for (ix = i - 1 ; ix >= 0 ; ix--) { if (priority_list[ix] == DHO_ROUTERS) { /* swap */ priority_list[ix] = DHO_SUBNET_MASK; priority_list[i] = DHO_ROUTERS; break; } } } } /* Copy out the options in the order that they appear in the priority list... */ for (i = 0; i < priority_len; i++) { /* Number of bytes left to store (some may already have been stored by a previous pass). */ unsigned length; int optstart, soptstart, toptstart; struct universe *u; int have_encapsulation = 0; struct data_string encapsulation; int splitup; memset (&encapsulation, 0, sizeof encapsulation); have_encapsulation = 0; if (option != NULL) option_dereference(&option, MDL); /* 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; oc = lookup_option (u, cfg_options, code); if (oc && oc->option) option_reference(&option, oc->option, MDL); else option_code_hash_lookup(&option, u->code_hash, &code, 0, MDL); /* If it's a straight encapsulation, and the user supplied a * value for the entire option, use that. Otherwise, search * the encapsulated space. * * If it's a limited encapsulation with preceding data, and the * user supplied values for the preceding bytes, search the * encapsulated space. */ if ((option != NULL) && (((oc == NULL) && (option->format[0] == 'E')) || ((oc != NULL) && (option->format[0] == 'e')))) { static char *s, *t; struct option_cache *tmp; struct data_string name; s = strchr (option->format, 'E'); if (s) t = strchr (++s, '.'); if (s && t) { memset (&name, 0, sizeof name); /* 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) /* No need to check the return as we check name.len below */ (void) 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); } } /* 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. */ /* If no data is available for this option, skip it. */ if (!oc && !have_encapsulation) { continue; } /* Find the value of the option... */ od.len = 0; if (oc) { /* No need to check the return as we check od.len below */ (void) evaluate_option_cache (&od, packet, lease, client_state, in_options, cfg_options, scope, oc, MDL); /* If we have encapsulation for this option, and an oc * lookup succeeded, but the evaluation failed, it is * either because this is a complex atom (atoms before * E on format list) and the top half of the option is * not configured, or this is a simple encapsulated * space and the evaluator is giving us a NULL. Prefer * the evaluator's opinion over the subspace. */ if (!od.len) { data_string_forget (&encapsulation, MDL); data_string_forget (&od, MDL); continue; } } /* We should now have a constant length for the option. */ length = od.len; if (have_encapsulation) { length += encapsulation.len; /* od.len can be nonzero if we got here without an * oc (cache lookup failed), but did have an encapsulated * simple encapsulation space. */ 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; } } /* Do we add a NUL? */ if (terminate && option && format_has_text(option->format)) { 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. */ if (length > 255) splitup = 1; else splitup = 0; ix = 0; optstart = bufix; soptstart = six; toptstart = tix; while (length) { unsigned incr = length; int *pix; unsigned char *base; /* Try to fit it in the options buffer. */ if (!splitup && ((!six && !tix && (i == priority_len - 1) && (bufix + 2 + length < bufend)) || (bufix + 5 + length < bufend))) { base = buffer; pix = &bufix; /* Try to fit it in the second buffer. */ } else if (!splitup && first_cutoff && (first_cutoff + six + 3 + length < sbufend)) { base = &buffer[first_cutoff]; pix = &six; /* Try to fit it in the third buffer. */ } else if (!splitup && second_cutoff && (second_cutoff + tix + 3 + length < buflen)) { base = &buffer[second_cutoff]; pix = &tix; /* Split the option up into the remaining space. */ } else { splitup = 1; /* Use any remaining options space. */ if (bufix + 6 < bufend) { incr = bufend - bufix - 5; base = buffer; pix = &bufix; /* Use any remaining first_cutoff space. */ } else if (first_cutoff && (first_cutoff + six + 4 < sbufend)) { incr = sbufend - (first_cutoff + six) - 3; base = &buffer[first_cutoff]; pix = &six; /* Use any remaining second_cutoff space. */ } else if (second_cutoff && (second_cutoff + tix + 4 < buflen)) { incr = buflen - (second_cutoff + tix) - 3; base = &buffer[second_cutoff]; pix = &tix; /* Give up, roll back this option. */ } else { bufix = optstart; six = soptstart; tix = toptstart; break; } } if (incr > length) incr = length; if (incr > 255) incr = 255; /* Everything looks good - copy it in! */ base [*pix] = code; base [*pix + 1] = (unsigned char)incr; if (tto && incr == length) { if (incr > 1) memcpy (base + *pix + 2, od.data + ix, (unsigned)(incr - 1)); base [*pix + 2 + incr - 1] = 0; } else { memcpy (base + *pix + 2, od.data + ix, (unsigned)incr); } length -= incr; ix += incr; *pix += 2 + incr; } data_string_forget (&od, MDL); } if (option != NULL) option_dereference(&option, MDL); /* If we can overload, and we have, then PAD and END those spaces. */ if (first_cutoff && six) { if ((first_cutoff + six + 1) < sbufend) memset (&buffer[first_cutoff + six + 1], DHO_PAD, sbufend - (first_cutoff + six + 1)); else if (first_cutoff + six >= sbufend) log_fatal("Second buffer overflow in overloaded options."); buffer[first_cutoff + six] = DHO_END; if (ocount != NULL) *ocount |= 1; /* So that caller knows there's data there. */ } if (second_cutoff && tix) { if (second_cutoff + tix + 1 < buflen) { memset (&buffer[second_cutoff + tix + 1], DHO_PAD, buflen - (second_cutoff + tix + 1)); } else if (second_cutoff + tix >= buflen) log_fatal("Third buffer overflow in overloaded options."); buffer[second_cutoff + tix] = DHO_END; if (ocount != NULL) *ocount |= 2; /* So that caller knows there's data there. */ } if ((six || tix) && (bufix + 3 > bufend)) log_fatal("Not enough space for option overload option."); return bufix; } /* Return true if the format string has a variable length text option * ("t"), return false otherwise. */ int format_has_text(format) const char *format; { const char *p; p = format; while (*p != '\0') { switch (*p++) { case 't': case 'k': return 1; /* These symbols are arbitrary, not fixed or * determinable length...text options with them is * invalid (whatever the case, they are never NULL * terminated). */ case 'A': case 'a': case 'X': case 'x': case 'D': case 'd': return 0; case 'c': /* 'c' only follows 'D' atoms, and indicates that * compression may be used. If there was a 'D' * atom already, we would have returned. So this * is an error, but continue looking for 't' anyway. */ log_error("format_has_text(%s): 'c' atoms are illegal " "except after 'D' atoms.", format); break; /* 'E' is variable length, but not arbitrary...you * can find its length if you can find an END option. * N is (n)-byte in length but trails a name of a * space defining the enumeration values. So treat * both the same - valid, fixed-length fields. */ case 'E': case 'N': /* Consume the space name. */ while ((*p != '\0') && (*p++ != '.')) ; break; default: break; } } return 0; } /* Determine the minimum length of a DHCP option prior to any variable * or inconsistent length formats, according to its configured format * variable (and possibly from supplied option cache contents for variable * length format symbols). */ int format_min_length(format, oc) const char *format; struct option_cache *oc; { const char *p, *name; int min_len = 0; int last_size = 0; struct enumeration *espace; p = format; while (*p != '\0') { switch (*p++) { case '6': /* IPv6 Address */ min_len += 16; last_size = 16; break; case 'I': /* IPv4 Address */ case 'l': /* int32_t */ case 'L': /* uint32_t */ case 'T': /* Lease Time, uint32_t equivalent */ min_len += 4; last_size = 4; break; case 's': /* int16_t */ case 'S': /* uint16_t */ min_len += 2; last_size = 2; break; case 'N': /* Enumeration value. */ /* Consume space name. */ name = p; p = strchr(p, '.'); if (p == NULL) log_fatal("Corrupt format: %s", format); espace = find_enumeration(name, p - name); if (espace == NULL) { log_error("Unknown enumeration: %s", format); /* Max is safest value to return. */ return INT_MAX; } min_len += espace->width; last_size = espace->width; p++; break; case 'b': /* int8_t */ case 'B': /* uint8_t */ case 'F': /* Flag that is always true. */ case 'f': /* Flag */ min_len++; last_size = 1; break; case 'o': /* Last argument is optional. */ min_len -= last_size; /* XXX: It MAY be possible to sense the end of an * encapsulated space, but right now this is too * hard to support. Return a safe value. */ case 'e': /* Encapsulation hint (there is an 'E' later). */ case 'E': /* Encapsulated options. */ return min_len; case 'd': /* "Domain name" */ case 'D': /* "rfc1035 formatted names" */ case 't': /* "ASCII Text" */ case 'X': /* "ASCII or Hex Conditional */ case 'x': /* "Hex" */ case 'A': /* Array of all that precedes. */ case 'a': /* Array of preceding symbol. */ case 'Z': /* nothing. */ case 'k': /* key name */ return min_len; case 'c': /* Compress flag for D atom. */ log_error("format_min_length(%s): 'c' atom is illegal " "except after 'D' atom.", format); return INT_MAX; default: /* No safe value is known. */ log_error("format_min_length(%s): No safe value " "for unknown format symbols.", format); return INT_MAX; } } return min_len; } /* Format the specified option so that a human can easily read it. */ /* Maximum pretty printed size */ #define MAX_OUTPUT_SIZE 32*1024 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; { /* We add 128 byte pad so we don't have to add checks everywhere. */ static char optbuf [MAX_OUTPUT_SIZE + 128]; /* XXX */ static char *endbuf = optbuf + MAX_OUTPUT_SIZE; int hunksize = 0; int opthunk = 0; int hunkinc = 0; int numhunk = -1; int numelem = 0; int count; int i, j, k, l; char fmtbuf[32] = ""; struct iaddr iaddr; struct enumeration *enumbuf[32]; /* MUST be same as fmtbuf */ char *op = optbuf; const unsigned char *dp = data; char comma; unsigned long tval; isc_boolean_t a_array = ISC_FALSE; int len_used; if (emit_commas) comma = ','; else comma = ' '; memset (enumbuf, 0, sizeof enumbuf); /* Figure out the size of the data. */ for (l = i = 0; option -> format [i]; i++, l++) { if (l >= sizeof(fmtbuf) - 1) log_fatal("Bounds failure on internal buffer at " "%s:%d", MDL); if (!numhunk) { log_error ("%s: Extra codes in format string: %s", option -> name, &(option -> format [i])); break; } numelem++; fmtbuf [l] = option -> format [i]; switch (option -> format [i]) { case 'a': a_array = ISC_TRUE; /* Fall through */ case 'A': --numelem; fmtbuf [l] = 0; numhunk = 0; break; case 'E': /* Skip the universe name. */ while (option -> format [i] && option -> format [i] != '.') i++; /* Fall Through! */ case 'X': for (k = 0; k < len; k++) { if (!isascii (data [k]) || !isprint (data [k])) break; } /* 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 [l] = 'x'; hunksize++; comma = ':'; numhunk = 0; a_array = ISC_TRUE; hunkinc = 1; } fmtbuf [l + 1] = 0; break; case 'c': /* The 'c' atom is a 'D' modifier only. */ log_error("'c' atom not following D atom in format " "string: %s", option->format); break; case 'D': /* * Skip the 'c' atom, if present. It does not affect * how we convert wire->text format (if compression is * present either way, we still process it). */ if (option->format[i+1] == 'c') i++; fmtbuf[l + 1] = 0; numhunk = -2; break; case 'd': /* Should not be optional, array or compressed */ if ((option->format[i+1] == 'o') || (option->format[i+1] == 'a') || (option->format[i+1] == 'A') || (option->format[i+1] == 'c')) { log_error("%s: Illegal use of domain name: %s", option->name, &(option->format[i-1])); fmtbuf[l + 1] = 0; } k = MRns_name_len(data + len, data + hunksize); if (k == -1) { log_error("Invalid domain name."); return ""; } hunksize += k; break; case 't': case 'k': 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); if (enumbuf[l] == NULL) { hunksize += 1; hunkinc = 1; } else { hunksize += enumbuf[l]->width; hunkinc = enumbuf[l]->width; } break; case '6': hunksize += 16; hunkinc = 16; 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': case 'F': hunksize++; hunkinc = 1; break; case 'e': case 'Z': break; case 'o': opthunk += hunkinc; break; default: log_error ("%s: garbage in format string: %s", option -> name, &(option -> format [i])); break; } } /* Check for too few bytes... */ if (hunksize - opthunk > len) { log_error ("%s: expecting at least %d bytes; got %d", option -> name, hunksize, len); return ""; } /* Check for too many bytes... */ if (numhunk == -1 && hunksize < len) log_error ("%s: %d extra bytes", option -> name, len - hunksize); /* If this is an array, compute its size. */ if (numhunk == 0) { if (a_array == ISC_TRUE) { /* * It is an 'a' type array - we repeat the * last format type. A binary string for 'X' * is also like this. hunkinc is the size * of the last format type and we add 1 to * cover the entire first record. */ /* If format string had no valid entries prior to * 'a' hunkinc will be 0. Ex: "a", "oa", "aA" */ if (hunkinc == 0) { log_error ("%s: invalid 'a' format: %s", option->name, option->format); return (""); } numhunk = ((len - hunksize) / hunkinc) + 1; len_used = hunksize + ((numhunk - 1) * hunkinc); } else { /* * It is an 'A' type array - we repeat the * entire record */ /* If format string had no valid entries prior to * 'A' hunksize will be 0. Ex: "A", "oA", "foA" */ if (hunksize == 0) { log_error ("%s: invalid 'A' format: %s", option->name, option->format); return (""); } numhunk = len / hunksize; len_used = numhunk * hunksize; } /* See if we got an exact number of hunks. */ if (len_used < len) { log_error ("%s: %d extra bytes at end of array\n", option -> name, len - len_used); } } /* 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++) { if ((a_array == ISC_TRUE) && (i != 0) && (numelem > 0)) { /* * For 'a' type of arrays we repeat * only the last format character * We should never hit the case of numelem == 0 * but let's include the check to be safe. */ j = numelem - 1; } else { /* * for other types of arrays or the first * time through for 'a' types, we go through * the entire set of format characters. */ j = 0; } for (; j < numelem; j++) { switch (fmtbuf [j]) { case 't': case 'k': /* endbuf-1 leaves room for NULL. */ k = pretty_text(&op, endbuf - 1, &dp, data + len, emit_quotes); if (k == -1) { log_error("Error printing text."); break; } *op = 0; break; case 'd': /* RFC1035 format name */ k = MRns_name_len(data + len, dp); /* Already tested... */ if (k == -1) { log_error("invalid domain name."); return ""; } pretty_dname(&op, endbuf-1, dp, data + len); /* pretty_dname does not add the nul */ *op = '\0'; dp += k; break; case 'D': /* RFC1035 format name list */ for( ; dp < (data + len) ; dp += k) { unsigned char nbuff[NS_MAXCDNAME]; const unsigned char *nbp, *nend; nend = &nbuff[sizeof(nbuff)]; /* If this is for ISC DHCP consumption * (emit_quotes), lay it out as a list * of STRING tokens. Otherwise, it is * a space-separated list of DNS- * escaped names as /etc/resolv.conf * might digest. */ if (dp != data) { if (op + 2 > endbuf) break; if (emit_quotes) *op++ = ','; *op++ = ' '; } /* XXX: if fmtbuf[j+1] != 'c', we * should warn if the data was * compressed anyway. */ k = MRns_name_unpack(data, data + len, dp, nbuff, sizeof(nbuff)); if (k == -1) { log_error("Invalid domain " "list."); break; } /* If emit_quotes, then use ISC DHCP * escapes. Otherwise, rely only on * MRns_name_ntop(). */ if (emit_quotes) { nbp = nbuff; pretty_domain(&op, endbuf-1, &nbp, nend); } else { /* MRns_name_ntop() includes * a trailing NUL in its * count. */ count = MRns_name_ntop( nbuff, op, (endbuf-op)-1); if (count <= 0) { log_error("Invalid " "domain name."); break; } /* Consume all but the trailing * NUL. */ op += count - 1; /* Replace the trailing NUL * with the implicit root * (in the unlikely event the * domain name /is/ the root). */ *op++ = '.'; } } *op = '\0'; break; /* pretty-printing an array of enums is going to get ugly. */ case 'N': if (!enumbuf [j]) { tval = *dp++; goto enum_as_num; } switch (enumbuf[j]->width) { case 1: tval = getUChar(dp); break; case 2: tval = getUShort(dp); break; case 4: tval = getULong(dp); break; default: log_fatal("Impossible case at %s:%d.", MDL); return ""; } for (i = 0; ;i++) { if (!enumbuf [j] -> values [i].name) goto enum_as_num; if (enumbuf [j] -> values [i].value == tval) break; } strcpy (op, enumbuf [j] -> values [i].name); dp += enumbuf[j]->width; break; enum_as_num: sprintf(op, "%lu", tval); break; case 'I': iaddr.len = 4; memcpy(iaddr.iabuf, dp, 4); strcpy(op, piaddr(iaddr)); dp += 4; break; case '6': iaddr.len = 16; memcpy(iaddr.iabuf, dp, 16); strcpy(op, piaddr(iaddr)); dp += 16; break; case 'l': sprintf (op, "%ld", (long)getLong (dp)); dp += 4; break; case 'T': tval = getULong (dp); if (tval == -1) sprintf (op, "%s", "infinite"); else sprintf(op, "%lu", tval); break; case 'L': sprintf(op, "%lu", (unsigned long)getULong(dp)); dp += 4; break; case 's': sprintf (op, "%d", (int)getShort (dp)); dp += 2; break; case 'S': sprintf(op, "%u", (unsigned)getUShort(dp)); dp += 2; break; case 'b': sprintf (op, "%d", *(const char *)dp++); break; case 'B': sprintf (op, "%d", *dp++); break; case 'X': case 'x': sprintf (op, "%x", *dp++); break; case 'f': strcpy (op, *dp++ ? "true" : "false"); break; case 'F': strcpy (op, "true"); break; case 'e': case 'Z': *op = '\0'; break; default: log_error ("Unexpected format code %c", fmtbuf [j]); } op += strlen (op); if (op >= endbuf) { log_error ("Option data exceeds" " maximum size %d", MAX_OUTPUT_SIZE); return (""); } 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; } /* * Look for the option and dig out the value assoicated with it. * Currently this is used for 1 byte integers, it maybe expanded * in the future to handle other integers at which point it will * need a size argument. */ int get_option_int (result, universe, packet, lease, client_state, in_options, cfg_options, options, scope, code, file, line) int *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; struct data_string d1; int rcode = 0; /* basic sanity checks */ if ((options == NULL) || (universe->lookup_func == NULL)) return (0); /* find the option cache */ oc = ((*universe->lookup_func)(universe, options, code)); if (!oc) return (0); /* if there is a value get it into the string */ memset(&d1, 0, sizeof(d1)); if (!evaluate_option_cache(&d1, packet, lease, client_state, in_options, cfg_options, scope, oc, file, line)) return (0); /* If the length matches extract the value for the return */ if (d1.len == 1) { *result = d1.data[0]; rcode = 1; } data_string_forget(&d1, MDL); return (rcode); } 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 set_option."); 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; } } /* If we are trying to combine compressed domain-lists then * we need to change the expression opcode. The lists must * be decompressed, combined, and then recompressed to work * correctly. You cannot simply add two compressed lists * together. */ switch (((memcmp(option->option->format, "Dc", 2) == 0) + (memcmp(oc->option->format, "Dc", 2) == 0))) { case 1: /* Only one is "Dc", this won't work * Not sure if you can make this occur, but just * in case. */ log_error ("Both options must be Dc format"); option_cache_dereference (&noc, MDL); return; case 2: /* Both are "Dc", change the code */ noc->expression->op = expr_concat_dclist; break; default: /* Neither are "Dc", so as you were */ break; } option_reference(&(noc->option), oc->option, MDL); 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; } /* Save a specified buffer into an option cache. */ int save_option_buffer(struct universe *universe, struct option_state *options, struct buffer *bp, unsigned char *buffer, unsigned length, unsigned code, int terminatep) { struct option_cache *op = NULL; int status = 1; status = prepare_option_buffer(universe, bp, buffer, length, code, terminatep, &op); if (status == 0) goto cleanup; save_option(universe, options, op); cleanup: if (op != NULL) option_cache_dereference(&op, MDL); return status; } /* Append a specified buffer onto the tail of an option cache. */ int append_option_buffer(struct universe *universe, struct option_state *options, struct buffer *bp, unsigned char *buffer, unsigned length, unsigned code, int terminatep) { struct option_cache *op = NULL; int status = 1; status = prepare_option_buffer(universe, bp, buffer, length, code, terminatep, &op); if (status == 0) goto cleanup; also_save_option(universe, options, op); cleanup: if (op != NULL) option_cache_dereference(&op, MDL); return status; } /* Create/copy a buffer into a new option cache. */ static int prepare_option_buffer(struct universe *universe, struct buffer *bp, unsigned char *buffer, unsigned length, unsigned code, int terminatep, struct option_cache **opp) { struct buffer *lbp = NULL; struct option *option = NULL; struct option_cache *op; int status = 1; /* Code sizes of 8, 16, and 32 bits are allowed. */ switch(universe->tag_size) { case 1: if (code > 0xff) return 0; break; case 2: if (code > 0xffff) return 0; break; case 4: if (code > 0xffffffff) return 0; break; default: log_fatal("Inconsistent universe tag size at %s:%d.", MDL); } option_code_hash_lookup(&option, universe->code_hash, &code, 0, MDL); /* If we created an option structure for each option a client * supplied, it's possible we may create > 2^32 option structures. * That's not feasible. So by failing to enter these option * structures into the code and name hash tables, references will * never be more than 1 - when the option cache is destroyed, this * will be cleaned up. */ if (!option) { char nbuf[sizeof("unknown-4294967295")]; sprintf(nbuf, "unknown-%u", code); option = new_option(nbuf, MDL); if (!option) return 0; option->format = default_option_format; option->universe = universe; option->code = code; /* new_option() doesn't set references, pretend. */ option->refcnt = 1; } if (!option_cache_allocate (opp, MDL)) { log_error("No memory for option code %s.%s.", universe->name, option->name); status = 0; goto cleanup; } /* Pointer rather than double pointer makes for less parens. */ op = *opp; option_reference(&op->option, option, MDL); /* 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 + terminatep, MDL)) { log_error ("no memory for option buffer."); status = 0; goto cleanup; } memcpy (lbp -> data, buffer, length + terminatep); 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 (terminatep) { /* 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; /* If this option is ultimately a text option, null determinate to * comply with RFC2132 section 2. Mark a flag so this can be sensed * later to echo NULLs back to clients that supplied them (they * probably expect them). */ if (format_has_text(option->format)) { int min_len = format_min_length(option->format, op); while ((op->data.len > min_len) && (op->data.data[op->data.len-1] == '\0')) { op->data.len--; op->flags |= OPTION_HAD_NULLS; } } /* And let go of our references. */ cleanup: if (lbp != NULL) buffer_dereference(&lbp, MDL); option_dereference(&option, MDL); return status; } static void count_options(struct option_cache *dummy_oc, struct packet *dummy_packet, struct lease *dummy_lease, struct client_state *dummy_client_state, struct option_state *dummy_opt_state, struct option_state *opt_state, struct binding_scope **dummy_binding_scope, struct universe *dummy_universe, void *void_accumulator) { int *accumulator = (int *)void_accumulator; *accumulator += 1; } static void collect_oro(struct option_cache *oc, struct packet *dummy_packet, struct lease *dummy_lease, struct client_state *dummy_client_state, struct option_state *dummy_opt_state, struct option_state *opt_state, struct binding_scope **dummy_binding_scope, struct universe *dummy_universe, void *void_oro) { struct data_string *oro = (struct data_string *)void_oro; putUShort(oro->buffer->data + oro->len, oc->option->code); oro->len += 2; } /* build_server_oro() is presently unusued, but may be used at a future date * with support for Reconfigure messages (as a hint to the client about new * option value contents). */ void build_server_oro(struct data_string *server_oro, struct option_state *options, const char *file, int line) { int num_opts; int i; struct option *o; /* * Count the number of options, so we can allocate enough memory. * We want to mention sub-options too, so check all universes. */ num_opts = 0; option_space_foreach(NULL, NULL, NULL, NULL, options, NULL, &dhcpv6_universe, (void *)&num_opts, count_options); for (i=0; i < options->universe_count; i++) { if (options->universes[i] != NULL) { o = universes[i]->enc_opt; while (o != NULL) { if (o->universe == &dhcpv6_universe) { num_opts++; break; } o = o->universe->enc_opt; } } } /* * Allocate space. */ memset(server_oro, 0, sizeof(*server_oro)); if (!buffer_allocate(&server_oro->buffer, num_opts * 2, MDL)) { log_fatal("no memory to build server ORO"); } server_oro->data = server_oro->buffer->data; /* * Copy the data in. * We want to mention sub-options too, so check all universes. */ server_oro->len = 0; /* gets set in collect_oro */ option_space_foreach(NULL, NULL, NULL, NULL, options, NULL, &dhcpv6_universe, (void *)server_oro, collect_oro); for (i=0; i < options->universe_count; i++) { if (options->universes[i] != NULL) { o = universes[i]->enc_opt; while (o != NULL) { if (o->universe == &dhcpv6_universe) { unsigned char *tmp; tmp = server_oro->buffer->data; putUShort(tmp + server_oro->len, o->code); server_oro->len += 2; break; } o = o->universe->enc_opt; } } } } /* Wrapper function to put an option cache into an option state. */ void save_option(struct universe *universe, struct option_state *options, struct option_cache *oc) { if (universe->save_func) (*universe->save_func)(universe, options, oc, ISC_FALSE); else log_error("can't store options in %s space.", universe->name); } /* Wrapper function to append an option cache into an option state's list. */ void also_save_option(struct universe *universe, struct option_state *options, struct option_cache *oc) { if (universe->save_func) (*universe->save_func)(universe, options, oc, ISC_TRUE); else log_error("can't store options in %s space.", universe->name); } void save_hashed_option(struct universe *universe, struct option_state *options, struct option_cache *oc, isc_boolean_t appendp) { int hashix; pair bptr; pair *hash = options -> universes [universe -> index]; struct option_cache **ocloc; 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] = (void *)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; } /* Deal with collisions on the hash list. */ if (bptr) { ocloc = (struct option_cache **)&bptr->car; /* * If appendp is set, append it onto the tail of the * ->next list. If it is not set, rotate it into * position at the head of the list. */ if (appendp) { do { ocloc = &(*ocloc)->next; } while (*ocloc != NULL); } else { option_cache_dereference(ocloc, MDL); } option_cache_reference(ocloc, 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)->option) option_dereference(&(*ptr)->option, MDL); 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; } /* The 'data_string' primitive doesn't have an appension mechanism. * This function must then append a new option onto an existing buffer * by first duplicating the original buffer and appending the desired * values, followed by coping the new value into place. */ int append_option(struct data_string *dst, struct universe *universe, struct option *option, struct data_string *src) { struct data_string tmp; if (src->len == 0 && option->format[0] != 'Z') return 0; memset(&tmp, 0, sizeof(tmp)); /* Allocate a buffer to hold existing data, the current option's * tag and length, and the option's content. */ if (!buffer_allocate(&tmp.buffer, (dst->len + universe->length_size + universe->tag_size + src->len), MDL)) { /* XXX: This kills all options presently stored in the * destination buffer. This is the way the original code * worked, and assumes an 'all or nothing' approach to * eg encapsulated option spaces. It may or may not be * desirable. */ data_string_forget(dst, MDL); return 0; } tmp.data = tmp.buffer->data; /* Copy the existing data off the destination. */ if (dst->len != 0) memcpy(tmp.buffer->data, dst->data, dst->len); tmp.len = dst->len; /* Place the new option tag and length. */ (*universe->store_tag)(tmp.buffer->data + tmp.len, option->code); tmp.len += universe->tag_size; (*universe->store_length)(tmp.buffer->data + tmp.len, src->len); tmp.len += universe->length_size; /* Copy the option contents onto the end. */ memcpy(tmp.buffer->data + tmp.len, src->data, src->len); tmp.len += src->len; /* Play the shell game. */ data_string_forget(dst, MDL); data_string_copy(dst, &tmp, MDL); data_string_forget(&tmp, MDL); return 1; } int store_option(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 tmp; struct universe *subu=NULL; int status; char *start, *end; memset(&tmp, 0, sizeof(tmp)); if (evaluate_option_cache(&tmp, packet, lease, client_state, in_options, cfg_options, scope, oc, MDL)) { /* If the option is an extended 'e'ncapsulation (not a * direct 'E'ncapsulation), append the encapsulated space * onto the currently prepared value. */ do { if (oc->option->format && oc->option->format[0] == 'e') { /* Skip forward to the universe name. */ start = strchr(oc->option->format, 'E'); if (start == NULL) break; /* Locate the name-terminating '.'. */ end = strchr(++start, '.'); /* A zero-length name is not allowed in * these kinds of encapsulations. */ if (end == NULL || start == end) break; universe_hash_lookup(&subu, universe_hash, start, end - start, MDL); if (subu == NULL) { log_error("store_option: option %d " "refers to unknown " "option space '%.*s'.", oc->option->code, (int)(end - start), start); break; } /* Append encapsulations, if any. We * already have the prepended values, so * we send those even if there are no * encapsulated options (and ->encapsulate() * returns zero). */ subu->encapsulate(&tmp, packet, lease, client_state, in_options, cfg_options, scope, subu); subu = NULL; } } while (ISC_FALSE); status = append_option(result, universe, oc->option, &tmp); data_string_forget(&tmp, MDL); return status; } 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 = NULL; int status = 0; universe_hash_lookup(&u, universe_hash, (const char *)name->data, name->len, MDL); if (u == NULL) { log_error("option_space_encapsulate: option space '%.*s' does " "not exist, but is configured.", (int)name->len, name->data); return status; } if (u->encapsulate != NULL) { if (u->encapsulate(result, packet, lease, client_state, in_options, cfg_options, scope, u)) status = 1; } else log_error("encapsulation requested for '%s' with no support.", name->data); return status; } /* Attempt to store any 'E'ncapsulated options that have not yet been * placed on the option buffer by the above (configuring a value in * the space over-rides any values in the child universe). * * Note that there are far fewer universes than there will ever be * options in any universe. So it is faster to traverse the * configured universes, checking if each is encapsulated in the * current universe, and if so attempting to do so. * * For each configured universe for this configuration option space, * which is encapsulated within the current universe, can not be found * by the lookup function (the universe-specific encapsulation * functions would already have stored such a value), and encapsulates * at least one option, append it. */ static int search_subencapsulation(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) { struct data_string sub; struct universe *subu; int i, status = 0; memset(&sub, 0, sizeof(sub)); for (i = 0 ; i < cfg_options->universe_count ; i++) { subu = universes[i]; if (subu == NULL) log_fatal("Impossible condition at %s:%d.", MDL); if (subu->enc_opt != NULL && subu->enc_opt->universe == universe && subu->enc_opt->format != NULL && subu->enc_opt->format[0] == 'E' && lookup_option(universe, cfg_options, subu->enc_opt->code) == NULL && subu->encapsulate(&sub, packet, lease, client_state, in_options, cfg_options, scope, subu)) { if (append_option(result, universe, subu->enc_opt, &sub)) status = 1; data_string_forget(&sub, MDL); } } return status; } 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; /* For each hash bucket, and each configured option cache within * that bucket, append the option onto the buffer in encapsulated * format appropriate to the universe. */ 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; } } if (search_subencapsulation(result, packet, lease, client_state, in_options, cfg_options, scope, universe)) 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; 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 [nwip_universe.index]); if (!head) return 0; status = 0; for (ocp = head -> first; ocp; ocp = ocp -> cdr) { 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) { unsigned one = 1; 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); if (!option_code_hash_lookup(&no_nwip->option, nwip_universe.code_hash, &one, 0, MDL)) log_fatal("Nwip option hash does not contain " "1 (%s:%d).", MDL); } 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; } /* We don't want to use MRns_name_pton()...it doesn't tell us how many bytes * it has consumed, and it plays havoc with our escapes. * * So this function does DNS encoding, and returns either the number of * octects consumed (on success), or -1 on failure. */ static int fqdn_encode(unsigned char *dst, int dstlen, const unsigned char *src, int srclen) { unsigned char *out; int i, j, len, outlen=0; out = dst; for (i = 0, j = 0 ; i < srclen ; i = j) { while ((j < srclen) && (src[j] != '.') && (src[j] != '\0')) j++; len = j - i; if ((outlen + 1 + len) > dstlen) return -1; *out++ = len; outlen++; /* We only do one FQDN, ending in one root label. */ if (len == 0) return outlen; memcpy(out, src + i, len); out += len; outlen += len; /* Advance past the root label. */ j++; } if ((outlen + 1) > dstlen) return -1; /* Place the root label. */ *out++ = 0; outlen++; return outlen; } 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]; int status = 1; int 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; /* No need to check the return code, we check the length later */ (void) evaluate_option_cache (&results[oc->option->code], packet, lease, client_state, in_options, cfg_options, scope, oc, MDL); } /* We add a byte for the flags field. * We add two bytes for the two RCODE fields. * We add a byte because we will prepend a label count. * We add a byte because the input len doesn't count null termination, * and we will add a root label. */ len = 5 + 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."); status = 0; goto exit; } buffer_reference (&result -> buffer, bp, MDL); result -> len = 3; result -> data = &bp -> data [0]; memset (&bp -> data [0], 0, len); /* XXX: The server should set bit 4 (yes, 4, not 3) to 1 if it is * not going to perform any ddns updates. The client should set the * bit if it doesn't want the server to perform any updates. * The problem is at this layer of abstraction we have no idea if * the caller is a client or server. * * See RFC4702, Section 3.1, 'The "N" bit'. * * if (?) * bp->data[0] |= 8; */ 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]) { bp->data[0] |= 4; if (results [FQDN_FQDN].len) { i = fqdn_encode(&bp->data[3], len - 3, results[FQDN_FQDN].data, results[FQDN_FQDN].len); if (i < 0) { status = 0; goto exit; } result->len += i; 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; } } exit: for (i = 1; i <= FQDN_SUBOPTION_COUNT; i++) { data_string_forget (&results[i], MDL); } buffer_dereference (&bp, MDL); if (!status) data_string_forget(result, MDL); return status; } /* * Trap invalid attempts to inspect FQND6 contents. */ struct option_cache * lookup_fqdn6_option(struct universe *universe, struct option_state *options, unsigned code) { log_fatal("Impossible condition at %s:%d.", MDL); return NULL; } /* * Trap invalid attempts to save options directly to FQDN6 rather than FQDN. */ void save_fqdn6_option(struct universe *universe, struct option_state *options, struct option_cache *oc, isc_boolean_t appendp) { log_fatal("Impossible condition at %s:%d.", MDL); } /* * Trap invalid attempts to delete an option out of the FQDN6 universe. */ void delete_fqdn6_option(struct universe *universe, struct option_state *options, int code) { log_fatal("Impossible condition at %s:%d.", MDL); } /* Shill to the DHCPv4 fqdn option cache any attempts to traverse the * V6's option cache entry. * * This function is called speculatively by dhclient to setup * environment variables. But it would have already called the * foreach on the normal fqdn universe, so this is superfluous. */ void fqdn6_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 *)) { /* Pretend it is empty. */ return; } /* Turn the FQDN option space into a DHCPv6 FQDN option buffer. */ int fqdn6_option_space_encapsulate(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 option_chain_head *head; struct option_cache *oc; unsigned char *data; int i, len, rval = 0, count; struct data_string results[FQDN_SUBOPTION_COUNT + 1]; if (fqdn_universe.index >= cfg_options->universe_count) return 0; head = ((struct option_chain_head *) cfg_options->universes[fqdn_universe.index]); if (head == NULL) return 0; memset(results, 0, sizeof(results)); for (ocp = head->first ; ocp != NULL ; ocp = ocp->cdr) { oc = (struct option_cache *)(ocp->car); if (oc->option->code > FQDN_SUBOPTION_COUNT) log_fatal("Impossible condition at %s:%d.", MDL); /* No need to check the return code, we check the length later */ (void) evaluate_option_cache(&results[oc->option->code], packet, lease, client_state, in_options, cfg_options, scope, oc, MDL); } /* We add a byte for the flags field at the start of the option. * We add a byte because we will prepend a label count. * We add a byte because the input length doesn't include a trailing * NULL, and we will add a root label. */ len = results[FQDN_FQDN].len + 3; if (!buffer_allocate(&result->buffer, len, MDL)) { log_error("No memory for virtual option buffer."); goto exit; } data = result->buffer->data; result->data = data; /* The first byte is the flags field. */ result->len = 1; data[0] = 0; /* XXX: The server should set bit 3 (yes, 3, not 4) to 1 if we * are not going to perform any DNS updates. The problem is * that at this layer of abstraction, we do not know if the caller * is the client or the server. * * See RFC4704 Section 4.1, 'The "N" bit'. * * if (?) * data[0] |= 4; */ if (results[FQDN_NO_CLIENT_UPDATE].len && results[FQDN_NO_CLIENT_UPDATE].data[0]) data[0] |= 2; if (results[FQDN_SERVER_UPDATE].len && results[FQDN_SERVER_UPDATE].data[0]) data[0] |= 1; /* If there is no name, we're done. */ if (results[FQDN_FQDN].len == 0) { rval = 1; goto exit; } /* Convert textual representation to DNS format. */ count = fqdn_encode(data + 1, len - 1, results[FQDN_FQDN].data, results[FQDN_FQDN].len); if (count < 0) { rval = 0; data_string_forget(result, MDL); goto exit; } result->len += count; result->terminated = 0; /* Success! */ rval = 1; exit: for (i = 1 ; i <= FQDN_SUBOPTION_COUNT ; i++) { data_string_forget(&results[i], MDL); } return rval; } /* Read the DHCPv6 FQDN option's contents into the FQDN virtual space. */ int fqdn6_universe_decode(struct option_state *options, const unsigned char *buffer, unsigned length, struct universe *u) { struct buffer *bp = NULL; unsigned char *first_dot; int len, hlen, dlen; /* The FQDN option has to be at least 1 byte long. */ if (length < 1) return 0; /* Save the contents of the option in a buffer. There are 3 * one-byte values we record from the packet. The input is * DNS encoded and to be safe we'll assume that each character * is non-printable and will be converted to an escaped number: * "\\nnn". Yes, we'll have dead space pretty much all the time * but the alternative is to basically dry run the conversion * first to calculate the precise size or reallocate to a smaller * buffer later, either of which is a bigger performance hit than * just doing a generous allocation. */ unsigned bp_size = 3 + (length * 4); if (!buffer_allocate(&bp, bp_size, MDL)) { log_error("No memory for dhcp6.fqdn option buffer."); return 0; } /* The v6 FQDN is always 'encoded' per DNS. */ bp->data[0] = 1; if (!save_option_buffer(&fqdn_universe, options, bp, bp->data, 1, FQDN_ENCODED, 0)) goto error; /* XXX: We need to process 'The "N" bit'. */ if (buffer[0] & 1) /* server-update. */ bp->data[2] = 1; else bp->data[2] = 0; if (!save_option_buffer(&fqdn_universe, options, bp, bp->data + 2, 1, FQDN_SERVER_UPDATE, 0)) goto error; if (buffer[0] & 2) /* no-client-update. */ bp->data[1] = 1; else bp->data[1] = 0; if (!save_option_buffer(&fqdn_universe, options, bp, bp->data + 1, 1, FQDN_NO_CLIENT_UPDATE, 0)) goto error; /* Convert the domain name to textual representation for config. */ len = MRns_name_ntop(buffer + 1, (char *)bp->data + 3, bp_size - 3); if (len == -1) { log_error("Unable to convert dhcp6.fqdn domain name to " "printable form."); goto error; } /* Save the domain name. */ if (len > 0) { unsigned char *fqdn_start = bp->data + 3; if (!save_option_buffer(&fqdn_universe, options, bp, fqdn_start, len, FQDN_FQDN, 1)) goto error; first_dot = (unsigned char *)strchr((char *)fqdn_start, '.'); if (first_dot != NULL) { hlen = first_dot - fqdn_start; dlen = len - hlen; } else { hlen = len; dlen = 0; } if (!save_option_buffer(&fqdn_universe, options, bp, fqdn_start, len, FQDN_FQDN, 1) || ((hlen > 0) && !save_option_buffer(&fqdn_universe, options, bp, fqdn_start, hlen, FQDN_HOSTNAME, 0)) || ((dlen > 0) && !save_option_buffer(&fqdn_universe, options, bp, first_dot, dlen, FQDN_DOMAINNAME, 0))) goto error; } buffer_dereference(&bp, MDL); return 1; error: buffer_dereference(&bp, MDL); return 0; } 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); 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(struct universe *universe, struct option_state *options, struct option_cache *oc, isc_boolean_t appendp) { pair *tail; struct option_chain_head *head; struct option_cache **ocloc; 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)) { ocloc = (struct option_cache **)&(*tail)->car; if (oc->option->code == (*ocloc)->option->code) { if (appendp) { do { ocloc = &(*ocloc)->next; } while (*ocloc != NULL); } else { option_cache_dereference(ocloc, MDL); } option_cache_reference(ocloc, 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 = 0; pair oc; struct option_chain_head *head; if (universe -> index >= cfg_options -> universe_count) return status; head = ((struct option_chain_head *) cfg_options -> universes [universe -> index]); if (!head) return status; 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; } if (search_subencapsulation(result, packet, lease, client_state, in_options, cfg_options, scope, universe)) 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; unsigned len; unsigned int from_port; struct iaddr from; struct hardware *hfrom; { 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 = NULL; 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) { packet_dereference(&decoded_packet, MDL); log_info("Discarding packet with bogus hlen."); return; } /* Allocate packet->options now so it is non-null for all packets */ decoded_packet->options_valid = 0; if (!option_state_allocate (&decoded_packet->options, MDL)) { packet_dereference(&decoded_packet, MDL); return; } /* 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)) { packet_dereference (&decoded_packet, MDL); return; } 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, NULL, NULL, decoded_packet->options, NULL, NULL, 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 (validate_packet(decoded_packet) != 0) { if (decoded_packet->packet_type) libdhcp_callbacks.dhcp(decoded_packet); else libdhcp_callbacks.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); dmalloc_dump_outstanding(); #endif #if defined (DEBUG_RC_HISTORY_EXHAUSTIVELY) dump_rc_history(0); #endif } int packet6_len_okay(const char *packet, int len) { if (len < 1) { return 0; } if ((packet[0] == DHCPV6_RELAY_FORW) || (packet[0] == DHCPV6_RELAY_REPL)) { if (len >= offsetof(struct dhcpv6_relay_packet, options)) { return 1; } else { return 0; } } else { if (len >= offsetof(struct dhcpv6_packet, options)) { return 1; } else { return 0; } } } #ifdef DHCPv6 void do_packet6(struct interface_info *interface, const char *packet, int len, int from_port, const struct iaddr *from, isc_boolean_t was_unicast) { unsigned char msg_type; const struct dhcpv6_packet *msg; const struct dhcpv6_relay_packet *relay; #ifdef DHCP4o6 const struct dhcpv4_over_dhcpv6_packet *msg46; #endif struct packet *decoded_packet; #if defined (DEBUG_MEMORY_LEAKAGE) unsigned long previous_outstanding = dmalloc_outstanding; #endif if (!packet6_len_okay(packet, len)) { log_info("do_packet6: " "short packet from %s port %d, len %d, dropped", piaddr(*from), from_port, len); return; } decoded_packet = NULL; if (!packet_allocate(&decoded_packet, MDL)) { log_error("do_packet6: no memory for incoming packet."); return; } if (!option_state_allocate(&decoded_packet->options, MDL)) { log_error("do_packet6: no memory for options."); packet_dereference(&decoded_packet, MDL); return; } /* IPv4 information, already set to 0 */ /* decoded_packet->packet_type = 0; */ /* memset(&decoded_packet->haddr, 0, sizeof(decoded_packet->haddr)); */ /* decoded_packet->circuit_id = NULL; */ /* decoded_packet->circuit_id_len = 0; */ /* decoded_packet->remote_id = NULL; */ /* decoded_packet->remote_id_len = 0; */ decoded_packet->raw = (struct dhcp_packet *)packet; decoded_packet->packet_length = (unsigned)len; decoded_packet->client_port = from_port; decoded_packet->client_addr = *from; interface_reference(&decoded_packet->interface, interface, MDL); decoded_packet->unicast = was_unicast; msg_type = packet[0]; if ((msg_type == DHCPV6_RELAY_FORW) || (msg_type == DHCPV6_RELAY_REPL)) { int relaylen = (int)(offsetof(struct dhcpv6_relay_packet, options)); relay = (const struct dhcpv6_relay_packet *)packet; decoded_packet->dhcpv6_msg_type = relay->msg_type; /* relay-specific data */ decoded_packet->dhcpv6_hop_count = relay->hop_count; memcpy(&decoded_packet->dhcpv6_link_address, relay->link_address, sizeof(relay->link_address)); memcpy(&decoded_packet->dhcpv6_peer_address, relay->peer_address, sizeof(relay->peer_address)); if (!parse_option_buffer(decoded_packet->options, relay->options, len - relaylen, &dhcpv6_universe)) { /* no logging here, as parse_option_buffer() logs all cases where it fails */ packet_dereference(&decoded_packet, MDL); return; } #ifdef DHCP4o6 } else if ((msg_type == DHCPV6_DHCPV4_QUERY) || (msg_type == DHCPV6_DHCPV4_RESPONSE)) { int msglen = (int)(offsetof(struct dhcpv4_over_dhcpv6_packet, options)); msg46 = (struct dhcpv4_over_dhcpv6_packet *)packet; decoded_packet->dhcpv6_msg_type = msg46->msg_type; /* message-specific data */ memcpy(decoded_packet->dhcp4o6_flags, msg46->flags, sizeof(decoded_packet->dhcp4o6_flags)); if (!parse_option_buffer(decoded_packet->options, msg46->options, len - msglen, &dhcpv6_universe)) { /* no logging here, as parse_option_buffer() logs all cases where it fails */ packet_dereference(&decoded_packet, MDL); return; } #endif } else { int msglen = (int)(offsetof(struct dhcpv6_packet, options)); msg = (const struct dhcpv6_packet *)packet; decoded_packet->dhcpv6_msg_type = msg->msg_type; /* message-specific data */ memcpy(decoded_packet->dhcpv6_transaction_id, msg->transaction_id, sizeof(decoded_packet->dhcpv6_transaction_id)); if (!parse_option_buffer(decoded_packet->options, msg->options, len - msglen, &dhcpv6_universe)) { /* no logging here, as parse_option_buffer() logs all cases where it fails */ packet_dereference(&decoded_packet, MDL); return; } } libdhcp_callbacks.dhcpv6(decoded_packet); 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); dmalloc_dump_outstanding(); #endif #if defined (DEBUG_RC_HISTORY_EXHAUSTIVELY) dump_rc_history(0); #endif } #endif /* DHCPv6 */ int pretty_escape(char **dst, char *dend, const unsigned char **src, const unsigned char *send) { int count = 0; /* If there aren't as many bytes left as there are in the source * buffer, don't even bother entering the loop. */ if (dst == NULL || dend == NULL || src == NULL || send == NULL || *dst == NULL || *src == NULL || (*dst >= dend) || (*src > send) || ((send - *src) > (dend - *dst))) return -1; for ( ; *src < send ; (*src)++) { if (!isascii (**src) || !isprint (**src)) { /* Skip trailing NUL. */ if ((*src + 1) != send || **src != '\0') { if (*dst + 4 > dend) return -1; sprintf(*dst, "\\%03o", **src); (*dst) += 4; count += 4; } } else if (**src == '"' || **src == '\'' || **src == '$' || **src == '`' || **src == '\\' || **src == '|' || **src == '&') { if (*dst + 2 > dend) return -1; **dst = '\\'; (*dst)++; **dst = **src; (*dst)++; count += 2; } else { if (*dst + 1 > dend) return -1; **dst = **src; (*dst)++; count++; } } return count; } static int pretty_text(char **dst, char *dend, const unsigned char **src, const unsigned char *send, int emit_quotes) { int count; if (dst == NULL || dend == NULL || src == NULL || send == NULL || *dst == NULL || *src == NULL || ((*dst + (emit_quotes ? 2 : 0)) > dend) || (*src > send)) return -1; if (emit_quotes) { **dst = '"'; (*dst)++; } /* dend-1 leaves 1 byte for the closing quote. */ count = pretty_escape(dst, dend - (emit_quotes ? 1 : 0), src, send); if (count == -1) return -1; if (emit_quotes && (*dst < dend)) { **dst = '"'; (*dst)++; /* Includes quote prior to pretty_escape(); */ count += 2; } return count; } static int pretty_dname(char **dst, char *dend, const unsigned char *src, const unsigned char *send) { const unsigned char *tend; const unsigned char *srcp = src; int count = 0; int tsiz, status; if (dst == NULL || dend == NULL || src == NULL || send == NULL || *dst == NULL || ((*dst + 1) > dend) || (src >= send)) return -1; do { /* Continue loop until end of src buffer. */ if (srcp >= send) break; /* Consume tag size. */ tsiz = *srcp; srcp++; /* At root, finis. */ if (tsiz == 0) break; tend = srcp + tsiz; /* If the tag exceeds the source buffer, it's illegal. * This should also trap compression pointers (which should * not be in these buffers). */ if (tend > send) return -1; /* dend-1 leaves room for a trailing dot and quote. */ status = pretty_escape(dst, dend-1, &srcp, tend); if ((status == -1) || ((*dst + 1) > dend)) return -1; **dst = '.'; (*dst)++; count += status + 1; } while(1); return count; } static int pretty_domain(char **dst, char *dend, const unsigned char **src, const unsigned char *send) { const unsigned char *tend; int count = 2; int tsiz, status; if (dst == NULL || dend == NULL || src == NULL || send == NULL || *dst == NULL || *src == NULL || ((*dst + 2) > dend) || (*src >= send)) return -1; **dst = '"'; (*dst)++; do { /* Continue loop until end of src buffer. */ if (*src >= send) break; /* Consume tag size. */ tsiz = **src; (*src)++; /* At root, finis. */ if (tsiz == 0) break; tend = (*src) + tsiz; /* If the tag exceeds the source buffer, it's illegal. * This should also trap compression pointers (which should * not be in these buffers). */ if (tend > send) return -1; /* dend-2 leaves room for a trailing dot and quote. */ status = pretty_escape(dst, dend-2, src, tend); if ((status == -1) || ((*dst + 2) > dend)) return -1; **dst = '.'; (*dst)++; count += status + 1; } while(1); **dst = '"'; (*dst)++; return count; } /* * Add the option identified with the option number and data to the * options state. */ int add_option(struct option_state *options, unsigned int option_num, void *data, unsigned int data_len) { struct option_cache *oc; struct option *option; /* INSIST(options != NULL); */ /* INSIST(data != NULL); */ option = NULL; if (!option_code_hash_lookup(&option, dhcp_universe.code_hash, &option_num, 0, MDL)) { log_error("Attempting to add unknown option %d.", option_num); return 0; } oc = NULL; if (!option_cache_allocate(&oc, MDL)) { log_error("No memory for option cache adding %s (option %d).", option->name, option_num); /* Get rid of reference created during hash lookup. */ option_dereference(&option, MDL); return 0; } if (!make_const_data(&oc->expression, data, data_len, 0, 0, MDL)) { log_error("No memory for constant data adding %s (option %d).", option->name, option_num); /* Get rid of reference created during hash lookup. */ option_dereference(&option, MDL); option_cache_dereference(&oc, MDL); return 0; } option_reference(&(oc->option), option, MDL); save_option(&dhcp_universe, options, oc); option_cache_dereference(&oc, MDL); /* Get rid of reference created during hash lookup. */ option_dereference(&option, MDL); return 1; } /** * Checks if received BOOTP/DHCPv4 packet is sane * * @param packet received, decoded packet * * @return 1 if packet is sane, 0 if it is not */ int validate_packet(struct packet *packet) { struct option_cache *oc = NULL; oc = lookup_option (&dhcp_universe, packet->options, DHO_DHCP_CLIENT_IDENTIFIER); if (oc) { /* Let's check if client-identifier is sane */ if (oc->data.len == 0) { log_debug("Dropped DHCPv4 packet with zero-length client-id"); return (0); } else if (oc->data.len == 1) { /* * RFC2132, section 9.14 states that minimum length of client-id * is 2. We will allow single-character client-ids for now (for * backwards compatibility), but warn the user that support for * this is against the standard. */ log_debug("Accepted DHCPv4 packet with one-character client-id - " "a future version of ISC DHCP will reject this"); } } else { /* * If hlen is 0 we don't have any identifier, we warn the user * but continue processing the packet as we can. */ if (packet->raw->hlen == 0) { log_debug("Received DHCPv4 packet without client-id" " option and empty hlen field."); } } /* @todo: Add checks for other received options */ return (1); } /*! * * \brief Parse a vendor option (option 43) * * After the server has parsed most of the options and presented the result * to the user the user can set the proper vendor option space using * vendor-option-space in the config file and then cause this routine to be * called via parse-vendor-option in the config file. This routine will * then try and find the proper universe for the vendor-option-space and * parse the vendor option string based on that universe. * * If the information isn't available (no vendor space, no universe for the * vendor space, no vendor option in the options) or the decode fails we * simply ignore the option and continue processing. * * \param packet - structure to hold information about the packet being * processed * \param lease - lease structure * \param client_state * \param in_options - The incoming options, we expect to find the * vendor-option (option 43, containing the string * to parse) there. We shall attach decoded options * there. * \param out_options - The options we have added as we process the packet. * We expect to find the vendor-option-space there and * use that to find the name of the vendor universe to use * \param scope * * \return - void as there isn't much we can do about failures. */ void parse_vendor_option(packet, lease, client_state, in_options, out_options, scope) struct packet *packet; struct lease *lease; struct client_state *client_state; struct option_state *in_options; struct option_state *out_options; struct binding_scope **scope; { struct option_cache *oc = NULL; struct data_string name; struct option *option = NULL; unsigned int code = DHO_VENDOR_ENCAPSULATED_OPTIONS; /* check if we are processing a packet, if not we can return */ if ((packet == NULL) || (in_options == NULL) || (out_options == NULL)) return; /* Do we have any vendor option spaces? */ if (vendor_cfg_option == NULL) return; /* See if the admin has set a vendor option space name */ oc = lookup_option(vendor_cfg_option->universe, out_options, vendor_cfg_option->code); if (oc == NULL) return; memset(&name, 0, sizeof(name)); (void) evaluate_option_cache(&name, packet, lease, client_state, in_options, out_options, scope, oc, MDL); /* No name, all done */ if (name.len == 0) { data_string_forget(&name, MDL); return; } /* Get any vendor option information from the request */ oc = lookup_option(&dhcp_universe, in_options, code); /* No vendor option, all done */ if ((oc == NULL) || (oc->data.len == 0)) { data_string_forget(&name, MDL); return; } /* Get the proper option to pass to the parse routine */ option_code_hash_lookup(&option, dhcp_universe.code_hash, &code, 0, MDL); /* Now that we have the data from the vendor option and a vendor * option space try to parse things. On success the parsed options * will be added to the in_options list for future use. A return * return of 1 indicates success, but not much we can do on error */ (void) parse_encapsulated_suboptions(in_options, option, oc->data.data, oc->data.len, &dhcp_universe, (const char *)name.data); /* Lastly clean up any left overs */ data_string_forget(&name, MDL); option_dereference(&option, MDL); return; }