]> git.ipfire.org Git - thirdparty/dhcp.git/commitdiff
- Make option spaces stored as linked lists first-class objects.
authorTed Lemon <source@isc.org>
Tue, 10 Oct 2000 22:39:47 +0000 (22:39 +0000)
committerTed Lemon <source@isc.org>
Tue, 10 Oct 2000 22:39:47 +0000 (22:39 +0000)
- Use these spaces for the agent option space, fqdn option space and
  nwip option space.
- Make encapsulator and decoder for fqdn option space.
- Support the agent option space the way all option spaces are supported
  to the extent possibol, rather than special-casing it.
- Add an enumerator function for defined options in an option space.
- Support encapsulated options directly with a format string, rather than
  as another special case.

common/options.c

index 637a337386e21a36b7891930e9a4ed9af9734eda..54b96431318ff864a3be3d92e312a1f906b6e383 100644 (file)
 
 #ifndef lint
 static char copyright[] =
-"$Id: options.c,v 1.65 2000/09/29 19:58:24 mellon Exp $ Copyright (c) 1995-2000 The Internet Software Consortium.  All rights reserved.\n";
+"$Id: options.c,v 1.66 2000/10/10 22:39:47 mellon Exp $ Copyright (c) 1995-2000 The Internet Software Consortium.  All rights reserved.\n";
 #endif /* not lint */
 
 #define DHCP_OPTION_DATA
 #include "dhcpd.h"
 #include <omapip/omapip_p.h>
 
+struct option *vendor_cfg_option;
+
 static void do_option_set PROTO ((pair *,
                                  struct option_cache *,
                                  enum statement_op));
@@ -76,9 +78,11 @@ int parse_options (packet)
 
        /* Go through the options field, up to the end of the packet
           or the End field. */
-       if (!parse_option_buffer (packet, &packet -> raw -> options [4],
+       if (!parse_option_buffer (packet -> options,
+                                 &packet -> raw -> options [4],
                                  (packet -> packet_length -
-                                  DHCP_FIXED_NON_UDP - 4)))
+                                  DHCP_FIXED_NON_UDP - 4),
+                                 &dhcp_universe))
                return 0;
 
        /* If we parsed a DHCP Option Overload option, parse more
@@ -88,18 +92,22 @@ int parse_options (packet)
                                 DHO_DHCP_OPTION_OVERLOAD))) {
                if (op -> data.data [0] & 1) {
                        if (!parse_option_buffer
-                           (packet, (unsigned char *)packet -> raw -> file,
-                            sizeof packet -> raw -> file))
+                           (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,
+                           (packet -> options,
                             (unsigned char *)packet -> raw -> sname,
-                            sizeof packet -> raw -> sname))
+                            sizeof packet -> raw -> sname,
+                            &dhcp_universe))
                                return 0;
                }
        }
+       packet -> options_valid = 1;
        return 1;
 }
 
@@ -107,14 +115,15 @@ int parse_options (packet)
    values in packet -> options and setting packet -> options_valid if no
    errors are encountered. */
 
-int parse_option_buffer (packet, buffer, length)
-       struct packet *packet;
+int parse_option_buffer (options, buffer, length, universe)
+       struct option_state *options;
        unsigned char *buffer;
        unsigned length;
+       struct universe *universe;
 {
        unsigned char *t;
        unsigned char *end = buffer + length;
-       int len, offset;
+       unsigned len, offset;
        int code;
        struct option_cache *op = (struct option_cache *)0;
        struct buffer *bp = (struct buffer *)0;
@@ -145,51 +154,186 @@ int parse_option_buffer (packet, buffer, length)
                        return 0;
                }
 
