]> git.ipfire.org Git - people/ms/dnsmasq.git/blobdiff - src/rfc2131.c
Auth: correct replies to NS and SOA in .arpa zones.
[people/ms/dnsmasq.git] / src / rfc2131.c
index 2802ca1edf9840dabea94852af8c059870c24589..55526443dc8471fdfadee41438d8e5ac0e33be6c 100644 (file)
@@ -1,4 +1,4 @@
-/* dnsmasq is Copyright (c) 2000-2010 Simon Kelley
+/* dnsmasq is Copyright (c) 2000-2015 Simon Kelley
 
    This program is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
 
 #ifdef HAVE_DHCP
 
-#define BOOTREQUEST              1
-#define BOOTREPLY                2
-#define DHCP_COOKIE              0x63825363
-
-/* The Linux in-kernel DHCP client silently ignores any packet 
-   smaller than this. Sigh...........   */
-#define MIN_PACKETSZ             300
-
-#define OPTION_PAD               0
-#define OPTION_NETMASK           1
-#define OPTION_ROUTER            3
-#define OPTION_DNSSERVER         6
-#define OPTION_HOSTNAME          12
-#define OPTION_DOMAINNAME        15
-#define OPTION_BROADCAST         28
-#define OPTION_VENDOR_CLASS_OPT  43
-#define OPTION_REQUESTED_IP      50 
-#define OPTION_LEASE_TIME        51
-#define OPTION_OVERLOAD          52
-#define OPTION_MESSAGE_TYPE      53
-#define OPTION_SERVER_IDENTIFIER 54
-#define OPTION_REQUESTED_OPTIONS 55
-#define OPTION_MESSAGE           56
-#define OPTION_MAXMESSAGE        57
-#define OPTION_T1                58
-#define OPTION_T2                59
-#define OPTION_VENDOR_ID         60
-#define OPTION_CLIENT_ID         61
-#define OPTION_SNAME             66
-#define OPTION_FILENAME          67
-#define OPTION_USER_CLASS        77
-#define OPTION_CLIENT_FQDN       81
-#define OPTION_AGENT_ID          82
-#define OPTION_ARCH              93
-#define OPTION_PXE_UUID          97
-#define OPTION_SUBNET_SELECT     118
-#define OPTION_VENDOR_IDENT      124
-#define OPTION_VENDOR_IDENT_OPT  125
-#define OPTION_END               255
-
-#define SUBOPT_CIRCUIT_ID        1
-#define SUBOPT_REMOTE_ID         2
-#define SUBOPT_SUBNET_SELECT     5     /* RFC 3527 */
-#define SUBOPT_SUBSCR_ID         6     /* RFC 3393 */
-#define SUBOPT_SERVER_OR         11    /* RFC 5107 */
-
-#define SUBOPT_PXE_BOOT_ITEM     71    /* PXE standard */
-#define SUBOPT_PXE_DISCOVERY     6
-#define SUBOPT_PXE_SERVERS       8
-#define SUBOPT_PXE_MENU          9
-#define SUBOPT_PXE_MENU_PROMPT   10
-
-#define DHCPDISCOVER             1
-#define DHCPOFFER                2
-#define DHCPREQUEST              3
-#define DHCPDECLINE              4
-#define DHCPACK                  5
-#define DHCPNAK                  6
-#define DHCPRELEASE              7
-#define DHCPINFORM               8
-
-#define BRDBAND_FORUM_IANA       3561 /* Broadband forum IANA enterprise */
-
-#define have_config(config, mask) ((config) && ((config)->flags & (mask))) 
 #define option_len(opt) ((int)(((unsigned char *)(opt))[1]))
 #define option_ptr(opt, i) ((void *)&(((unsigned char *)(opt))[2u+(unsigned int)(i)]))
 
 #ifdef HAVE_SCRIPT
-static void add_extradata_data(struct dhcp_lease *lease, unsigned char *data, size_t len, int delim);
 static void add_extradata_opt(struct dhcp_lease *lease, unsigned char *opt);
 #endif
-static int match_bytes(struct dhcp_opt *o, unsigned char *p, int len);
+
 static int sanitise(unsigned char *opt, char *buf);
 static struct in_addr server_id(struct dhcp_context *context, struct in_addr override, struct in_addr fallback);
 static unsigned int calc_time(struct dhcp_context *context, struct dhcp_config *config, unsigned char *opt);
@@ -97,39 +32,39 @@ static void option_put(struct dhcp_packet *mess, unsigned char *end, int opt, in
 static void option_put_string(struct dhcp_packet *mess, unsigned char *end, 
                              int opt, char *string, int null_term);
 static struct in_addr option_addr(unsigned char *opt);
-static struct in_addr option_addr_arr(unsigned char *opt, int offset);
 static unsigned int option_uint(unsigned char *opt, int i, int size);
 static void log_packet(char *type, void *addr, unsigned char *ext_mac, 
-                      int mac_len, char *interface, char *string, u32 xid);
+                      int mac_len, char *interface, char *string, char *err, u32 xid);
 static unsigned char *option_find(struct dhcp_packet *mess, size_t size, int opt_type, int minsize);
 static unsigned char *option_find1(unsigned char *p, unsigned char *end, int opt, int minsize);
-static size_t dhcp_packet_size(struct dhcp_packet *mess, struct dhcp_netid *netid,
-                              unsigned char *agent_id, unsigned char *real_end);
+static size_t dhcp_packet_size(struct dhcp_packet *mess, unsigned char *agent_id, unsigned char *real_end);
 static void clear_packet(struct dhcp_packet *mess, unsigned char *end);
+static int in_list(unsigned char *list, int opt);
 static void do_options(struct dhcp_context *context,
                       struct dhcp_packet *mess,
                       unsigned char *real_end, 
                       unsigned char *req_options,
                       char *hostname, 
-                      char *domain, char *config_domain,
+                      char *config_domain,
                       struct dhcp_netid *netid,
-                      struct in_addr subnet_addr,
+                      struct in_addr subnet_addr, 
                       unsigned char fqdn_flags,
                       int null_term, int pxearch,
                       unsigned char *uuid,
-                      int vendor_class_len);
+                      int vendor_class_len,
+                      time_t now);
 
 
 static void match_vendor_opts(unsigned char *opt, struct dhcp_opt *dopt); 
 static int do_encap_opts(struct dhcp_opt *opts, int encap, int flag, struct dhcp_packet *mess, unsigned char *end, int null_term);
 static void pxe_misc(struct dhcp_packet *mess, unsigned char *end, unsigned char *uuid);
 static int prune_vendor_opts(struct dhcp_netid *netid);
