mirror of
https://gitlab.isc.org/isc-projects/dhcp
synced 2025-08-22 18:07:25 +00:00
4057 lines
107 KiB
C
4057 lines
107 KiB
C
/* options.c
|
|
|
|
DHCP options parsing and reassembly. */
|
|
|
|
/*
|
|
* Copyright (c) 2004-2008 by Internet Systems Consortium, Inc. ("ISC")
|
|
* Copyright (c) 1995-2003 by Internet Software Consortium
|
|
*
|
|
* Permission to use, copy, modify, and distribute this software for any
|
|
* purpose with or without fee is hereby granted, provided that the above
|
|
* copyright notice and this permission notice appear in all copies.
|
|
*
|
|
* 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.
|
|
* 950 Charter Street
|
|
* Redwood City, CA 94063
|
|
* <info@isc.org>
|
|
* http://www.isc.org/
|
|
*
|
|
* This software has been written for Internet Systems Consortium
|
|
* by Ted Lemon in cooperation with Vixie Enterprises and Nominum, Inc.
|
|
* To learn more about Internet Systems Consortium, see
|
|
* ``http://www.isc.org/''. To learn more about Vixie Enterprises,
|
|
* see ``http://www.vix.com''. To learn more about Nominum, Inc., see
|
|
* ``http://www.nominum.com''.
|
|
*/
|
|
|
|
#define DHCP_OPTION_DATA
|
|
#include "dhcpd.h"
|
|
#include <omapip/omapip_p.h>
|
|
#include <limits.h>
|
|
|
|
struct option *vendor_cfg_option;
|
|
|
|
static int pretty_text(char **, char *, const unsigned char **,
|
|
const unsigned char *, int);
|
|
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. */
|
|
|
|
int parse_options (packet)
|
|
struct packet *packet;
|
|
{
|
|
struct option_cache *op = (struct option_cache *)0;
|
|
|
|
/* Allocate a new option state. */
|
|
if (!option_state_allocate (&packet -> options, MDL)) {
|
|
packet -> options_valid = 0;
|
|
return 0;
|
|
}
|
|
|
|
/* If we don't see the magic cookie, there's nothing to parse. */
|
|
if (memcmp (packet -> raw -> options, DHCP_OPTIONS_COOKIE, 4)) {
|
|
packet -> options_valid = 0;
|
|
return 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 = (struct buffer *)0;
|
|
struct option *option = NULL;
|
|
|
|
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; ) {
|
|
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) {
|
|
len = 65536;
|
|
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;
|
|
|
|
option_code_hash_lookup(&option, universe->code_hash, &code,
|
|
0, MDL);
|
|
|
|
/* If the length is outrageous, the options are bad. */
|
|
if (offset + len > length) {
|
|
bogus:
|
|
log_error ("parse_option_buffer: option %s (%u:%u) %s.",
|
|
option ? option->name : "<unknown>",
|
|
code, len, "larger than buffer");
|
|
buffer_dereference (&bp, MDL);
|
|
return 0;
|
|
}
|
|
|
|
/* If the option contains an encapsulation, parse it. If
|
|
the parse fails, or the option isn't an encapsulation (by
|
|
far the most common case), or the option isn't entirely
|
|
an encapsulation, keep the raw data as well. */
|
|
if (!(option &&
|
|
(option->format[0] == 'e' ||
|
|
option->format[0] == 'E') &&
|
|
(parse_encapsulated_suboptions(options, option,
|
|
bp->data + offset, len,
|
|
universe, NULL)))) {
|
|
op = lookup_option(universe, options, code);
|
|
|
|
if (op != NULL && universe->concat_duplicates) {
|
|
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);
|
|
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 if (op != NULL) {
|
|
/* 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);
|
|
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);
|
|
} else {
|
|
save_option_buffer(universe, options, bp,
|
|
bp->data + offset, len,
|
|
code, 1);
|
|
}
|
|
}
|
|
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 ("fancy bits in fqdn option");
|
|
return 0;
|
|
}
|
|
if (len == 0) {
|
|
terminated = 1;
|
|
break;
|
|
}
|
|
if (s + len > &bp -> data [0] + length + 3) {
|
|
log_info ("fqdn tag longer than buffer");
|
|
return 0;
|
|
}
|
|
|
|
if (first_len == 0) {
|
|
first_len = len;
|
|
}
|
|
|
|
*s = '.';
|
|
s += len + 1;
|
|
total_len += len + 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.
|
|
*/
|
|
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);
|
|
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 - DHCP_FIXED_LEN >= 64))
|
|
mb_size = inpacket->packet_length - DHCP_FIXED_LEN;
|
|
} 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
|
|
*/
|
|
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.
|
|
*/
|
|
priority_len = 0;
|
|
priority_list[priority_len++] = DHO_DHCP_MESSAGE_TYPE;
|
|
priority_list[priority_len++] = DHO_DHCP_SERVER_IDENTIFIER;
|
|
priority_list[priority_len++] = DHO_DHCP_LEASE_TIME;
|
|
priority_list[priority_len++] = DHO_DHCP_MESSAGE;
|
|
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;
|
|
}
|
|
|
|
data_string_truncate(prl, (PRIORITY_COUNT - priority_len));
|
|
|
|
for (i = 0; i < prl->len; i++) {
|
|
/*
|
|
* Prevent client from changing order of delivery
|
|
* of relay agent information option.
|
|
*/
|
|
if (prl->data[i] != DHO_DHCP_AGENT_OPTIONS)
|
|
priority_list[priority_len++] = prl->data[i];
|
|
}
|
|
|
|
/*
|
|
* 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 ((priority_len < PRIORITY_COUNT) &&
|
|
(lookup_option(&dhcp_universe, inpacket->options,
|
|
DHO_FQDN) != 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 ((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);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* 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)) {
|
|
if ((ds.len + 4) <=
|
|
(buflen - bufpos)) {
|
|
tmp = (unsigned char *)buf;
|
|
tmp += bufpos;
|
|
/* option tag */
|
|
putUShort(tmp,
|
|
required_opts[i]);
|
|
/* option length */
|
|
putUShort(tmp+2, ds.len);
|
|
/* option data */
|
|
memcpy(tmp+4, ds.data, ds.len);
|
|
/* update position */
|
|
bufpos += (4 + ds.len);
|
|
} else {
|
|
log_debug("No space for "
|
|
"option %d",
|
|
required_opts[i]);
|
|
}
|
|
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; i<oro_size; i++) {
|
|
memcpy(&code, oro->data+(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;
|
|
}
|
|
|
|
/*
|
|
* See if this is the VSIO option.
|
|
*/
|
|
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));
|
|
for (; oc != NULL ; oc = oc->next) {
|
|
if (evaluate_option_cache(&ds, packet, NULL, NULL,
|
|
opt_state, NULL,
|
|
&global_scope, oc, MDL)) {
|
|
if ((ds.len + 4) <= (buflen - bufpos)) {
|
|
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);
|
|
} else {
|
|
log_debug("No space for option %d",
|
|
code);
|
|
}
|
|
data_string_forget(&ds, MDL);
|
|
} else {
|
|
log_error("Error evaluating option %d", code);
|
|
}
|
|
}
|
|
}
|
|
|
|
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 - 1; 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--) {
|
|
/* We know that anything before 'i' can only
|
|
* appear once. So shovel the options to make
|
|
* room to bubble the subnet mask ahead, and
|
|
* then break out of the loop, we're done.
|
|
*/
|
|
if (priority_list[ix] == DHO_ROUTERS) {
|
|
memmove(priority_list + ix + 1,
|
|
priority_list + ix, i - ix);
|
|
priority_list[ix] = DHO_SUBNET_MASK;
|
|
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)
|
|
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) {
|
|
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 'd':
|
|
case 't':
|
|
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':
|
|
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. */
|
|
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. */
|
|
|
|
const char *pretty_print_option (option, data, len, emit_commas, emit_quotes)
|
|
struct option *option;
|
|
const unsigned char *data;
|
|
unsigned len;
|
|
int emit_commas;
|
|
int emit_quotes;
|
|
{
|
|
static char optbuf [32768]; /* XXX */
|
|
static char *endbuf = &optbuf[sizeof(optbuf)];
|
|
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;
|
|
|
|
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':
|
|
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;
|
|
}
|
|
fmtbuf [l + 1] = 0;
|
|
break;
|
|
case 'd':
|
|
fmtbuf[l] = 't';
|
|
/* Fall Through ! */
|
|
case 't':
|
|
case 'D':
|
|
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 "<error>";
|
|
}
|
|
/* 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)
|
|
numhunk = len / hunksize;
|
|
/* See if we got an exact number of hunks. */
|
|
if (numhunk > 0 && numhunk * hunksize < len)
|
|
log_error ("%s: %d extra bytes at end of array\n",
|
|
option -> name,
|
|
len - numhunk * hunksize);
|
|
|
|
/* A one-hunk array prints the same as a single hunk. */
|
|
if (numhunk < 0)
|
|
numhunk = 1;
|
|
|
|
/* Cycle through the array (or hunk) printing the data. */
|
|
for (i = 0; i < numhunk; i++) {
|
|
for (j = 0; j < numelem; j++) {
|
|
switch (fmtbuf [j]) {
|
|
case 't':
|
|
/* 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 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
|
|
* ns_name_ntop().
|
|
*/
|
|
if (emit_quotes) {
|
|
nbp = nbuff;
|
|
pretty_domain(&op, endbuf-1,
|
|
&nbp, nend);
|
|
} else {
|
|
/* ns_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 "<double impossible condition>";
|
|
}
|
|
|
|
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 (dp == data + len)
|
|
break;
|
|
if (j + 1 < numelem && comma != ':')
|
|
*op++ = ' ';
|
|
}
|
|
if (i + 1 < numhunk) {
|
|
*op++ = comma;
|
|
}
|
|
if (dp == data + len)
|
|
break;
|
|
}
|
|
return optbuf;
|
|
}
|
|
|
|
int get_option (result, universe, packet, lease, client_state,
|
|
in_options, cfg_options, options, scope, code, file, line)
|
|
struct data_string *result;
|
|
struct universe *universe;
|
|
struct packet *packet;
|
|
struct lease *lease;
|
|
struct client_state *client_state;
|
|
struct option_state *in_options;
|
|
struct option_state *cfg_options;
|
|
struct option_state *options;
|
|
struct binding_scope **scope;
|
|
unsigned code;
|
|
const char *file;
|
|
int line;
|
|
{
|
|
struct option_cache *oc;
|
|
|
|
if (!universe -> lookup_func)
|
|
return 0;
|
|
oc = ((*universe -> lookup_func) (universe, options, code));
|
|
if (!oc)
|
|
return 0;
|
|
if (!evaluate_option_cache (result, packet, lease, client_state,
|
|
in_options, cfg_options, scope, oc,
|
|
file, line))
|
|
return 0;
|
|
return 1;
|
|
}
|
|
|
|
void set_option (universe, options, option, op)
|
|
struct universe *universe;
|
|
struct option_state *options;
|
|
struct option_cache *option;
|
|
enum statement_op op;
|
|
{
|
|
struct option_cache *oc, *noc;
|
|
|
|
switch (op) {
|
|
case if_statement:
|
|
case add_statement:
|
|
case eval_statement:
|
|
case break_statement:
|
|
default:
|
|
log_error ("bogus statement type in 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;
|
|
}
|
|
}
|
|
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:
|
|
option_dereference(&option, MDL);
|
|
|
|
return 1;
|
|
}
|
|
|
|
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 ns_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;
|
|
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++) {
|
|
if (results [i].len)
|
|
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);
|
|
|
|
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++) {
|
|
if (result[i].len)
|
|
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, so we go ahead
|
|
* and allocate a bigger buffer to accommodate them. But the
|
|
* 'length' we got (because it is a DNS encoded string) is
|
|
* one longer than we need...so we only add two extra octets.
|
|
*/
|
|
if (!buffer_allocate(&bp, length + 2, 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, length - 1);
|
|
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 = (struct packet *)0;
|
|
if (!packet_allocate (&decoded_packet, MDL)) {
|
|
log_error ("do_packet: no memory for incoming packet!");
|
|
return;
|
|
}
|
|
decoded_packet -> raw = packet;
|
|
decoded_packet -> packet_length = len;
|
|
decoded_packet -> client_port = from_port;
|
|
decoded_packet -> client_addr = from;
|
|
interface_reference (&decoded_packet -> interface, interface, MDL);
|
|
decoded_packet -> haddr = hfrom;
|
|
|
|
if (packet -> hlen > sizeof packet -> chaddr) {
|
|
packet_dereference (&decoded_packet, MDL);
|
|
log_info ("Discarding packet with bogus hlen.");
|
|
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)) {
|
|
if (decoded_packet -> options)
|
|
option_state_dereference
|
|
(&decoded_packet -> options, MDL);
|
|
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,
|
|
(struct lease *)0,
|
|
(struct client_state *)0,
|
|
decoded_packet -> options,
|
|
(struct option_state *)0,
|
|
(struct binding_scope **)0,
|
|
op, MDL);
|
|
if (dp.len > 0)
|
|
decoded_packet -> packet_type = dp.data [0];
|
|
else
|
|
decoded_packet -> packet_type = 0;
|
|
data_string_forget (&dp, MDL);
|
|
}
|
|
}
|
|
|
|
if (decoded_packet -> packet_type)
|
|
dhcp (decoded_packet);
|
|
else
|
|
bootp (decoded_packet);
|
|
|
|
/* If the caller kept the packet, they'll have upped the refcnt. */
|
|
packet_dereference (&decoded_packet, MDL);
|
|
|
|
#if defined (DEBUG_MEMORY_LEAKAGE)
|
|
log_info ("generation %ld: %ld new, %ld outstanding, %ld long-term",
|
|
dmalloc_generation,
|
|
dmalloc_outstanding - previous_outstanding,
|
|
dmalloc_outstanding, dmalloc_longterm);
|
|
#endif
|
|
#if defined (DEBUG_MEMORY_LEAKAGE)
|
|
dmalloc_dump_outstanding ();
|
|
#endif
|
|
#if defined (DEBUG_RC_HISTORY_EXHAUSTIVELY)
|
|
dump_rc_history (0);
|
|
#endif
|
|
}
|
|
|
|
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 >= sizeof(struct dhcpv6_relay_packet)) {
|
|
return 1;
|
|
} else {
|
|
return 0;
|
|
}
|
|
} else {
|
|
if (len >= sizeof(struct dhcpv6_packet)) {
|
|
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;
|
|
struct packet *decoded_packet;
|
|
|
|
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)) {
|
|
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-sizeof(*relay),
|
|
&dhcpv6_universe)) {
|
|
/* no logging here, as parse_option_buffer() logs all
|
|
cases where it fails */
|
|
packet_dereference(&decoded_packet, MDL);
|
|
return;
|
|
}
|
|
} else {
|
|
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-sizeof(*msg),
|
|
&dhcpv6_universe)) {
|
|
/* no logging here, as parse_option_buffer() logs all
|
|
cases where it fails */
|
|
packet_dereference(&decoded_packet, MDL);
|
|
return;
|
|
}
|
|
}
|
|
|
|
dhcpv6(decoded_packet);
|
|
|
|
packet_dereference(&decoded_packet, MDL);
|
|
}
|
|
#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 == '\\') {
|
|
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_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);
|
|
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);
|
|
option_cache_dereference(&oc, MDL);
|
|
return 0;
|
|
}
|
|
|
|
option_reference(&(oc->option), option, MDL);
|
|
save_option(&dhcp_universe, options, oc);
|
|
option_cache_dereference(&oc, MDL);
|
|
|
|
return 1;
|
|
}
|
|
|
|
|