-               /* If this is a Relay Agent Information option, we must
-                  handle it specially. */
-               if (code == DHO_DHCP_AGENT_OPTIONS) {
-                       if (!parse_agent_information_option
-                           (packet, len, buffer + offset + 2)) {
-                               log_error ("bad agent information option.");
-                               buffer_dereference (&bp, MDL);
+               /* If the option contains an encapsulation, parse it.   If
+                  the parse fails, or the option isn't an encapsulation (by
+                  far the most common case), or the option isn't entirely
+                  an encapsulation, keep the raw data as well. */
+               if (!((universe -> options [code] -> format [0] == 'e' ||
+                      universe -> options [code] -> format [0] == 'E') &&
+                     (parse_encapsulated_suboptions
+                      (options, universe -> options [code],
+                       buffer + offset + 2, len,
+                       universe, (struct universe *)0)))) {
+                   save_option_buffer (universe, options, bp,
+                                       &bp -> data [offset + 2], len,
+                                       universe -> options [code], 1);
+               }
+               offset += len + 2;
+       }
+       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. */
+
+int parse_encapsulated_suboptions (struct option_state *options,
+                                  struct option *eopt,
+                                  unsigned char *buffer,
+                                  unsigned len, struct universe *eu,
+                                  struct universe *vu)
+{
+       struct universe *universe;
+       int i;
+       char *s, *t;
+
+       /* 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) {
+               /* It's the vendor universe. */
+               universe = vu;
+       } else {
+               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;
+                       }
+               }
+       }
+
+       /* 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 (s != eopt -> format)
+               return 0;
+       /* Otherwise, return the status of the decode function. */
+       return i;
+}
+
+int fqdn_universe_decode (struct option_state *options,
+                         unsigned char *buffer,
+                         unsigned length, struct universe *u)
+{
+       char *name;
+       struct buffer *bp = (struct buffer *)0;
+
+       /* FQDN options have to be at least four bytes long. */
+       if (length < 4)
+               return 0;
+
+       /* Save the contents of the option in a buffer. */
+       if (!buffer_allocate (&bp, length + 4, MDL)) {
+               log_error ("no memory for option buffer.");
+               return 0;
+       }
+       memcpy (&bp -> data [3], buffer + 1, length - 1);
+
+       if (buffer [0] & 4)     /* encoded */
+               bp -> data [0] = 1;
+       else
+               bp -> data [0] = 0;
+       if (!save_option_buffer (&fqdn_universe, options, bp,
+                                &bp -> data [0], 1,
+                                &fqdn_options [FQDN_ENCODED], 0)) {
+             bad:
+               buffer_dereference (&bp, MDL);
+               return 0;
+       }
+
+       if (buffer [0] & 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_options [FQDN_NO_CLIENT_UPDATE], 0))
+           goto bad;
+
+       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_options [FQDN_SERVER_UPDATE], 0))
+               goto bad;
+
+       if (!save_option_buffer (&fqdn_universe, options, bp,
+                                &bp -> data [3], 1,
+                                &fqdn_options [FQDN_RCODE1], 0))
+               goto bad;
+       if (!save_option_buffer (&fqdn_universe, options, bp,
+                                &bp -> data [4], 1,
+                                &fqdn_options [FQDN_RCODE2], 0))
+               goto bad;
+
+       /* 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]) {
+               if (!save_option_buffer (&fqdn_universe, options, bp,
+                                        &bp -> data [5], length - 3,
+                                        &fqdn_options [FQDN_NAME], 1))
+                       goto bad;
+       } else {
+               int len;
+               unsigned char *s, *t, *u;
+
+               u = t = s = &bp -> data [5];
+               do {
+                       len = *s++;
+                       if (len > 63) {
+                               log_info ("fancy bits in fqdn option");
                                return 0;
+                       }       
+                       if (len == 0) {
+                               *t++ = '.';
+                               break;
                        }
-               } else {
-                       if (!option_cache_allocate (&op, MDL)) {
-                               log_error ("No memory for option %s.",
-                                          dhcp_options [code].name);
-                               buffer_dereference (&bp, MDL);
+                       if (s + len > &bp -> data [0] + length + 3) {
+                               log_info ("fqdn tag longer than buffer");
                                return 0;
                        }
-
-                       /* 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 = &bp -> data [offset + 2];
-                       op -> data.len = len;
-                       
-                       /* NUL terminate (we can get away with this
-                          because we 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 we're getting out of
-                          the *original* buffer. */
-                       bp -> data [offset + 2 + len] = 0;
-                       op -> data.terminated = 1;
-
-                       op -> option = &dhcp_options [code];
-                       /* Now store the option. */
-                       save_option (&dhcp_universe, packet -> options, op);
-
-                       /* And let go of our reference. */
-                       option_cache_dereference (&op, MDL);
-               }
-               offset += len + 2;
+                       while (len--)
+                               *t++ = *s++;
+                       *t++ = '.';
+               } while (s + len < &bp -> data [0] + length + 3);
+               if (!save_option_buffer (&fqdn_universe, options, bp,
+                                        &bp -> data [5], (unsigned)(t - u),
+                                        &fqdn_options [FQDN_NAME], 1))
+                       goto bad;
        }
-       packet -> options_valid = 1;
-       buffer_dereference (&bp, MDL);
        return 1;
 }
 
@@ -198,7 +342,7 @@ int parse_option_buffer (packet, buffer, length)
    of vendor options using the same routine. */
 
 int cons_options (inpacket, outpacket, lease, mms, in_options, cfg_options,
-                 scope, overload, terminate, bootpp, prl)
+                 scope, overload, terminate, bootpp, prl, vuname)
        struct packet *inpacket;
        struct dhcp_packet *outpacket;
        struct lease *lease;