-static struct dhcp_opt *pxe_opts(int pxe_arch, struct dhcp_netid *netid, struct in_addr local);
+static struct dhcp_opt *pxe_opts(int pxe_arch, struct dhcp_netid *netid, struct in_addr local, time_t now);
 struct dhcp_boot *find_boot(struct dhcp_netid *netid);
 
   
 size_t dhcp_reply(struct dhcp_context *context, char *iface_name, int int_index,
-                 size_t sz, time_t now, int unicast_dest, int *is_inform, int pxe)
+                 size_t sz, time_t now, int unicast_dest, int *is_inform, int pxe, struct in_addr fallback)
 {
   unsigned char *opt, *clid = NULL;
   struct dhcp_lease *ltmp, *lease = NULL;
@@ -147,7 +82,7 @@ size_t dhcp_reply(struct dhcp_context *context, char *iface_name, int int_index,
   unsigned int time;
   struct dhcp_config *config;
   struct dhcp_netid *netid, *tagif_netid;
-  struct in_addr subnet_addr, fallback, override;
+  struct in_addr subnet_addr, override;
   unsigned short fuzz = 0;
   unsigned int mess_type = 0;
   unsigned char fqdn_flags = 0;
@@ -157,7 +92,10 @@ size_t dhcp_reply(struct dhcp_context *context, char *iface_name, int int_index,
   struct dhcp_netid known_id, iface_id, cpewan_id;
   struct dhcp_opt *o;
   unsigned char pxe_uuid[17];
-  unsigned char *oui = NULL, *serial = NULL, *class = NULL;
+  unsigned char *oui = NULL, *serial = NULL;
+#ifdef HAVE_SCRIPT
+  unsigned char *class = NULL;
+#endif
 
   subnet_addr.s_addr = override.s_addr = 0;
 
@@ -221,8 +159,9 @@ size_t dhcp_reply(struct dhcp_context *context, char *iface_name, int int_index,
                  unsigned char *y = option_ptr(opt, offset + elen + 5);
                  oui = option_find1(x, y, 1, 1);
                  serial = option_find1(x, y, 2, 1);
-                 class = option_find1(x, y, 3, 1);
-                 
+#ifdef HAVE_SCRIPT
+                 class = option_find1(x, y, 3, 1);               
+#endif
                  /* If TR069-id is present set the tag "cpewan-id" to facilitate echoing 
                     the gateway id back. Note that the device class is optional */
                  if (oui && serial)
@@ -359,17 +298,43 @@ size_t dhcp_reply(struct dhcp_context *context, char *iface_name, int int_index,
                
       if (!context_new)
        for (context_tmp = daemon->dhcp; context_tmp; context_tmp = context_tmp->next)
-         if (context_tmp->netmask.s_addr  && 
-             is_same_net(addr, context_tmp->start, context_tmp->netmask) &&
-             is_same_net(addr, context_tmp->end, context_tmp->netmask))
-           {
-             context_tmp->current = context_new;
-             context_new = context_tmp;
-           }
+         {
+           struct in_addr netmask = context_tmp->netmask;
+
+           /* guess the netmask for relayed networks */
+           if (!(context_tmp->flags & CONTEXT_NETMASK) && context_tmp->netmask.s_addr == 0)
+             {
+               if (IN_CLASSA(ntohl(context_tmp->start.s_addr)) && IN_CLASSA(ntohl(context_tmp->end.s_addr)))
+                 netmask.s_addr = htonl(0xff000000);
+               else if (IN_CLASSB(ntohl(context_tmp->start.s_addr)) && IN_CLASSB(ntohl(context_tmp->end.s_addr)))
+                 netmask.s_addr = htonl(0xffff0000);
+               else if (IN_CLASSC(ntohl(context_tmp->start.s_addr)) && IN_CLASSC(ntohl(context_tmp->end.s_addr)))
+                 netmask.s_addr = htonl(0xffffff00); 
+             }
+           
+           /* This section fills in context mainly when a client which is on a remote (relayed)
+              network renews a lease without using the relay, after dnsmasq has restarted. */
+           if (netmask.s_addr != 0  && 
+               is_same_net(addr, context_tmp->start, netmask) &&
+               is_same_net(addr, context_tmp->end, netmask))
+             {
+               context_tmp->netmask = netmask;
+               if (context_tmp->local.s_addr == 0)
+                 context_tmp->local = fallback;
+               if (context_tmp->router.s_addr == 0)
+                 context_tmp->router = mess->giaddr;
+          
+               /* fill in missing broadcast addresses for relayed ranges */
+               if (!(context_tmp->flags & CONTEXT_BRDCAST) && context_tmp->broadcast.s_addr == 0 )
+                 context_tmp->broadcast.s_addr = context_tmp->start.s_addr | ~context_tmp->netmask.s_addr;
+               
+               context_tmp->current = context_new;
+               context_new = context_tmp;
+             }
+         }
       
       if (context_new || force)
-       context = context_new;
-      
+       context = context_new; 
     }
   
   if (!context)
@@ -380,10 +345,7 @@ size_t dhcp_reply(struct dhcp_context *context, char *iface_name, int int_index,
       return 0;
     }
 
-  /* keep _a_ local address available. */
-  fallback = context->local;
-  
-  if (daemon->options & OPT_LOG_OPTS)
+  if (option_bool(OPT_LOG_OPTS))
     {
       struct dhcp_context *context_tmp;
       for (context_tmp = context; context_tmp; context_tmp = context_tmp->current)
@@ -397,6 +359,117 @@ size_t dhcp_reply(struct dhcp_context *context, char *iface_name, int int_index,
                      ntohl(mess->xid), daemon->namebuff, inet_ntoa(context_tmp->end));
        }
     }
+  
+  /* dhcp-match. If we have hex-and-wildcards, look for a left-anchored match.
+     Otherwise assume the option is an array, and look for a matching element. 
+     If no data given, existance of the option is enough. This code handles 
+     rfc3925 V-I classes too. */
+  for (o = daemon->dhcp_match; o; o = o->next)
+    {
+      unsigned int len, elen, match = 0;
+      size_t offset, o2;
+
+      if (o->flags & DHOPT_RFC3925)
+       {
+         if (!(opt = option_find(mess, sz, OPTION_VENDOR_IDENT, 5)))
+           continue;
+         
+         for (offset = 0; offset < (option_len(opt) - 5u); offset += len + 5)
+           {
+             len = option_uint(opt, offset + 4 , 1);
+             /* Need to take care that bad data can't run us off the end of the packet */
+             if ((offset + len + 5 <= (option_len(opt))) &&
+                 (option_uint(opt, offset, 4) == (unsigned int)o->u.encap))
+               for (o2 = offset + 5; o2 < offset + len + 5; o2 += elen + 1)
+                 { 
+                   elen = option_uint(opt, o2, 1);
+                   if ((o2 + elen + 1 <= option_len(opt)) &&
+                       (match = match_bytes(o, option_ptr(opt, o2 + 1), elen)))
+                     break;
+                 }
+             if (match) 
+               break;
+           }     
+       }
+      else
+       {
+         if (!(opt = option_find(mess, sz, o->opt, 1)))
+           continue;
+         
+         match = match_bytes(o, option_ptr(opt, 0), option_len(opt));
+       } 
+
+      if (match)
+       {
+         o->netid->next = netid;
+         netid = o->netid;
+       }
+    }
+       
+  /* user-class options are, according to RFC3004, supposed to contain
+     a set of counted strings. Here we check that this is so (by seeing
+     if the counts are consistent with the overall option length) and if
+     so zero the counts so that we don't get spurious matches between 
+     the vendor string and the counts. If the lengths don't add up, we
+     assume that the option is a single string and non RFC3004 compliant 
+     and just do the substring match. dhclient provides these broken options.
+     The code, later, which sends user-class data to the lease-change script
+     relies on the transformation done here.
+  */
+
+  if ((opt = option_find(mess, sz, OPTION_USER_CLASS, 1)))
+    {
+      unsigned char *ucp = option_ptr(opt, 0);
+      int tmp, j;
+      for (j = 0; j < option_len(opt); j += ucp[j] + 1);
+      if (j == option_len(opt))
+       for (j = 0; j < option_len(opt); j = tmp)
+         {
+           tmp = j + ucp[j] + 1;
+           ucp[j] = 0;
+         }
+    }
+    
+  for (vendor = daemon->dhcp_vendors; vendor; vendor = vendor->next)
+    {
+      int mopt;
+      
+      if (vendor->match_type == MATCH_VENDOR)
+       mopt = OPTION_VENDOR_ID;
+      else if (vendor->match_type == MATCH_USER)
+       mopt = OPTION_USER_CLASS; 
+      else
+       continue;
+
+      if ((opt = option_find(mess, sz, mopt, 1)))
+       {
+         int i;
+         for (i = 0; i <= (option_len(opt) - vendor->len); i++)
+           if (memcmp(vendor->data, option_ptr(opt, i), vendor->len) == 0)
+             {
+               vendor->netid.next = netid;
+               netid = &vendor->netid;
+               break;
+             }
+       }
+    }
+
+  /* mark vendor-encapsulated options which match the client-supplied vendor class,
+     save client-supplied vendor class */
+  if ((opt = option_find(mess, sz, OPTION_VENDOR_ID, 1)))
+    {
+      memcpy(daemon->dhcp_buff3, option_ptr(opt, 0), option_len(opt));
+      vendor_class_len = option_len(opt);
+    }
+  match_vendor_opts(opt, daemon->dhcp_opts);
+  
+  if (option_bool(OPT_LOG_OPTS))
+    {
+      if (sanitise(opt, daemon->namebuff))
+       my_syslog(MS_DHCP | LOG_INFO, _("%u vendor class: %s"), ntohl(mess->xid), daemon->namebuff);
+      if (sanitise(option_find(mess, sz, OPTION_USER_CLASS, 1), daemon->namebuff))
+       my_syslog(MS_DHCP | LOG_INFO, _("%u user class: %s"), ntohl(mess->xid), daemon->namebuff);
+    }
 
   mess->op = BOOTREPLY;
   
@@ -503,9 +576,10 @@ size_t dhcp_reply(struct dhcp_context *context, char *iface_name, int int_index,
          else if (context->netid.net)
            {
              context->netid.next = netid;
-             netid = &context->netid; 
-             tagif_netid = run_tag_if(netid);
+             tagif_netid = run_tag_if(&context->netid);
            }
+
+         log_tags(tagif_netid, ntohl(mess->xid));
            
          if (!message && !nailed)
            {
@@ -518,34 +592,34 @@ size_t dhcp_reply(struct dhcp_context *context, char *iface_name, int int_index,
 
          if (!message && 
              !lease && 
-             (!(lease = lease_allocate(mess->yiaddr))))
+             (!(lease = lease4_allocate(mess->yiaddr))))
            message = _("no leases left");
          
          if (!message)
            {
              logaddr = &mess->yiaddr;
                
-             lease_set_hwaddr(lease, mess->chaddr, NULL, mess->hlen, mess->htype, 0);
+             lease_set_hwaddr(lease, mess->chaddr, NULL, mess->hlen, mess->htype, 0, now, 1);
              if (hostname)
-               lease_set_hostname(lease, hostname, 1); 
+               lease_set_hostname(lease, hostname, 1, get_domain(lease->addr), domain); 
              /* infinite lease unless nailed in dhcp-host line. */
              lease_set_expires(lease,  
                                have_config(config, CONFIG_TIME) ? config->lease_time : 0xffffffff, 
                                now); 
-             lease_set_interface(lease, int_index);
+             lease_set_interface(lease, int_index, now);
              
              clear_packet(mess, end);
              do_options(context, mess, end, NULL, hostname, get_domain(mess->yiaddr), 
-                        domain, tagif_netid, subnet_addr, 0, 0, 0, NULL, 0);
+                        netid, subnet_addr, 0, 0, -1, NULL, vendor_class_len, now);
            }
        }
       
-      log_packet("BOOTP", logaddr, mess->chaddr, mess->hlen, iface_name, message, mess->xid);
+      log_packet("BOOTP", logaddr, mess->chaddr, mess->hlen, iface_name, NULL, message, mess->xid);
       
-      return message ? 0 : dhcp_packet_size(mess, tagif_netid, agent_id, real_end);
+      return message ? 0 : dhcp_packet_size(mess, agent_id, real_end);
     }
       
-  if ((opt = option_find(mess, sz, OPTION_CLIENT_FQDN, 4)))
+  if ((opt = option_find(mess, sz, OPTION_CLIENT_FQDN, 3)))
     {
       /* http://tools.ietf.org/wg/dhc/draft-ietf-dhc-fqdn-option/draft-ietf-dhc-fqdn-option-10.txt */
       int len = option_len(opt);
@@ -557,15 +631,25 @@ size_t dhcp_reply(struct dhcp_context *context, char *iface_name, int int_index,
       op += 3;
       pp = op;
       
-      /* Always force update, since the client has no way to do it itself. */
-      if (!(fqdn_flags & 0x01))
-       fqdn_flags |= 0x02;
-      
-      fqdn_flags &= ~0x08;
-      fqdn_flags |= 0x01;
+      /* NB, the following always sets at least one bit */
+      if (option_bool(OPT_FQDN_UPDATE))
+       {
+         if (fqdn_flags & 0x01)
+           {
+             fqdn_flags |= 0x02; /* set O */
+             fqdn_flags &= ~0x01; /* clear S */
+           }
+         fqdn_flags |= 0x08; /* set N */
+       }
+      else 
+       {
+         if (!(fqdn_flags & 0x01))
+           fqdn_flags |= 0x03; /* set S and O */
+         fqdn_flags &= ~0x08; /* clear N */
+       }
       
       if (fqdn_flags & 0x04)
-       while (*op != 0 && ((op + (*op) + 1) - pp) < len)
+       while (*op != 0 && ((op + (*op)) - pp) < len)
          {
            memcpy(pq, op+1, *op);
            pq += *op;
@@ -603,7 +687,7 @@ size_t dhcp_reply(struct dhcp_context *context, char *iface_name, int int_index,
        client_hostname = daemon->dhcp_buff;
     }
 
-  if (client_hostname && daemon->options & OPT_LOG_OPTS)
+  if (client_hostname && option_bool(OPT_LOG_OPTS))
     my_syslog(MS_DHCP | LOG_INFO, _("%u client provides name: %s"), ntohl(mess->xid), client_hostname);
   
   if (have_config(config, CONFIG_NAME))
@@ -653,119 +737,8 @@ size_t dhcp_reply(struct dhcp_context *context, char *iface_name, int int_index,
        }
     }
   
-  /* dhcp-match. If we have hex-and-wildcards, look for a left-anchored match.
-     Otherwise assume the option is an array, and look for a matching element. 
-     If no data given, existance of the option is enough. This code handles 
-     rfc3925 V-I classes too. */
-  for (o = daemon->dhcp_match; o; o = o->next)
-    {
-      unsigned int len, elen, match = 0;
-      size_t offset, o2;
-
-      if (o->flags & DHOPT_RFC3925)
-       {
-         if (!(opt = option_find(mess, sz, OPTION_VENDOR_IDENT, 5)))
-           continue;
-         
-         for (offset = 0; offset < (option_len(opt) - 5u); offset += len + 5)
-           {
-             len = option_uint(opt, offset + 4 , 1);
-             /* Need to take care that bad data can't run us off the end of the packet */
-             if ((offset + len + 5 <= (option_len(opt))) &&
-                 (option_uint(opt, offset, 4) == (unsigned int)o->u.encap))
-               for (o2 = offset + 5; o2 < offset + len + 5; o2 += elen + 1)
-                 { 
-                   elen = option_uint(opt, o2, 1);
-                   if ((o2 + elen + 1 <= option_len(opt)) &&
-                       (match = match_bytes(o, option_ptr(opt, o2 + 1), elen)))
-                     break;
-                 }
-             if (match) 
-               break;
-           }     
-       }
-      else
-       {
-         if (!(opt = option_find(mess, sz, o->opt, 1)))
-           continue;
-         
-         match = match_bytes(o, option_ptr(opt, 0), option_len(opt));
-       } 
-
-      if (match)
-       {
-         o->netid->next = netid;
-         netid = o->netid;
-       }
-    }
-       
-  /* user-class options are, according to RFC3004, supposed to contain
-     a set of counted strings. Here we check that this is so (by seeing
-     if the counts are consistent with the overall option length) and if
-     so zero the counts so that we don't get spurious matches between 
-     the vendor string and the counts. If the lengths don't add up, we
-     assume that the option is a single string and non RFC3004 compliant 
-     and just do the substring match. dhclient provides these broken options.
-     The code, later, which sends user-class data to the lease-change script
-     relies on the transformation done here.
-  */
-
-  if ((opt = option_find(mess, sz, OPTION_USER_CLASS, 1)))
-    {
-      unsigned char *ucp = option_ptr(opt, 0);
-      int tmp, j;
-      for (j = 0; j < option_len(opt); j += ucp[j] + 1);
-      if (j == option_len(opt))
-       for (j = 0; j < option_len(opt); j = tmp)
-         {
-           tmp = j + ucp[j] + 1;
-           ucp[j] = 0;
-         }
-    }
-    
-  for (vendor = daemon->dhcp_vendors; vendor; vendor = vendor->next)
-    {
-      int mopt;
-      
-      if (vendor->match_type == MATCH_VENDOR)
-       mopt = OPTION_VENDOR_ID;
-      else if (vendor->match_type == MATCH_USER)
-       mopt = OPTION_USER_CLASS; 
-      else
-       continue;
-
-      if ((opt = option_find(mess, sz, mopt, 1)))
-       {
-         int i;
-         for (i = 0; i <= (option_len(opt) - vendor->len); i++)
-           if (memcmp(vendor->data, option_ptr(opt, i), vendor->len) == 0)
-             {
-               vendor->netid.next = netid;
-               netid = &vendor->netid;
-               break;
-             }
-       }
-    }
-
-  /* mark vendor-encapsulated options which match the client-supplied vendor class,
-     save client-supplied vendor class */
-  if ((opt = option_find(mess, sz, OPTION_VENDOR_ID, 1)))
-    {
-      memcpy(daemon->dhcp_buff3, option_ptr(opt, 0), option_len(opt));
-      vendor_class_len = option_len(opt);
-    }
-  match_vendor_opts(opt, daemon->dhcp_opts);
-  
-  if (daemon->options & OPT_LOG_OPTS)
-    {
-      if (sanitise(opt, daemon->namebuff))
-       my_syslog(MS_DHCP | LOG_INFO, _("%u vendor class: %s"), ntohl(mess->xid), daemon->namebuff);
-      if (sanitise(option_find(mess, sz, OPTION_USER_CLASS, 1), daemon->namebuff))
-       my_syslog(MS_DHCP | LOG_INFO, _("%u user class: %s"), ntohl(mess->xid), daemon->namebuff);
-    }
-
   tagif_netid = run_tag_if(netid);
-
+  
   /* if all the netids in the ignore list are present, ignore this client */
   for (id_list = daemon->dhcp_ignore; id_list; id_list = id_list->next)
     if (match_netid(id_list->list, tagif_netid, 0))
@@ -837,7 +810,9 @@ size_t dhcp_reply(struct dhcp_context *context, char *iface_name, int int_index,
          
          mess->yiaddr = mess->ciaddr;
          mess->ciaddr.s_addr = 0;
-         if (service->server.s_addr != 0)
+         if (service->sname)
+           mess->siaddr = a_record_from_hosts(service->sname, now);
+         else if (service->server.s_addr != 0)
            mess->siaddr = service->server; 
          else
            mess->siaddr = context->local; 
@@ -856,8 +831,9 @@ size_t dhcp_reply(struct dhcp_context *context, char *iface_name, int int_index,
          opt71.next = daemon->dhcp_opts;
          do_encap_opts(&opt71, OPTION_VENDOR_CLASS_OPT, DHOPT_VENDOR_MATCH, mess, end, 0);
          
-         log_packet("PXE", &mess->yiaddr, emac, emac_len, iface_name, (char *)mess->file, mess->xid);
-         return dhcp_packet_size(mess, tagif_netid, agent_id, real_end);         
+         log_packet("PXE", &mess->yiaddr, emac, emac_len, iface_name, (char *)mess->file, NULL, mess->xid);
+         log_tags(tagif_netid, ntohl(mess->xid));
+         return dhcp_packet_size(mess, agent_id, real_end);      
        }
       
       if ((opt = option_find(mess, sz, OPTION_ARCH, 2)))
@@ -865,40 +841,60 @@ size_t dhcp_reply(struct dhcp_context *context, char *iface_name, int int_index,
          pxearch = option_uint(opt, 0, 2);
 
          /* proxy DHCP here. */
-         if ((mess_type == DHCPDISCOVER || (pxe && mess_type == DHCPREQUEST)) && 
-             (context->flags & CONTEXT_PROXY))
+         if ((mess_type == DHCPDISCOVER || (pxe && mess_type == DHCPREQUEST)))
            {
-             struct dhcp_boot *boot = find_boot(tagif_netid);
-
-             mess->yiaddr.s_addr = 0;
-             if  (mess_type == DHCPDISCOVER || mess->ciaddr.s_addr == 0)
-               {
-                 mess->ciaddr.s_addr = 0;
-                 mess->flags |= htons(0x8000); /* broadcast */
-               }
-
-             clear_packet(mess, end);
+             struct dhcp_context *tmp;
              
-             /* Provide the bootfile here, for gPXE, and in case we have no menu items
-                and set discovery_control = 8 */
-             if (boot)
+             for (tmp = context; tmp; tmp = tmp->current)
+               if ((tmp->flags & CONTEXT_PROXY) &&
+                   match_netid(tmp->filter, tagif_netid, 1))
+                 break;
+             
+             if (tmp)
                {
-                 if (boot->next_server.s_addr)
-                   mess->siaddr = boot->next_server;
+                 struct dhcp_boot *boot;
+                 
+                 if (tmp->netid.net)
+                   {
+                     tmp->netid.next = netid;
+                     tagif_netid = run_tag_if(&tmp->netid);
+                   }
+                 
+                 boot = find_boot(tagif_netid);
                  
-                 if (boot->file)
-                   strncpy((char *)mess->file, boot->file, sizeof(mess->file)-1);
+                 mess->yiaddr.s_addr = 0;
+                 if  (mess_type == DHCPDISCOVER || mess->ciaddr.s_addr == 0)
+                   {
+                     mess->ciaddr.s_addr = 0;
+                     mess->flags |= htons(0x8000); /* broadcast */
+                   }
+                 
+                 clear_packet(mess, end);
+                 
+                 /* Provide the bootfile here, for gPXE, and in case we have no menu items
+                    and set discovery_control = 8 */
+                 if (boot)
+                   {
+                     if (boot->next_server.s_addr) 
+                       mess->siaddr = boot->next_server;
+                     else if (boot->tftp_sname) 
+                       mess->siaddr = a_record_from_hosts(boot->tftp_sname, now);
+                     
+                     if (boot->file)
+                       strncpy((char *)mess->file, boot->file, sizeof(mess->file)-1);
+                   }
+                 
+                 option_put(mess, end, OPTION_MESSAGE_TYPE, 1, 
+                            mess_type == DHCPDISCOVER ? DHCPOFFER : DHCPACK);
+                 option_put(mess, end, OPTION_SERVER_IDENTIFIER, INADDRSZ, htonl(context->local.s_addr));
+                 pxe_misc(mess, end, uuid);
+                 prune_vendor_opts(tagif_netid);
+                 do_encap_opts(pxe_opts(pxearch, tagif_netid, context->local, now), OPTION_VENDOR_CLASS_OPT, DHOPT_VENDOR_MATCH, mess, end, 0);
+                 
+                 log_packet("PXE", NULL, emac, emac_len, iface_name, ignore ? "proxy-ignored" : "proxy", NULL, mess->xid);
+                 log_tags(tagif_netid, ntohl(mess->xid));
+                 return ignore ? 0 : dhcp_packet_size(mess, agent_id, real_end);         
                }
-
-             option_put(mess, end, OPTION_MESSAGE_TYPE, 1, 
-                        mess_type == DHCPDISCOVER ? DHCPOFFER : DHCPACK);
-             option_put(mess, end, OPTION_SERVER_IDENTIFIER, INADDRSZ, htonl(context->local.s_addr));
-             pxe_misc(mess, end, uuid);
-             prune_vendor_opts(tagif_netid);
-             do_encap_opts(pxe_opts(pxearch, tagif_netid, context->local), OPTION_VENDOR_CLASS_OPT, DHOPT_VENDOR_MATCH, mess, end, 0);
-             
-             log_packet("PXE", NULL, emac, emac_len, iface_name, ignore ? "proxy-ignored" : "proxy", mess->xid);
-             return ignore ? 0 : dhcp_packet_size(mess, tagif_netid, agent_id, real_end);        
            }
        }
     }
@@ -927,7 +923,7 @@ size_t dhcp_reply(struct dhcp_context *context, char *iface_name, int int_index,
       if (!(opt = option_find(mess, sz, OPTION_REQUESTED_IP, INADDRSZ)))
        return 0;
       
-      log_packet("DHCPDECLINE", option_ptr(opt, 0), emac, emac_len, iface_name, daemon->dhcp_buff, mess->xid);
+      log_packet("DHCPDECLINE", option_ptr(opt, 0), emac, emac_len, iface_name, NULL, daemon->dhcp_buff, mess->xid);
       
       if (lease && lease->addr.s_addr == option_addr(opt).s_addr)
        lease_prune(lease, now);
@@ -959,13 +955,15 @@ size_t dhcp_reply(struct dhcp_context *context, char *iface_name, int int_index,
       else
        message = _("unknown lease");
 
-      log_packet("DHCPRELEASE", &mess->ciaddr, emac, emac_len, iface_name, message, mess->xid);
+      log_packet("DHCPRELEASE", &mess->ciaddr, emac, emac_len, iface_name, NULL, message, mess->xid);
        
       return 0;
       
     case DHCPDISCOVER:
       if (ignore || have_config(config, CONFIG_DISABLE))
        {
+         if (option_bool(OPT_QUIET_DHCP))
+           return 0;
          message = _("ignored");
          opt = NULL;
        }
@@ -1023,20 +1021,21 @@ size_t dhcp_reply(struct dhcp_context *context, char *iface_name, int int_index,
            message = _("no address available");      
        }
       
-      log_packet("DHCPDISCOVER", opt ? option_ptr(opt, 0) : NULL, emac, emac_len, iface_name, message, mess->xid); 
+      log_packet("DHCPDISCOVER", opt ? option_ptr(opt, 0) : NULL, emac, emac_len, iface_name, NULL, message, mess->xid); 
 
       if (message || !(context = narrow_context(context, mess->yiaddr, tagif_netid)))
        return 0;
 
-      log_packet("DHCPOFFER" , &mess->yiaddr, emac, emac_len, iface_name, NULL, mess->xid);
-
       if (context->netid.net)
        {
          context->netid.next = netid;
-         netid = &context->netid;
-         tagif_netid = run_tag_if(netid);
+         tagif_netid = run_tag_if(&context->netid);
        }
-       
+
+      log_tags(tagif_netid, ntohl(mess->xid));
+      
+      log_packet("DHCPOFFER" , &mess->yiaddr, emac, emac_len, iface_name, NULL, NULL, mess->xid);
+      
       time = calc_time(context, config, option_find(mess, sz, OPTION_LEASE_TIME, 4));
       clear_packet(mess, end);
       option_put(mess, end, OPTION_MESSAGE_TYPE, 1, DHCPOFFER);
@@ -1049,9 +1048,9 @@ size_t dhcp_reply(struct dhcp_context *context, char *iface_name, int int_index,
          option_put(mess, end, OPTION_T2, 4, (time*7)/8);
        }
       do_options(context, mess, end, req_options, offer_hostname, get_domain(mess->yiaddr), 
-                domain, tagif_netid, subnet_addr, fqdn_flags, borken_opt, pxearch, uuid, vendor_class_len);
+                netid, subnet_addr, fqdn_flags, borken_opt, pxearch, uuid, vendor_class_len, now);
       
-      return dhcp_packet_size(mess, tagif_netid, agent_id, real_end);
+      return dhcp_packet_size(mess, agent_id, real_end);
       
     case DHCPREQUEST:
       if (ignore || have_config(config, CONFIG_DISABLE))
@@ -1082,12 +1081,30 @@ size_t dhcp_reply(struct dhcp_context *context, char *iface_name, int int_index,
                  
                  if (!context)
                    {
-                     /* In auth mode, a REQUEST sent to the wrong server
-                        should be faulted, so that the client establishes 
-                        communication with us, otherwise, silently ignore. */
-                     if (!(daemon->options & OPT_AUTHORITATIVE))
-                       return 0;
-                     message = _("wrong server-ID");
+                     /* Handle very strange configs where clients have more than one route to the server.
+                        If a clients idea of its server-id matches any of our DHCP interfaces, we let it pass.
+                        Have to set override to make sure we echo back the correct server-id */
+                     struct irec *intr;
+                     
+                     enumerate_interfaces(0);
+
+                     for (intr = daemon->interfaces; intr; intr = intr->next)
+                       if (intr->addr.sa.sa_family == AF_INET &&
+                           intr->addr.in.sin_addr.s_addr == option_addr(opt).s_addr &&
+                           intr->tftp_ok)
+                         break;
+
+                     if (intr)
+                       override = intr->addr.in.sin_addr;
+                     else
+                       {
+                         /* In auth mode, a REQUEST sent to the wrong server
+                            should be faulted, so that the client establishes 
+                            communication with us, otherwise, silently ignore. */
+                         if (!option_bool(OPT_AUTHORITATIVE))
+                           return 0;
+                         message = _("wrong server-ID");
+                       }
                    }
                }
 
@@ -1101,7 +1118,7 @@ size_t dhcp_reply(struct dhcp_context *context, char *iface_name, int int_index,
          else
            {
              /* INIT-REBOOT */
-             if (!lease && !(daemon->options & OPT_AUTHORITATIVE))
+             if (!lease && !option_bool(OPT_AUTHORITATIVE))
                return 0;
              
              if (lease && lease->addr.s_addr != mess->yiaddr.s_addr)
@@ -1116,8 +1133,13 @@ size_t dhcp_reply(struct dhcp_context *context, char *iface_name, int int_index,
             as long as we can allocate the lease now - checked below.
             This makes for a smooth recovery from a lost lease DB */
          if ((lease && mess->ciaddr.s_addr != lease->addr.s_addr) ||
-             (!lease && !(daemon->options & OPT_AUTHORITATIVE)))
+             (!lease && !option_bool(OPT_AUTHORITATIVE)))
            {
+             /* A client rebinding will broadcast the request, so we may see it even 
+                if the lease is held by another server. Just ignore it in that case. 
+                If the request is unicast to us, then somethings wrong, NAK */
+             if (!unicast_dest)
+               return 0;
              message = _("lease not found");
              /* ensure we broadcast NAK */
              unicast_dest = 0;
@@ -1128,7 +1150,7 @@ size_t dhcp_reply(struct dhcp_context *context, char *iface_name, int int_index,
          mess->yiaddr = mess->ciaddr;
        }
       
-      log_packet("DHCPREQUEST", &mess->yiaddr, emac, emac_len, iface_name, NULL, mess->xid);
+      log_packet("DHCPREQUEST", &mess->yiaddr, emac, emac_len, iface_name, NULL, NULL, mess->xid);
  
       if (!message)
        {
@@ -1190,7 +1212,7 @@ size_t dhcp_reply(struct dhcp_context *context, char *iface_name, int int_index,
              
              else if (!lease)
                {            
-                 if ((lease = lease_allocate(mess->yiaddr)))
+                 if ((lease = lease4_allocate(mess->yiaddr)))
                    do_classes = 1;
                  else
                    message = _("no leases left");
@@ -1200,7 +1222,7 @@ size_t dhcp_reply(struct dhcp_context *context, char *iface_name, int int_index,
 
       if (message)
        {
-         log_packet("DHCPNAK", &mess->yiaddr, emac, emac_len, iface_name, message, mess->xid);
+         log_packet("DHCPNAK", &mess->yiaddr, emac, emac_len, iface_name, NULL, message, mess->xid);
          
          mess->yiaddr.s_addr = 0;
          clear_packet(mess, end);
@@ -1221,46 +1243,74 @@ size_t dhcp_reply(struct dhcp_context *context, char *iface_name, int int_index,
          if (context->netid.net)
            {
              context->netid.next = netid;
-             netid = &context->netid;
-             tagif_netid = run_tag_if(netid);
+             tagif_netid = run_tag_if( &context->netid);
            }
+
+         log_tags(tagif_netid, ntohl(mess->xid));
          
-#ifdef HAVE_SCRIPT
-         if (do_classes && daemon->lease_change_command)
+         if (do_classes)
            {
-             struct dhcp_netid *n;
-             
-             if (mess->giaddr.s_addr)
-               lease->giaddr = mess->giaddr;
-             
-             lease->changed = 1;
-             free(lease->extradata);
-             lease->extradata_size = lease->extradata_len = 0;
-             
-             add_extradata_opt(lease, option_find(mess, sz, OPTION_VENDOR_ID, 1));
-             add_extradata_opt(lease, option_find(mess, sz, OPTION_HOSTNAME, 1));
-             add_extradata_opt(lease, oui);
-             add_extradata_opt(lease, serial);
-             add_extradata_opt(lease, class);
-             
-             /* space-concat tag set */
-             if (!tagif_netid)
-               add_extradata_opt(lease, NULL);
-             else
-               for (n = tagif_netid; n; n = n->next)
-                 add_extradata_data(lease, (unsigned char *)n->net, strlen(n->net), n->next ? ' ' : 0); 
-             
-             if ((opt = option_find(mess, sz, OPTION_USER_CLASS, 1)))
+             /* pick up INIT-REBOOT events. */
+             lease->flags |= LEASE_CHANGED;
+
+#ifdef HAVE_SCRIPT
+             if (daemon->lease_change_command)
                {
-                 int len = option_len(opt);
-                 unsigned char *ucp = option_ptr(opt, 0);
-                 /* If the user-class option started as counted strings, the first byte will be zero. */
-                 if (len != 0 && ucp[0] == 0)
-                   ucp++, len--;
-                 add_extradata_data(lease, ucp, len, 0);
+                 struct dhcp_netid *n;
+                 
+                 if (mess->giaddr.s_addr)
+                   lease->giaddr = mess->giaddr;
+                 
+                 free(lease->extradata);
+                 lease->extradata = NULL;
+                 lease->extradata_size = lease->extradata_len = 0;
+                 
+                 add_extradata_opt(lease, option_find(mess, sz, OPTION_VENDOR_ID, 1));
+                 add_extradata_opt(lease, option_find(mess, sz, OPTION_HOSTNAME, 1));
+                 add_extradata_opt(lease, oui);
+                 add_extradata_opt(lease, serial);
+                 add_extradata_opt(lease, class);
+
+                 if ((opt = option_find(mess, sz, OPTION_AGENT_ID, 1)))
+                   {
+                     add_extradata_opt(lease, option_find1(option_ptr(opt, 0), option_ptr(opt, option_len(opt)), SUBOPT_CIRCUIT_ID, 1));
+                     add_extradata_opt(lease, option_find1(option_ptr(opt, 0), option_ptr(opt, option_len(opt)), SUBOPT_SUBSCR_ID, 1));
+                     add_extradata_opt(lease, option_find1(option_ptr(opt, 0), option_ptr(opt, option_len(opt)), SUBOPT_REMOTE_ID, 1));
+                   }
+                 else
+                   {
+                     add_extradata_opt(lease, NULL);
+                     add_extradata_opt(lease, NULL);
+                     add_extradata_opt(lease, NULL);
+                   }
+
+                 /* space-concat tag set */
+                 if (!tagif_netid)
+                   add_extradata_opt(lease, NULL);
+                 else
+                   for (n = tagif_netid; n; n = n->next)
+                     {
+                       struct dhcp_netid *n1;
+                       /* kill dupes */
+                       for (n1 = n->next; n1; n1 = n1->next)
+                         if (strcmp(n->net, n1->net) == 0)
+                           break;
+                       if (!n1)
+                         lease_add_extradata(lease, (unsigned char *)n->net, strlen(n->net), n->next ? ' ' : 0); 
+                     }
+                 
+                 if ((opt = option_find(mess, sz, OPTION_USER_CLASS, 1)))
+                   {
+                     int len = option_len(opt);
+                     unsigned char *ucp = option_ptr(opt, 0);
+                     /* If the user-class option started as counted strings, the first byte will be zero. */
+                     if (len != 0 && ucp[0] == 0)
+                       ucp++, len--;
+                     lease_add_extradata(lease, ucp, len, 0);
+                   }
                }
-           }
 #endif
+           }
          
          if (!hostname_auth && (client_hostname = host_from_dns(mess->yiaddr)))
            {
@@ -1270,7 +1320,7 @@ size_t dhcp_reply(struct dhcp_context *context, char *iface_name, int int_index,
            }
          
          time = calc_time(context, config, option_find(mess, sz, OPTION_LEASE_TIME, 4));
-         lease_set_hwaddr(lease, mess->chaddr, clid, mess->hlen, mess->htype, clid_len);
+         lease_set_hwaddr(lease, mess->chaddr, clid, mess->hlen, mess->htype, clid_len, now, do_classes);
          
          /* if all the netids in the ignore_name list are present, ignore client-supplied name */
          if (!hostname_auth)
@@ -1301,17 +1351,17 @@ size_t dhcp_reply(struct dhcp_context *context, char *iface_name, int int_index,
            }
 
          if (hostname)
-           lease_set_hostname(lease, hostname, hostname_auth);
+           lease_set_hostname(lease, hostname, hostname_auth, get_domain(lease->addr), domain);
          
          lease_set_expires(lease, time, now);
-         lease_set_interface(lease, int_index);
+         lease_set_interface(lease, int_index, now);
 
          if (override.s_addr != 0)
            lease->override = override;
          else
            override = lease->override;
 
-         log_packet("DHCPACK", &mess->yiaddr, emac, emac_len, iface_name, hostname, mess->xid);  
+         log_packet("DHCPACK", &mess->yiaddr, emac, emac_len, iface_name, hostname, NULL, mess->xid);  
          
          clear_packet(mess, end);
          option_put(mess, end, OPTION_MESSAGE_TYPE, 1, DHCPACK);
@@ -1325,16 +1375,16 @@ size_t dhcp_reply(struct dhcp_context *context, char *iface_name, int int_index,
              option_put(mess, end, OPTION_T2, 4, ((time/8)*7) - fuzz);
            }
          do_options(context, mess, end, req_options, hostname, get_domain(mess->yiaddr), 
-                    domain, tagif_netid, subnet_addr, fqdn_flags, borken_opt, pxearch, uuid, vendor_class_len);
+                    netid, subnet_addr, fqdn_flags, borken_opt, pxearch, uuid, vendor_class_len, now);
        }
 
-      return dhcp_packet_size(mess, tagif_netid, agent_id, real_end); 
+      return dhcp_packet_size(mess, agent_id, real_end); 
       
     case DHCPINFORM:
       if (ignore || have_config(config, CONFIG_DISABLE))
        message = _("ignored");
       
-      log_packet("DHCPINFORM", &mess->ciaddr, emac, emac_len, iface_name, message, mess->xid);
+      log_packet("DHCPINFORM", &mess->ciaddr, emac, emac_len, iface_name, message, NULL, mess->xid);
      
       if (message || mess->ciaddr.s_addr == 0)
        return 0;
@@ -1349,20 +1399,22 @@ size_t dhcp_reply(struct dhcp_context *context, char *iface_name, int int_index,
          lease->hostname)
        hostname = lease->hostname;
       
-      if (!hostname && (hostname = host_from_dns(mess->ciaddr)))
-       domain = get_domain(mess->ciaddr);
-
-      log_packet("DHCPACK", &mess->ciaddr, emac, emac_len, iface_name, hostname, mess->xid);
+      if (!hostname)
+       hostname = host_from_dns(mess->ciaddr);
       
       if (context && context->netid.net)
        {
          context->netid.next = netid;
-         netid = &context->netid;
-         tagif_netid = run_tag_if(netid);
+         tagif_netid = run_tag_if(&context->netid);
        }
+
+      log_tags(tagif_netid, ntohl(mess->xid));
+      
+      log_packet("DHCPACK", &mess->ciaddr, emac, emac_len, iface_name, hostname, NULL, mess->xid);
       
       if (lease)
        {
+         lease_set_interface(lease, int_index, now);
          if (override.s_addr != 0)
            lease->override = override;
          else
@@ -1372,58 +1424,31 @@ size_t dhcp_reply(struct dhcp_context *context, char *iface_name, int int_index,
       clear_packet(mess, end);
       option_put(mess, end, OPTION_MESSAGE_TYPE, 1, DHCPACK);
       option_put(mess, end, OPTION_SERVER_IDENTIFIER, INADDRSZ, ntohl(server_id(context, override, fallback).s_addr));
-      
-      if (lease)
+     
+      /* RFC 2131 says that DHCPINFORM shouldn't include lease-time parameters, but 
+        we supply a utility which makes DHCPINFORM requests to get this information.
+        Only include lease time if OPTION_LEASE_TIME is in the parameter request list,
+        which won't be true for ordinary clients, but will be true for the 
+        dhcp_lease_time utility. */
+      if (lease && in_list(req_options, OPTION_LEASE_TIME))
        {
          if (lease->expires == 0)
            time = 0xffffffff;
          else
            time = (unsigned int)difftime(lease->expires, now);
          option_put(mess, end, OPTION_LEASE_TIME, 4, time);
-         lease_set_interface(lease, int_index);
        }
 
       do_options(context, mess, end, req_options, hostname, get_domain(mess->ciaddr),
-                domain, tagif_netid, subnet_addr, fqdn_flags, borken_opt, pxearch, uuid, vendor_class_len);
+                netid, subnet_addr, fqdn_flags, borken_opt, pxearch, uuid, vendor_class_len, now);
       
       *is_inform = 1; /* handle reply differently */
-      return dhcp_packet_size(mess, tagif_netid, agent_id, real_end); 
+      return dhcp_packet_size(mess, agent_id, real_end); 
     }
   
   return 0;
 }
 
-static int match_bytes(struct dhcp_opt *o, unsigned char *p, int len)
-{
-  int i;
-  
-  if (o->len > len)
-    return 0;
-  
-  if (o->len == 0)
-    return 1;
-     
-  if (o->flags & DHOPT_HEX)
-    { 
-      if (memcmp_masked(o->val, p, o->len, o->u.wildcard_mask))
-       return 1;
-    }
-  else 
-    for (i = 0; i <= (len - o->len); ) 
-      {
-       if (memcmp(o->val, p + i, o->len) == 0)
-         return 1;
-           
-       if (o->flags & DHOPT_STRING)
-         i++;
-       else
-         i += o->len;
-      }
-  
-  return 0;
-}
-      
-
 /* find a good value to use as MAC address for logging and address-allocation hashing.
    This is normally just the chaddr field from the DHCP packet,
    but eg Firewire will have hlen == 0 and use the client-id instead. 
@@ -1479,7 +1504,7 @@ static struct in_addr server_id(struct dhcp_context *context, struct in_addr ove
 {
   if (override.s_addr != 0)
     return override;
-  else if (context)
+  else if (context && context->local.s_addr != 0)
     return context->local;
   else
     return fallback;
@@ -1509,112 +1534,58 @@ static int sanitise(unsigned char *opt, char *buf)
 }
 
 #ifdef HAVE_SCRIPT
-static void add_extradata_data(struct dhcp_lease *lease, unsigned char *data, size_t len, int delim)
-{
-  if ((lease->extradata_size - lease->extradata_len) < (len + 1))
-    {
-      size_t newsz = lease->extradata_len + len + 100;
-      unsigned char *new = whine_malloc(newsz);
-  
-      if (!new)
-       return;
-      
-      if (lease->extradata)
-       {
-         memcpy(new, lease->extradata, lease->extradata_len);
-         free(lease->extradata);
-       }
-
-      lease->extradata = new;
-      lease->extradata_size = newsz;
-    }
-
-  if (len != 0)
-    memcpy(lease->extradata + lease->extradata_len, data, len);
-  lease->extradata[lease->extradata_len + len] = delim;
-  lease->extradata_len += len + 1; 
-}
-
 static void add_extradata_opt(struct dhcp_lease *lease, unsigned char *opt)
 {
   if (!opt)
-    add_extradata_data(lease, NULL, 0, 0);
+    lease_add_extradata(lease, NULL, 0, 0);
   else
-    {
-      size_t i, len = option_len(opt);
-      unsigned char *ucp = option_ptr(opt, 0);
-      
-      /* check for embeded NULLs */
-      for (i = 0; i < len; i++)
-       if (ucp[i] == 0)
-         {
-           len = i;
-           break;
-         }
-
-      add_extradata_data(lease, ucp, len, 0);
-    }
+    lease_add_extradata(lease, option_ptr(opt, 0), option_len(opt), 0); 
 }
 #endif
 
 static void log_packet(char *type, void *addr, unsigned char *ext_mac, 
-                      int mac_len, char *interface, char *string, u32 xid)
+                      int mac_len, char *interface, char *string, char *err, u32 xid)
 {
   struct in_addr a;
  
+  if (!err && !option_bool(OPT_LOG_OPTS) && option_bool(OPT_QUIET_DHCP))
+    return;
+  
   /* addr may be misaligned */
   if (addr)
     memcpy(&a, addr, sizeof(a));
   
   print_mac(daemon->namebuff, ext_mac, mac_len);
   
-  if(daemon->options & OPT_LOG_OPTS)
-     my_syslog(MS_DHCP | LOG_INFO, "%u %s(%s) %s%s%s %s",
+  if(option_bool(OPT_LOG_OPTS))
+     my_syslog(MS_DHCP | LOG_INFO, "%u %s(%s) %s%s%s %s%s",
               ntohl(xid), 
               type,
               interface, 
               addr ? inet_ntoa(a) : "",
               addr ? " " : "",
               daemon->namebuff,
-              string ? string : "");
+              string ? string : "",
+              err ? err : "");
   else
-    my_syslog(MS_DHCP | LOG_INFO, "%s(%s) %s%s%s %s",
+    my_syslog(MS_DHCP | LOG_INFO, "%s(%s) %s%s%s %s%s",
              type,
              interface, 
              addr ? inet_ntoa(a) : "",
              addr ? " " : "",
              daemon->namebuff,
-             string ? string : "");
+             string ? string : "",
+             err ? err : "");
 }
 
 static void log_options(unsigned char *start, u32 xid)
 {
   while (*start != OPTION_END)
     {
-      int is_ip, is_name, i;
-      char *text = option_string(start[0], &is_ip, &is_name);
-      unsigned char trunc = option_len(start);
-      
-      if (is_ip)
-       for (daemon->namebuff[0]= 0, i = 0; i <= trunc - INADDRSZ; i += INADDRSZ) 
-         {
-           if (i != 0)
-             strncat(daemon->namebuff, ", ", 256 - strlen(daemon->namebuff));
-           strncat(daemon->namebuff, inet_ntoa(option_addr_arr(start, i)), 256 - strlen(daemon->namebuff));
-         }
-      else if (!is_name || !sanitise(start, daemon->namebuff))
-       {
-         if (trunc > 13)
-           trunc = 13;
-         print_mac(daemon->namebuff, option_ptr(start, 0), trunc);
-       }
+      char *optname = option_string(AF_INET, start[0], option_ptr(start, 0), option_len(start), daemon->namebuff, MAXDNAME);
       
-      my_syslog(MS_DHCP | LOG_INFO, "%u sent size:%3d option:%3d%s%s%s%s%s", 
-               ntohl(xid), option_len(start), start[0],
-               text ? ":" : "", text ? text : "",
-               trunc == 0 ? "" : "  ",
-               trunc == 0 ? "" : daemon->namebuff,
-               trunc == option_len(start) ? "" : "...");
+      my_syslog(MS_DHCP | LOG_INFO, "%u sent size:%3d option:%3d %s  %s", 
+               ntohl(xid), option_len(start), start[0], optname, daemon->namebuff);
       start += start[1] + 2;
     }
 }
@@ -1669,22 +1640,17 @@ static unsigned char *option_find(struct dhcp_packet *mess, size_t size, int opt
   return NULL;
 }
 
-static struct in_addr option_addr_arr(unsigned char *opt, int offset)
+static struct in_addr option_addr(unsigned char *opt)
 {
-  /* this worries about unaligned data in the option. */
+   /* this worries about unaligned data in the option. */
   /* struct in_addr is network byte order */
   struct in_addr ret;
 
-  memcpy(&ret, option_ptr(opt, offset), INADDRSZ);
+  memcpy(&ret, option_ptr(opt, 0), INADDRSZ);
 
   return ret;
 }
 
-static struct in_addr option_addr(unsigned char *opt)
-{
-  return option_addr_arr(opt, 0);
-}
-
 static unsigned int option_uint(unsigned char *opt, int offset, int size)
 {
   /* this worries about unaligned data and byte order */
@@ -1719,15 +1685,12 @@ static unsigned char *find_overload(struct dhcp_packet *mess)
   return NULL;
 }
 
-static size_t dhcp_packet_size(struct dhcp_packet *mess, struct dhcp_netid *netid,
-                              unsigned char *agent_id, unsigned char *real_end)
+static size_t dhcp_packet_size(struct dhcp_packet *mess, unsigned char *agent_id, unsigned char *real_end)
 {
   unsigned char *p = dhcp_skip_opts(&mess->options[0] + sizeof(u32));
   unsigned char *overload;
   size_t ret;
-  struct dhcp_netid_list *id_list;
-  struct dhcp_netid *n;
-
+  
   /* move agent_id back down to the end of the packet */
   if (agent_id)
     {
@@ -1736,58 +1699,31 @@ static size_t dhcp_packet_size(struct dhcp_packet *mess, struct dhcp_netid *neti
       memset(p, 0, real_end - p); /* in case of overlap */
     }
   
-  /* We do logging too */
-  if (netid && (daemon->options & OPT_LOG_OPTS))
-    {
-      char *s = daemon->namebuff;
-      for (*s = 0; netid; netid = netid->next)
-       {
-         /* kill dupes. */
-         for (n = netid->next; n; n = n->next)
-           if (strcmp(netid->net, n->net) == 0)
-             break;
-         
-         if (!n)
-           {
-             strncat (s, netid->net, (MAXDNAME-1) - strlen(s));
-             if (netid->next)
-               strncat (s, ", ", (MAXDNAME-1) - strlen(s));
-           }
-       }
-      my_syslog(MS_DHCP | LOG_INFO, _("%u tags: %s"), ntohl(mess->xid), s);
-    } 
-   
   /* add END options to the regions. */
   overload = find_overload(mess);
   
   if (overload && (option_uint(overload, 0, 1) & 1))
     {
       *dhcp_skip_opts(mess->file) = OPTION_END;
-      if (daemon->options & OPT_LOG_OPTS)
+      if (option_bool(OPT_LOG_OPTS))
        log_options(mess->file, mess->xid);
     }
-  else if ((daemon->options & OPT_LOG_OPTS) && strlen((char *)mess->file) != 0)
+  else if (option_bool(OPT_LOG_OPTS) && strlen((char *)mess->file) != 0)
     my_syslog(MS_DHCP | LOG_INFO, _("%u bootfile name: %s"), ntohl(mess->xid), (char *)mess->file);
   
   if (overload && (option_uint(overload, 0, 1) & 2))
     {
       *dhcp_skip_opts(mess->sname) = OPTION_END;
-      if (daemon->options & OPT_LOG_OPTS)
+      if (option_bool(OPT_LOG_OPTS))
        log_options(mess->sname, mess->xid);
     }
-  else if ((daemon->options & OPT_LOG_OPTS) && strlen((char *)mess->sname) != 0)
+  else if (option_bool(OPT_LOG_OPTS) && strlen((char *)mess->sname) != 0)
     my_syslog(MS_DHCP | LOG_INFO, _("%u server name: %s"), ntohl(mess->xid), (char *)mess->sname);
 
 
   *p++ = OPTION_END;
   
-  for (id_list = daemon->force_broadcast; id_list; id_list = id_list->next)
-    if ((!id_list->list) || match_netid(id_list->list, netid, 0))
-      break;
-  if (id_list)
-    mess->flags |= htons(0x8000); /* force broadcast */
-  
-  if (daemon->options & OPT_LOG_OPTS)
+  if (option_bool(OPT_LOG_OPTS))
     {
       if (mess->siaddr.s_addr != 0)
        my_syslog(MS_DHCP | LOG_INFO, _("%u next server: %s"), ntohl(mess->xid), inet_ntoa(mess->siaddr));
@@ -1849,7 +1785,7 @@ static unsigned char *free_space(struct dhcp_packet *mess, unsigned char *end, i
              if (overload[2] & 2)
                {
                  p = dhcp_skip_opts(mess->sname);
-                 if (p + len + 3 >= mess->sname + sizeof(mess->file))
+                 if (p + len + 3 >= mess->sname + sizeof(mess->sname))
                    p = NULL;
                }
            }
@@ -1916,7 +1852,8 @@ static int do_opt(struct dhcp_opt *opt, unsigned char *p, struct dhcp_context *c
            }
        }
       else
-       memcpy(p, opt->val, len);
+       /* empty string may be extended to "\0" by null_term */
+       memcpy(p, opt->val ? opt->val : (unsigned char *)"", len);
     }  
   return len;
 }
@@ -1936,20 +1873,14 @@ static int in_list(unsigned char *list, int opt)
   return 0;
 }
 
-static struct dhcp_opt *option_find2(struct dhcp_netid *netid, struct dhcp_opt *opts, int opt)
+static struct dhcp_opt *option_find2(int opt)
 {
-  struct dhcp_opt *tmp;  
-  for (tmp = opts; tmp; tmp = tmp->next)
-    if (tmp->opt == opt && !(tmp->flags & (DHOPT_ENCAPSULATE | DHOPT_VENDOR | DHOPT_RFC3925)))
-      if (match_netid(tmp->netid, netid, 0))
-       return tmp;
-
-  /* No match, look for one without a netid */
-  for (tmp = opts; tmp; tmp = tmp->next)
-    if (tmp->opt == opt && !(tmp->flags & (DHOPT_ENCAPSULATE | DHOPT_VENDOR | DHOPT_RFC3925)))
-      if (match_netid(tmp->netid, netid, 1))
-       return tmp;
-                     
+  struct dhcp_opt *opts;
+  
+  for (opts = daemon->dhcp_opts; opts; opts = opts->next)
+    if (opts->opt == opt && (opts->flags & DHOPT_TAGOK))
+      return opts;
+  
   return NULL;
 }
 
@@ -2049,7 +1980,7 @@ static int prune_vendor_opts(struct dhcp_netid *netid)
   return force;
 }
 
-static struct dhcp_opt *pxe_opts(int pxe_arch, struct dhcp_netid *netid, struct in_addr local)
+static struct dhcp_opt *pxe_opts(int pxe_arch, struct dhcp_netid *netid, struct in_addr local, time_t now)
 {
 #define NUM_OPTS 4  
 
@@ -2106,8 +2037,9 @@ static struct dhcp_opt *pxe_opts(int pxe_arch, struct dhcp_netid *netid, struct
            return daemon->dhcp_opts;
          }
        
-       boot_server = service->basename ? local : service->server;
-
+       boot_server = service->basename ? local : 
+         (service->sname ? a_record_from_hosts(service->sname, now) : service->server);
+       
        if (boot_server.s_addr != 0)
          {
            if (q - (unsigned char *)daemon->dhcp_buff3 + 3 + INADDRSZ >= 253)
@@ -2198,13 +2130,14 @@ static void do_options(struct dhcp_context *context,
                       unsigned char *end, 
                       unsigned char *req_options,
                       char *hostname, 
-                      char *domain, char *config_domain,
+                      char *domain,
                       struct dhcp_netid *netid,
                       struct in_addr subnet_addr,
                       unsigned char fqdn_flags,
                       int null_term, int pxe_arch,
                       unsigned char *uuid,
-                      int vendor_class_len)
+                      int vendor_class_len,
+                      time_t now)
 {
   struct dhcp_opt *opt, *config_opts = daemon->dhcp_opts;
   struct dhcp_boot *boot;
@@ -2213,22 +2146,26 @@ static void do_options(struct dhcp_context *context,
   unsigned char f0 = 0, s0 = 0;
   int done_file = 0, done_server = 0;
   int done_vendor_class = 0;
+  struct dhcp_netid *tagif;
+  struct dhcp_netid_list *id_list;
 
-  if (config_domain && (!domain || !hostname_isequal(domain, config_domain)))
-    my_syslog(MS_DHCP | LOG_WARNING, _("Ignoring domain %s for DHCP host name %s"), config_domain, hostname);
-  
+  /* filter options based on tags, those we want get DHOPT_TAGOK bit set */
+  if (context)
+    context->netid.next = NULL;
+  tagif = option_filter(netid, context && context->netid.net ? &context->netid : NULL, config_opts);
+       
   /* logging */
-  if ((daemon->options & OPT_LOG_OPTS) && req_options)
+  if (option_bool(OPT_LOG_OPTS) && req_options)
     {
       char *q = daemon->namebuff;
       for (i = 0; req_options[i] != OPTION_END; i++)
        {
-         char *s = option_string(req_options[i], NULL, NULL);
+         char *s = option_string(AF_INET, req_options[i], NULL, 0, NULL, 0);
          q += snprintf(q, MAXDNAME - (q - daemon->namebuff),
                        "%d%s%s%s", 
                        req_options[i],
-                       s ? ":" : "",
-                       s ? s : ""
+                       strlen(s) != 0 ? ":" : "",
+                       s, 
                        req_options[i+1] == OPTION_END ? "" : ", ");
          if (req_options[i+1] == OPTION_END || (q - daemon->namebuff) > 40)
            {
@@ -2238,6 +2175,12 @@ static void do_options(struct dhcp_context *context,
        }
     }
       
+  for (id_list = daemon->force_broadcast; id_list; id_list = id_list->next)
+    if ((!id_list->list) || match_netid(id_list->list, netid, 0))
+      break;
+  if (id_list)
+    mess->flags |= htons(0x8000); /* force broadcast */
+  
   if (context)
     mess->siaddr = context->local;
   
@@ -2247,11 +2190,11 @@ static void do_options(struct dhcp_context *context,
      provide an manual option to disable it.
      Some PXE ROMs have bugs (surprise!) and need zero-terminated 
      names, so we always send those.  */
-  if ((boot = find_boot(netid)))
+  if ((boot = find_boot(tagif)))
     {
       if (boot->sname)
        {         
-         if (!(daemon->options & OPT_NO_OVERRIDE) &&
+         if (!option_bool(OPT_NO_OVERRIDE) &&
              req_options && 
              in_list(req_options, OPTION_SNAME))
            option_put_string(mess, end, OPTION_SNAME, boot->sname, 1);
@@ -2261,7 +2204,7 @@ static void do_options(struct dhcp_context *context,
       
       if (boot->file)
        {
-         if (!(daemon->options & OPT_NO_OVERRIDE) &&
+         if (!option_bool(OPT_NO_OVERRIDE) &&
              req_options && 
              in_list(req_options, OPTION_FILENAME))
            option_put_string(mess, end, OPTION_FILENAME, boot->file, 1);
@@ -2269,8 +2212,10 @@ static void do_options(struct dhcp_context *context,
            strncpy((char *)mess->file, boot->file, sizeof(mess->file)-1);
        }
       
-      if (boot->next_server.s_addr)
+      if (boot->next_server.s_addr) 
        mess->siaddr = boot->next_server;
+      else if (boot->tftp_sname)
+       mess->siaddr = a_record_from_hosts(boot->tftp_sname, now);
     }
   else
     /* Use the values of the relevant options if no dhcp-boot given and
@@ -2279,20 +2224,20 @@ static void do_options(struct dhcp_context *context,
        dhcp-optsfile. */
     {
       if ((!req_options || !in_list(req_options, OPTION_FILENAME)) &&
-         (opt = option_find2(netid, config_opts, OPTION_FILENAME)) && !(opt->flags & DHOPT_FORCE))
+         (opt = option_find2(OPTION_FILENAME)) && !(opt->flags & DHOPT_FORCE))
        {
          strncpy((char *)mess->file, (char *)opt->val, sizeof(mess->file)-1);
          done_file = 1;
        }
       
       if ((!req_options || !in_list(req_options, OPTION_SNAME)) &&
-         (opt = option_find2(netid, config_opts, OPTION_SNAME)) && !(opt->flags & DHOPT_FORCE))
+         (opt = option_find2(OPTION_SNAME)) && !(opt->flags & DHOPT_FORCE))
        {
          strncpy((char *)mess->sname, (char *)opt->val, sizeof(mess->sname)-1);
          done_server = 1;
        }
       
-      if ((opt = option_find2(netid, config_opts, OPTION_END)))
+      if ((opt = option_find2(OPTION_END)))
        mess->siaddr.s_addr = ((struct in_addr *)opt->val)->s_addr;     
     }
         
@@ -2300,7 +2245,7 @@ static void do_options(struct dhcp_context *context,
      fields look like they are in use, even when they aren't. This gets restored
      at the end of this function. */
 
-  if (!req_options || (daemon->options & OPT_NO_OVERRIDE))
+  if (!req_options || option_bool(OPT_NO_OVERRIDE))
     {
       f0 = mess->file[0];
       mess->file[0] = 1;
@@ -2320,36 +2265,37 @@ static void do_options(struct dhcp_context *context,
   /* replies to DHCPINFORM may not have a valid context */
   if (context)
     {
-      if (!option_find2(netid, config_opts, OPTION_NETMASK))
+      if (!option_find2(OPTION_NETMASK))
        option_put(mess, end, OPTION_NETMASK, INADDRSZ, ntohl(context->netmask.s_addr));
   
       /* May not have a "guessed" broadcast address if we got no packets via a relay
         from this net yet (ie just unicast renewals after a restart */
       if (context->broadcast.s_addr &&
-         !option_find2(netid, config_opts, OPTION_BROADCAST))
+         !option_find2(OPTION_BROADCAST))
        option_put(mess, end, OPTION_BROADCAST, INADDRSZ, ntohl(context->broadcast.s_addr));
       
       /* Same comments as broadcast apply, and also may not be able to get a sensible
         default when using subnet select.  User must configure by steam in that case. */
       if (context->router.s_addr &&
          in_list(req_options, OPTION_ROUTER) &&
-         !option_find2(netid, config_opts, OPTION_ROUTER))
+         !option_find2(OPTION_ROUTER))
        option_put(mess, end, OPTION_ROUTER, INADDRSZ, ntohl(context->router.s_addr));
       
-      if (in_list(req_options, OPTION_DNSSERVER) &&
-         !option_find2(netid, config_opts, OPTION_DNSSERVER))
+      if (daemon->port == NAMESERVER_PORT &&
+         in_list(req_options, OPTION_DNSSERVER) &&
+         !option_find2(OPTION_DNSSERVER))
        option_put(mess, end, OPTION_DNSSERVER, INADDRSZ, ntohl(context->local.s_addr));
     }
 
   if (domain && in_list(req_options, OPTION_DOMAINNAME) && 
-      !option_find2(netid, config_opts, OPTION_DOMAINNAME))
+      !option_find2(OPTION_DOMAINNAME))
     option_put_string(mess, end, OPTION_DOMAINNAME, domain, null_term);
  
   /* Note that we ignore attempts to set the fqdn using --dhc-option=81,<name> */
   if (hostname)
     {
       if (in_list(req_options, OPTION_HOSTNAME) &&
-         !option_find2(netid, config_opts, OPTION_HOSTNAME))
+         !option_find2(OPTION_HOSTNAME))
        option_put_string(mess, end, OPTION_HOSTNAME, hostname, null_term);
       
       if (fqdn_flags != 0)
@@ -2363,10 +2309,12 @@ static void do_options(struct dhcp_context *context,
 
          if (domain)
            len += strlen(domain) + 1;
-         
+         else if (fqdn_flags & 0x04)
+           len--;
+
          if ((p = free_space(mess, end, OPTION_CLIENT_FQDN, len)))
            {
-             *(p++) = fqdn_flags;
+             *(p++) = fqdn_flags & 0x0f; /* MBZ bits to zero */ 
              *(p++) = 255;
              *(p++) = 255;
 
@@ -2374,8 +2322,10 @@ static void do_options(struct dhcp_context *context,
                {
                  p = do_rfc1035_name(p, hostname);
                  if (domain)
-                   p = do_rfc1035_name(p, domain);
-                 *p++ = 0;
+                   {
+                     p = do_rfc1035_name(p, domain);
+                     *p++ = 0;
+                   }
                }
              else
                {
@@ -2398,6 +2348,10 @@ static void do_options(struct dhcp_context *context,
     {
       int optno = opt->opt;
 
+      /* netids match and not encapsulated? */
+      if (!(opt->flags & DHOPT_TAGOK))
+       continue;
+      
       /* was it asked for, or are we sending it anyway? */
       if (!(opt->flags & DHOPT_FORCE) && !in_list(req_options, optno))
        continue;
@@ -2416,10 +2370,6 @@ static void do_options(struct dhcp_context *context,
       if (optno == OPTION_FILENAME && done_file)
        continue;
       
-      /* netids match and not encapsulated? */
-      if (opt != option_find2(netid, config_opts, optno))
-       continue;
-      
       /* For the options we have default values on
         dhc-option=<optionno> means "don't include this option"
         not "include a zero-length option" */
@@ -2457,8 +2407,8 @@ static void do_options(struct dhcp_context *context,
 
   /* Now send options to be encapsulated in arbitrary options, 
      eg dhcp-option=encap:172,17,.......
-     Also hand vendor-identifying vendor-encapsulated options,
-     dhcp-option = rfc3925-encap:13,17,.......
+     Also handle vendor-identifying vendor-encapsulated options,
+     dhcp-option = vi-encap:13,17,.......
      The may be more that one "outer" to do, so group
      all the options which match each outer in turn. */
   for (opt = config_opts; opt; opt = opt->next)
@@ -2486,7 +2436,7 @@ static void do_options(struct dhcp_context *context,
                continue;
              
              o->flags |= DHOPT_ENCAP_DONE;
-             if (match_netid(o->netid, netid, 1) &&
+             if (match_netid(o->netid, tagif, 1) &&
                  ((o->flags & DHOPT_FORCE) || in_list(req_options, outer)))
                {
                  o->flags |= DHOPT_ENCAP_MATCH;
@@ -2520,12 +2470,12 @@ static void do_options(struct dhcp_context *context,
        }
     }      
 
-  force_encap = prune_vendor_opts(netid);
+  force_encap = prune_vendor_opts(tagif);
   
   if (context && pxe_arch != -1)
     {
       pxe_misc(mess, end, uuid);
-      config_opts = pxe_opts(pxe_arch, netid, context->local);
+      config_opts = pxe_opts(pxe_arch, tagif, context->local, now);
     }
 
   if ((force_encap || in_list(req_options, OPTION_VENDOR_CLASS_OPT)) &&
@@ -2537,7 +2487,7 @@ static void do_options(struct dhcp_context *context,
     memcpy(p, daemon->dhcp_buff3, vendor_class_len);       
    
    /* restore BOOTP anti-overload hack */
-  if (!req_options || (daemon->options & OPT_NO_OVERRIDE))
+  if (!req_options || option_bool(OPT_NO_OVERRIDE))
     {
       mess->file[0] = f0;
       mess->sname[0] = s0;