@@ -210,6 +354,7 @@ int cons_options (inpacket, outpacket, lease, mms, in_options, cfg_options,
        int terminate;
        int bootpp;
        struct data_string *prl;
+       const char *vuname;
 {
 #define PRIORITY_COUNT 300
        unsigned priority_list [PRIORITY_COUNT];
@@ -278,8 +423,13 @@ int cons_options (inpacket, outpacket, lease, mms, in_options, cfg_options,
        if (prl && prl -> len > 0) {
                data_string_truncate (prl, (PRIORITY_COUNT - priority_len));
 
-               for (i = 0; i < prl -> len; i++)
-                       priority_list [priority_len++] = prl -> data [i];
+               for (i = 0; i < prl -> len; i++) {
+                       /* Prevent client from changing order of delivery
+                          of relay agent information option. */
+                       if (prl -> data [i] != DHO_DHCP_AGENT_OPTIONS)
+                               priority_list [priority_len++] =
+                                       prl -> data [i];
+               }
        } else {
                /* First, hardcode some more options that ought to be
                   sent first... */
@@ -302,7 +452,9 @@ int cons_options (inpacket, outpacket, lease, mms, in_options, cfg_options,
                                op = (struct option_cache *)(pp -> car);
                                if (op -> option -> code <
                                    cfg_options -> site_code_min &&
-                                   priority_len < PRIORITY_COUNT)
+                                   priority_len < PRIORITY_COUNT &&
+                                   (op -> option -> code !=
+                                    DHO_DHCP_AGENT_OPTIONS))
                                        priority_list [priority_len++] =
                                                op -> option -> code;
                        }
@@ -319,11 +471,36 @@ int cons_options (inpacket, outpacket, lease, mms, in_options, cfg_options,
                                op = (struct option_cache *)(pp -> car);
                                if (op -> option -> code >=
                                    cfg_options -> site_code_min &&
-                                   priority_len < PRIORITY_COUNT)
+                                   priority_len < PRIORITY_COUNT &&
+                                   (op -> option -> code !=
+                                    DHO_DHCP_AGENT_OPTIONS))
                                        priority_list [priority_len++] =
                                                op -> option -> code;
                        }
+               }
+
+               /* Now go through all the universes for which options
+                  were set and see if there are encapsulations for
+                  them; if there are, put the encapsulation options
+                  on the priority list as well. */
+               for (i = 0; i < cfg_options -> universe_count; i++) {
+                   if (cfg_options -> universes [i] &&
+                       universes [i] -> enc_opt &&
+                       priority_len < PRIORITY_COUNT &&
+                       universes [i] -> enc_opt -> universe == &dhcp_universe)
+                   {
+                           if (universes [i] -> enc_opt -> code !=
+                               DHO_DHCP_AGENT_OPTIONS)
+                                   priority_list [priority_len++] =
+                                           universes [i] -> enc_opt -> code;
                    }
+               }
+
+               /* The vendor option space can't stand on its own, so always
+                  add it to the list. */
+               if (priority_len < PRIORITY_COUNT)
+                       priority_list [priority_len++] =
+                               DHO_VENDOR_ENCAPSULATED_OPTIONS;
        }
 
        /* Copy the options into the big buffer... */
@@ -338,7 +515,7 @@ int cons_options (inpacket, outpacket, lease, mms, in_options, cfg_options,
                                     main_buffer_size,
                                     (main_buffer_size +
                                      ((overload & 1) ? DHCP_FILE_LEN : 0)),
-                                    terminate);
+                                    terminate, vuname);
 
        /* Put the cookie up front... */
        memcpy (outpacket -> options, DHCP_OPTIONS_COOKIE, 4);
@@ -403,17 +580,25 @@ int cons_options (inpacket, outpacket, lease, mms, in_options, cfg_options,
                }
        }
 
-       length = cons_agent_information_options (cfg_options,
-                                                outpacket, agentix, length);
-               
+       /* Now hack in the agent options if there are any. */
+       priority_list [0] = DHO_DHCP_AGENT_OPTIONS;
+       priority_len = 1;
+       agentix +=
+               store_options (&outpacket -> options [agentix],
+                              1500 - DHCP_FIXED_LEN - agentix,
+                              inpacket, lease, 
+                              in_options, cfg_options, scope,
+                              priority_list, priority_len,
+                              1500 - DHCP_FIXED_LEN - agentix,
+                              1500 - DHCP_FIXED_LEN - agentix, 0, (char *)0);
        return length;
 }
 
 /* Store all the requested options into the requested buffer. */
 
 int store_options (buffer, buflen, packet, lease,
-                  in_options, cfg_options, scope, priority_list,
-                  priority_len, first_cutoff, second_cutoff, terminate)
+                  in_options, cfg_options, scope, priority_list, priority_len,
+                  first_cutoff, second_cutoff, terminate, vuname)
        unsigned char *buffer;
        unsigned buflen;
        struct packet *packet;
@@ -425,6 +610,7 @@ int store_options (buffer, buflen, packet, lease,
        int priority_len;
        unsigned first_cutoff, second_cutoff;
        int terminate;
+       const char *vuname;
 {
        int bufix = 0;
        int i;
@@ -432,6 +618,8 @@ int store_options (buffer, buflen, packet, lease,
        int tto;
        struct data_string od;
        struct option_cache *oc;
+       unsigned code;
+       int optstart;
 
        memset (&od, 0, sizeof od);
 
@@ -455,93 +643,187 @@ int store_options (buffer, buflen, packet, lease,
        /* Copy out the options in the order that they appear in the
           priority list... */
        for (i = 0; i < priority_len; i++) {
-               /* Code for next option to try to store. */
-               unsigned code = priority_list [i];
-               int optstart;
-
-               /* Number of bytes left to store (some may already
-                  have been stored by a previous pass). */
-               int length;
-
-               /* 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)
-                       oc = lookup_option
-                               (universes [cfg_options -> site_universe],
-                                cfg_options, code);
-               else
-                       oc = lookup_option (&dhcp_universe, cfg_options, code);
-
-               /* If no data is available for this option, skip it. */
-               if (!oc) {
-                       continue;
+           /* Number of bytes left to store (some may already
+              have been stored by a previous pass). */
+           unsigned length;
+           int optstart;
+           struct universe *u;
+           int have_encapsulation = 0;
+           struct data_string encapsulation;
+
+           /* 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);
+
+           /* It's an encapsulation, try to find the universe
+              to be encapsulated first, except that if it's a straight
+              encapsulation and the user has provided a value for the
+              encapsulation option, use the user-provided value. */
+           if ((u -> options [code] -> format [0] == 'E' && !oc) ||
+               u -> options [code] -> format [0] == 'e') {
+               int uix;
+               static char *s, *t;
+               struct option_cache *tmp;
+               struct data_string name;
+
+               s = strchr (u -> options [code] -> 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,
+                                                      in_options,
+                                                      cfg_options,
+                                                      scope, tmp, MDL);
+                       } else if (vuname) {
+                           name.data = s;
+                           name.len = strlen (s);
+                       }
+                   } else {
+                       name.data = 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) {
+                       memset (&encapsulation, 0, sizeof encapsulation);
+                       have_encapsulation =
+                               (option_space_encapsulate
+                                (&encapsulation, packet, lease,
+                                 in_options, cfg_options, scope, &name));
+                       data_string_forget (&name, MDL);
+                   }
                }
-
-               /* Find the value of the option... */
+           }
+
+           /* 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... */
+           if (oc) {
                evaluate_option_cache (&od, packet, lease, in_options,
                                       cfg_options, scope, oc, MDL);
                if (!od.len) {
-                       continue;
-               }
-
-               /* We should now have a constant length for the option. */
-               length = od.len;
-
-               /* Do we add a NUL? */
-               if (terminate && dhcp_options [code].format [0] == 't') {
-                       length++;
-                       tto = 1;
-               } else {
-                       tto = 0;
-               }
-
-               /* Try to store the option. */
-
-               /* If the option's length is more than 255, we must store it
-                  in multiple hunks.   Store 255-byte hunks first.  However,
-                  in any case, if the option data will cross a buffer
-                  boundary, split it across that boundary. */
-
-               ix = 0;
-
-               optstart = bufix;
-               while (length) {
-                       unsigned char incr = length > 255 ? 255 : length;
-
-                       /* If this hunk of the buffer will cross a
-                          boundary, only go up to the boundary in this
-                          pass. */
-                       if (bufix < first_cutoff &&
-                           bufix + incr > first_cutoff)
-                               incr = first_cutoff - bufix;
-                       else if (bufix < second_cutoff &&
-                                bufix + incr > second_cutoff)
-                               incr = second_cutoff - bufix;
-
-                       /* If this option is going to overflow the buffer,
-                          skip it. */
-                       if (bufix + 2 + incr > buflen) {
-                               bufix = optstart;
-                               break;
-                       }
-
-                       /* Everything looks good - copy it in! */
-                       buffer [bufix] = code;
-                       buffer [bufix + 1] = incr;
-                       if (tto && incr == length) {
-                               memcpy (buffer + bufix + 2,
-                                       od.data + ix, (unsigned)(incr - 1));
-                               buffer [bufix + 2 + incr - 1] = 0;
-                       } else {
-                               memcpy (buffer + bufix + 2,
-                                       od.data + ix, (unsigned)incr);
-                       }
-                       length -= incr;
-                       ix += incr;
-                       bufix += 2 + incr;
+                   option_cache_dereference (&oc, MDL);
+                   data_string_forget (&encapsulation, MDL);
+                   continue;
                }
-               data_string_forget (&od, MDL);
+           }
+
+           /* We should now have a constant length for the option. */
+           length = od.len;
+           if (have_encapsulation) {
+                   length += encapsulation.len;
+                   if (!od.len) {
+                           data_string_copy (&od, &encapsulation, MDL);
+                           data_string_forget (&encapsulation, MDL);
+                   } else {
+                           struct buffer *bp = (struct buffer *)0;
+                           if (!buffer_allocate (&bp, length, MDL)) {
+                                   option_cache_dereference (&oc, MDL);
+                                   data_string_forget (&od, MDL);
+                                   data_string_forget (&encapsulation, MDL);
+                                   continue;
+                           }
+                           memcpy (&bp -> data [0], od.data, od.len);
+                           memcpy (&bp -> data [od.len], encapsulation.data,
+                                   encapsulation.len);
+                           data_string_forget (&od, MDL);
+                           data_string_forget (&encapsulation, MDL);
+                           od.data = &bp -> data [0];
+                           buffer_reference (&od.buffer, bp, MDL);
+                           buffer_dereference (&bp, MDL);
+                           od.len = length;
+                           od.terminated = 0;
+                   }
+           }
+
+           /* Do we add a NUL? */
+           if (terminate && dhcp_options [code].format [0] == 't') {
+                   length++;
+                   tto = 1;
+           } else {
+                   tto = 0;
+           }
+
+           /* Try to store the option. */
+           
+           /* If the option's length is more than 255, we must store it
+              in multiple hunks.   Store 255-byte hunks first.  However,
+              in any case, if the option data will cross a buffer
+              boundary, split it across that boundary. */
+
+           ix = 0;
+           optstart = bufix;
+           while (length) {
+                   unsigned char incr = length > 255 ? 255 : length;
+                   int consumed = 0;
+                   
+                   /* If this hunk of the buffer will cross a
+                      boundary, only go up to the boundary in this
+                      pass. */
+                   if (bufix < first_cutoff &&
+                       bufix + incr > first_cutoff)
+                           incr = first_cutoff - bufix;
+                   else if (bufix < second_cutoff &&
+                            bufix + incr > second_cutoff)
+                           incr = second_cutoff - bufix;
+                   
+                   /* If this option is going to overflow the buffer,
+                      skip it. */
+                   if (bufix + 2 + incr > buflen) {
+                           bufix = optstart;
+                           break;
+                   }
+                   
+                   /* Everything looks good - copy it in! */
+                   buffer [bufix] = code;
+                   buffer [bufix + 1] = incr;
+                   if (tto && incr == length) {
+                           memcpy (buffer + bufix + 2,
+                                   od.data + ix, (unsigned)(incr - 1));
+                           buffer [bufix + 2 + incr - 1] = 0;
+                   } else {
+                           memcpy (buffer + bufix + 2,
+                                   od.data + ix, (unsigned)incr);
+                   }
+                   length -= incr;
+                   ix += incr;
+                   bufix += 2 + incr;
+           }
+           data_string_forget (&od, MDL);
+           if (oc)
+                   option_cache_dereference (&oc, MDL);
        }
+
        return bufix;
 }
 
@@ -751,8 +1033,8 @@ const char *pretty_print_option (code, data, len, emit_commas, emit_quotes)
        return optbuf;
 }
 
-int hashed_option_get (result, universe, packet, lease,
-                      in_options, cfg_options, options, scope, code)
+int get_option (result, universe, packet, lease,
+               in_options, cfg_options, options, scope, code)
        struct data_string *result;
        struct universe *universe;
        struct packet *packet;
@@ -776,55 +1058,7 @@ int hashed_option_get (result, universe, packet, lease,
        return 1;
 }
 
-int agent_option_get (result, universe, packet, lease,
-                     in_options, cfg_options, options, scope, code)
-       struct data_string *result;
-       struct universe *universe;
-       struct packet *packet;
-       struct lease *lease;
-       struct option_state *in_options;
-       struct option_state *cfg_options;
-       struct option_state *options;
-       struct binding_scope **scope;
-       unsigned code;
-{
-       struct agent_options *ao;
-       struct option_tag *t;
-
-       /* Make sure there's agent option state. */
-       if (universe -> index >= options -> universe_count ||
-           !(options -> universes [universe -> index]))
-               return 0;
-       ao = (struct agent_options *)options -> universes [universe -> index];
-
-       /* Find the last set of agent options and consider it definitive. */
-       for (; ao -> next; ao = ao -> next)
-               ;
-       if (ao) {
-               for (t = ao -> first; t; t = t -> next) {
-                       if (t -> data [0] == code) {
-                               result -> len = t -> data [1];
-                               if (!(buffer_allocate (&result -> buffer,
-                                                      result -> len + 1,
-                                                      MDL))) {
-                                       result -> len = 0;
-                                       buffer_dereference
-                                               (&result -> buffer, MDL);
-                                       return 0;
-                               }
-                               result -> data = &result -> buffer -> data [0];
-                               memcpy (result -> buffer -> data,
-                                       &t -> data [2], result -> len);
-                               result -> buffer -> data [result -> len] = 0;
-                               result -> terminated = 1;
-                               return 1;
-                       }
-               }
-       }
-       return 0;
-}
-
-void hashed_option_set (universe, options, option, op)
+void set_option (universe, options, option, op)
        struct universe *universe;
        struct option_state *options;
        struct option_cache *option;
@@ -937,10 +1171,67 @@ struct option_cache *lookup_hashed_option (universe, options, code)
        return (struct option_cache *)0;
 }
 
-void save_option (universe, options, oc)
-       struct universe *universe;
-       struct option_state *options;
-       struct option_cache *oc;
+int save_option_buffer (struct universe *universe,
+                       struct option_state *options,
+                       struct buffer *bp,
+                       unsigned char *buffer, unsigned length,
+                       struct option *option, int tp)
+{
+       struct buffer *lbp = (struct buffer *)0;
+       struct option_cache *op = (struct option_cache *)0;
+
+       if (!option_cache_allocate (&op, MDL)) {
+               log_error ("No memory for option %s.%s.",
+                          universe -> name,
+                          option -> name);
+               return 0;
+       }
+
+       /* If we weren't passed a buffer in which the data are saved and
+          refcounted, allocate one now. */
+       if (!bp) {
+               if (!buffer_allocate (&lbp, length, MDL)) {
+                       log_error ("no memory for option buffer.");
+                       option_cache_dereference (&op, MDL);
+                       return 0;
+               }
+               memcpy (lbp -> data, buffer, length + tp);
+               bp = lbp;
+               buffer = &bp -> data [0]; /* Refer to saved buffer. */
+       }
+
+       /* Reference buffer copy to option cache. */
+       op -> data.buffer = (struct buffer *)0;
+       buffer_reference (&op -> data.buffer, bp, MDL);
+               
+       /* Point option cache into buffer. */
+       op -> data.data = buffer;
+       op -> data.len = length;
+                       
+       if (tp) {
+               /* NUL terminate (we can get away with this because we (or
+                  the caller!) allocated one more than the buffer size, and
+                  because the byte following the end of an option is always
+                  the code of the next option, which the caller is getting
+                  out of the *original* buffer. */
+               buffer [length] = 0;
+               op -> data.terminated = 1;
+       } else
+               op -> data.terminated = 0;
+       
+       op -> option = option;
+
+       /* Now store the option. */
+       save_option (universe, options, op);
+
+       /* And let go of our reference. */
+       option_cache_dereference (&op, MDL);
+
+       return 1;
+}
+
+void save_option (struct universe *universe,
+                 struct option_state *options, struct option_cache *oc)
 {
        if (universe -> save_func)
                (*universe -> save_func) (universe, options, oc);
@@ -1074,6 +1365,9 @@ int option_cache_dereference (ptr, file, line)
                if ((*ptr) -> expression)
                        expression_dereference (&(*ptr) -> expression,
                                                file, line);
+               if ((*ptr) -> next)
+                       option_cache_dereference (&((*ptr) -> next),
+                                                 file, line);
                /* Put it back on the free list... */
                (*ptr) -> expression = (struct expression *)free_option_caches;
                free_option_caches = *ptr;
@@ -1117,7 +1411,8 @@ int hashed_option_state_dereference (universe, state, file, line)
                for (cp = heads [i]; cp; cp = next) {
                        next = cp -> cdr;
                        option_cache_dereference
-                               ((struct option_cache **)&cp -> car, file, line);
+                               ((struct option_cache **)&cp -> car,
+                                file, line);
                        free_pair (cp, file, line);
                }
        }
@@ -1127,34 +1422,6 @@ int hashed_option_state_dereference (universe, state, file, line)
        return 1;
 }
 
-int agent_option_state_dereference (universe, state, file, line)
-       struct universe *universe;
-       struct option_state *state;
-       const char *file;
-       int line;
-{
-       struct agent_options *a, *na;
-       struct option_tag *ot, *not;
-
-       if (universe -> index >= state -> universe_count ||
-           !state -> universes [universe -> index])
-               return 0;
-
-       /* We can also release the agent options, if any... */
-       for (a = (struct agent_options *)(state -> universes
-                                         [universe -> index]); a; a = na) {
-               na = a -> next;
-               for (ot = a -> first; ot; ot = not) {
-                       not = ot -> next;
-                       dfree (ot, file, line);
-               }
-       }
-
-       dfree (state -> universes [universe -> index], file, line);
-       state -> universes [universe -> index] = (void *)0;
-       return 1;
-}
-
 int store_option (result, universe, packet, lease,
                  in_options, cfg_options, scope, oc)
        struct data_string *result;
@@ -1218,10 +1485,8 @@ int option_space_encapsulate (result, packet, lease,
        u = (struct universe *)0;
        universe_hash_lookup (&u, universe_hash,
                              (const char *)name -> data, name -> len, MDL);
-       if (!u) {
-               log_error ("unknown option space %s.", name -> data);
+       if (!u)
                return 0;
-       }
 
        if (u -> encapsulate)
                return (*u -> encapsulate) (result, packet, lease,
@@ -1332,6 +1597,294 @@ int nwip_option_space_encapsulate (result, packet, lease,
        return status;
 }
 
+int fqdn_option_space_encapsulate (result, packet, lease,
+                                  in_options, cfg_options, scope, universe)
+       struct data_string *result;
+       struct packet *packet;
+       struct lease *lease;
+       struct option_state *in_options;
+       struct option_state *cfg_options;
+       struct binding_scope **scope;
+       struct universe *universe;
+{
+       struct option_cache *oc;
+       struct data_string results [FQDN_SUBOPTION_COUNT + 1];
+       unsigned i;
+       unsigned len;
+       struct buffer *bp = (struct buffer *)0;
+
+       /* If there's no FQDN universe, don't encapsulate. */
+       if (!cfg_options -> universes [fqdn_universe.index])
+               return 0;
+
+       /* Figure out the values of all the suboptions. */
+       memset (results, 0, sizeof results);
+       for (oc = ((struct option_cache *)
+                  cfg_options -> universes [universe -> index]);
+            oc; oc = oc -> next) {
+               if (oc -> option -> code > FQDN_SUBOPTION_COUNT)
+                       continue;
+               evaluate_option_cache (&results [oc -> option -> code],
+                                      packet, lease, in_options,
+                                      cfg_options, scope,  oc, MDL);
+       }
+       len = 4 + results [FQDN_NAME].len;
+       /* Save the contents of the option in a buffer. */
+       if (!buffer_allocate (&bp, len, MDL)) {
+               log_error ("no memory for option buffer.");
+               return 0;
+       }
+       result -> buffer = bp;
+       result -> len = 3;
+       result -> data = &bp -> data [0];
+
+       memset (&bp -> data [0], 0, len);
+       if (results [FQDN_NO_CLIENT_UPDATE].len &&
+           results [FQDN_NO_CLIENT_UPDATE].data [0])
+               bp -> data [0] |= 2;
+       if (results [FQDN_SERVER_UPDATE].len &&
+           results [FQDN_SERVER_UPDATE].data [0])
+               bp -> data [0] |= 1;
+       if (results [FQDN_RCODE1].len)
+               bp -> data [1] = results [FQDN_RCODE1].data [0];
+       if (results [FQDN_RCODE2].len)
+               bp -> data [2] = results [FQDN_RCODE2].data [0];
+
+       if (results [FQDN_ENCODED].len &&
+           results [FQDN_ENCODED].data [0]) {
+               unsigned char *out;
+               int i;
+               bp -> data [0] |= 4;
+               out = &bp -> data [3];
+               if (results [FQDN_NAME].len) {
+                       i = 0;
+                       while (i < results [FQDN_NAME].len) {
+                               int j;
+                               for (j = i; ('.' !=
+                                            results [FQDN_NAME].data [j]) &&
+                                            j < results [FQDN_NAME].len; j++)
+                                       ;
+                               *out++ = j - i;
+                               memcpy (out, &results [FQDN_NAME].data [i],
+                                       (unsigned)(j - i));
+                               out += j - i;
+                               i = j;
+                               if (results [FQDN_NAME].data [j] == '.')
+                                       i++;
+                       }
+                       if ((results [FQDN_NAME].data
+                            [results [FQDN_NAME].len - 1] == '.'))
+                               *out++ = 0;
+                       result -> len = out - result -> data;
+                       result -> terminated = 0;
+               }
+       } else {
+               if (results [FQDN_NAME].len) {
+                       memcpy (&bp -> data [3], results [FQDN_NAME].data,
+                               results [FQDN_NAME].len);
+                       result -> len += results [FQDN_NAME].len;
+                       result -> terminated = 0;
+               }
+       }
+       for (i = 1; i <= FQDN_SUBOPTION_COUNT; i++) {
+               if (results [i].len)
+                       data_string_forget (&results [i], MDL);
+       }
+       return 1;
+}
+
+void option_space_foreach (struct packet *packet, struct lease *lease,
+                          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 option_state *,
+                                        struct option_state *,
+                                        struct binding_scope **,
+                                        struct universe *, void *))
+{
+       if (u -> foreach)
+               (*u -> foreach) (packet, lease, in_options, cfg_options,
+                                scope, u, stuff, func);
+}
+
+void hashed_option_space_foreach (struct packet *packet, struct lease *lease,
+                                 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 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,
+                                in_options, cfg_options, scope, u, stuff);
+               }
+       }
+}
+
+void save_linked_option (universe, options, oc)
+       struct universe *universe;
+       struct option_state *options;
+       struct option_cache *oc;
+{
+       struct option_cache **tail;
+
+       /* Find the tail of the list. */
+       for (tail = ((struct option_cache **)
+                    &options -> universes [universe -> index]);
+            *tail; tail = &((*tail) -> next)) {
+               if ((*tail) -> option == oc -> option) {
+                       if ((*tail) -> next) {
+                               option_cache_reference (&oc -> next,
+                                                       (*tail) -> next, MDL);
+                       }
+                       option_cache_dereference (tail, MDL);
+                       break;
+               }
+       }
+
+       option_cache_reference (tail, oc, MDL);
+}
+
+int linked_option_space_encapsulate (result, packet, lease,
+                                   in_options, cfg_options, scope, universe)
+       struct data_string *result;
+       struct packet *packet;
+       struct lease *lease;
+       struct option_state *in_options;
+       struct option_state *cfg_options;
+       struct binding_scope **scope;
+       struct universe *universe;
+{
+       int status;
+       struct option_cache *oc;
+
+       if (universe -> index >= cfg_options -> universe_count)
+               return 0;
+
+       status = 0;
+       for (oc = ((struct option_cache *)
+                  cfg_options -> universes [universe -> index]);
+            oc; oc = oc -> next) {
+               if (store_option (result, universe, packet, lease,
+                                 in_options, cfg_options, scope, oc))
+                       status = 1;
+       }
+
+       return status;
+}
+
+void delete_linked_option (universe, options, code)
+       struct universe *universe;
+       struct option_state *options;
+       int code;
+{
+       struct option_cache **tail, *tmp = (struct option_cache *)0;
+
+       /* Find the tail of the list. */
+       for (tail = ((struct option_cache **)
+                    &options -> universes [universe -> index]);
+            *tail; tail = &((*tail) -> next)) {
+               if ((*tail) -> option -> code == code) {
+                       if ((*tail) -> next) {
+                               option_cache_reference (&tmp,
+                                                       (*tail) -> next, MDL);
+                               option_cache_dereference (tail, MDL);
+                       }
+                       if (tmp) {
+                               option_cache_reference (tail, tmp, MDL);
+                               option_cache_dereference (&tmp, MDL);
+                       }
+                       break;
+               }
+       }
+}
+
+struct option_cache *lookup_linked_option (universe, options, code)
+       struct universe *universe;
+       struct option_state *options;
+       unsigned code;
+{
+       struct option_cache *oc;
+
+       if (universe -> index >= options -> universe_count)
+               return 0;
+
+       for (oc = ((struct option_cache *)
+                  options -> universes [universe -> index]);
+            oc; oc = oc -> next) {
+               if (oc -> option -> code == code) {
+                       return oc;
+               }
+       }
+
+       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;
+{
+       struct option_cache **tail;
+
+       /* Find the tail of the list. */
+       tail = ((struct option_cache **)
+               &state -> universes [universe -> index]);
+       if (*tail)
+               return option_cache_dereference (tail, file, line);
+       return 1;
+}
+
+void linked_option_space_foreach (struct packet *packet, struct lease *lease,
+                                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 option_state *,
+                                              struct option_state *,
+                                              struct binding_scope **,
+                                              struct universe *, void *))
+{
+       struct option_cache *oc;
+
+       if (u -> index >= cfg_options -> universe_count)
+               return;
+
+       for (oc = ((struct option_cache *)
+                  cfg_options -> universes [u -> index]);
+            oc; oc = oc -> next) {
+               (*func) (oc, packet, lease,
+                        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;