Imported Upstream version 2.88
[platform/upstream/dnsmasq.git] / src / option.c
index ffce9fc..8e61a6b 100644 (file)
@@ -1,4 +1,4 @@
-/* dnsmasq is Copyright (c) 2000-2021 Simon Kelley
+/* dnsmasq is Copyright (c) 2000-2022 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
@@ -174,7 +174,18 @@ struct myoption {
 #define LOPT_CMARK_ALST_EN 365
 #define LOPT_CMARK_ALST    366
 #define LOPT_QUIET_TFTP    367
+#define LOPT_NFTSET        368
+#define LOPT_FILTER_A      369
+#define LOPT_FILTER_AAAA   370
+#define LOPT_STRIP_SBNET   371
+#define LOPT_STRIP_MAC     372
+#define LOPT_CONF_OPT      373
+#define LOPT_CONF_SCRIPT   374
+#define LOPT_RANDPORT_LIM  375
+#define LOPT_FAST_RETRY    376
+#define LOPT_STALE_CACHE   377
+#define LOPT_NORR          378
+
 #ifdef HAVE_GETOPT_LONG
 static const struct option opts[] =  
 #else
@@ -211,6 +222,8 @@ static const struct myoption opts[] =
     { "ignore-address", 1, 0, LOPT_IGNORE_ADDR },
     { "selfmx", 0, 0, 'e' },
     { "filterwin2k", 0, 0, 'f' },
+    { "filter-A", 0, 0, LOPT_FILTER_A },
+    { "filter-AAAA", 0, 0, LOPT_FILTER_AAAA },
     { "pid-file", 2, 0, 'x' },
     { "strict-order", 0, 0, 'o' },
     { "server", 1, 0, 'S' },
@@ -218,11 +231,13 @@ static const struct myoption opts[] =
     { "local", 1, 0, LOPT_LOCAL },
     { "address", 1, 0, 'A' },
     { "conf-file", 2, 0, 'C' },
+    { "conf-script", 1, 0, LOPT_CONF_SCRIPT },
     { "no-resolv", 0, 0, 'R' },
     { "expand-hosts", 0, 0, 'E' },
     { "localmx", 0, 0, 'L' },
     { "local-ttl", 1, 0, 'T' },
     { "no-negcache", 0, 0, 'N' },
+    { "no-round-robin", 0, 0, LOPT_NORR },
     { "addn-hosts", 1, 0, 'H' },
     { "hostsdir", 1, 0, LOPT_HOST_INOTIFY },
     { "query-port", 1, 0, 'Q' },
@@ -309,7 +324,9 @@ static const struct myoption opts[] =
     { "dhcp-generate-names", 2, 0, LOPT_GEN_NAMES },
     { "rebind-localhost-ok", 0, 0,  LOPT_LOC_REBND },
     { "add-mac", 2, 0, LOPT_ADD_MAC },
+    { "strip-mac", 0, 0, LOPT_STRIP_MAC },
     { "add-subnet", 2, 0, LOPT_ADD_SBNET },
+    { "strip-subnet", 0, 0, LOPT_STRIP_SBNET },
     { "add-cpe-id", 1, 0 , LOPT_CPE_ID },
     { "proxy-dnssec", 0, 0, LOPT_DNSSEC },
     { "dhcp-sequential-ip", 0, 0,  LOPT_INCR_ADDR },
@@ -327,6 +344,7 @@ static const struct myoption opts[] =
     { "auth-sec-servers", 1, 0, LOPT_AUTHSFS },
     { "auth-peer", 1, 0, LOPT_AUTHPEER }, 
     { "ipset", 1, 0, LOPT_IPSET },
+    { "nftset", 1, 0, LOPT_NFTSET },
     { "connmark-allowlist-enable", 2, 0, LOPT_CMARK_ALST_EN },
     { "connmark-allowlist", 1, 0, LOPT_CMARK_ALST },
     { "synth-domain", 1, 0, LOPT_SYNTH },
@@ -351,8 +369,11 @@ static const struct myoption opts[] =
     { "dhcp-ignore-clid", 0, 0,  LOPT_IGNORE_CLID },
     { "dynamic-host", 1, 0, LOPT_DYNHOST },
     { "log-debug", 0, 0, LOPT_LOG_DEBUG },
-       { "umbrella", 2, 0, LOPT_UMBRELLA },
+    { "umbrella", 2, 0, LOPT_UMBRELLA },
     { "quiet-tftp", 0, 0, LOPT_QUIET_TFTP },
+    { "port-limit", 1, 0, LOPT_RANDPORT_LIM },
+    { "fast-dns-retry", 2, 0, LOPT_FAST_RETRY },
+    { "use-stale-cache", 2, 0 , LOPT_STALE_CACHE },
     { NULL, 0, 0, 0 }
   };
 
@@ -380,6 +401,8 @@ static struct {
   { 'e', OPT_SELFMX, NULL, gettext_noop("Return self-pointing MX records for local hosts."), NULL },
   { 'E', OPT_EXPAND, NULL, gettext_noop("Expand simple names in /etc/hosts with domain-suffix."), NULL },
   { 'f', OPT_FILTER, NULL, gettext_noop("Don't forward spurious DNS requests from Windows hosts."), NULL },
+  { LOPT_FILTER_A, OPT_FILTER_A, NULL, gettext_noop("Don't include IPv4 addresses in DNS answers."), NULL },
+  { LOPT_FILTER_AAAA, OPT_FILTER_AAAA, NULL, gettext_noop("Don't include IPv6 addresses in DNS answers."), NULL },
   { 'F', ARG_DUP, "<ipaddr>,...", gettext_noop("Enable DHCP in the range given with lease duration."), NULL },
   { 'g', ARG_ONE, "<groupname>", gettext_noop("Change to this group after startup (defaults to %s)."), CHGRP },
   { 'G', ARG_DUP, "<hostspec>", gettext_noop("Set address or hostname for a specified machine."), NULL },
@@ -408,6 +431,7 @@ static struct {
   { 'M', ARG_DUP, "<bootp opts>", gettext_noop("Specify BOOTP options to DHCP server."), NULL },
   { 'n', OPT_NO_POLL, NULL, gettext_noop("Do NOT poll %s file, reload only on SIGHUP."), RESOLVFILE }, 
   { 'N', OPT_NO_NEG, NULL, gettext_noop("Do NOT cache failed search results."), NULL },
+  { LOPT_STALE_CACHE, ARG_ONE, "[=<max_expired>]", gettext_noop("Use expired cache data for faster reply."), NULL },
   { 'o', OPT_ORDER, NULL, gettext_noop("Use nameservers strictly in the order given in %s."), RESOLVFILE },
   { 'O', ARG_DUP, "<optspec>", gettext_noop("Specify options to be sent to DHCP clients."), NULL },
   { LOPT_FORCE, ARG_DUP, "<optspec>", gettext_noop("DHCP option sent even if the client does not request it."), NULL},
@@ -415,6 +439,7 @@ static struct {
   { 'P', ARG_ONE, "<integer>", gettext_noop("Maximum supported UDP packet size for EDNS.0 (defaults to %s)."), "*" },
   { 'q', ARG_DUP, NULL, gettext_noop("Log DNS queries."), NULL },
   { 'Q', ARG_ONE, "<integer>", gettext_noop("Force the originating port for upstream DNS queries."), NULL },
+  { LOPT_RANDPORT_LIM, ARG_ONE, "#ports", gettext_noop("Set maximum number of random originating ports for a query."), NULL },
   { 'R', OPT_NO_RESOLV, NULL, gettext_noop("Do NOT read resolv.conf."), NULL },
   { 'r', ARG_DUP, "<path>", gettext_noop("Specify path to resolv.conf (defaults to %s)."), RESOLVFILE }, 
   { LOPT_SERVERS_FILE, ARG_ONE, "<path>", gettext_noop("Specify path to file with server= options"), NULL },
@@ -428,6 +453,7 @@ static struct {
   { LOPT_MAXTTL, ARG_ONE, "<integer>", gettext_noop("Specify time-to-live in seconds for maximum TTL to send to clients."), NULL },
   { LOPT_MAXCTTL, ARG_ONE, "<integer>", gettext_noop("Specify time-to-live ceiling for cache."), NULL },
   { LOPT_MINCTTL, ARG_ONE, "<integer>", gettext_noop("Specify time-to-live floor for cache."), NULL },
+  { LOPT_FAST_RETRY, ARG_ONE, "<milliseconds>", gettext_noop("Retry DNS queries after this many milliseconds."), NULL},
   { 'u', ARG_ONE, "<username>", gettext_noop("Change to this user after startup. (defaults to %s)."), CHUSER }, 
   { 'U', ARG_DUP, "set:<tag>,<class>", gettext_noop("Map DHCP vendor class to tag."), NULL },
   { 'v', 0, NULL, gettext_noop("Display dnsmasq version and copyright information."), NULL },
@@ -455,6 +481,7 @@ static struct {
   { LOPT_SCRIPTUSR, ARG_ONE, "<username>", gettext_noop("Run lease-change scripts as this user."), NULL },
   { LOPT_SCRIPT_ARP, OPT_SCRIPT_ARP, NULL, gettext_noop("Call dhcp-script with changes to local ARP table."), NULL },
   { '7', ARG_DUP, "<path>", gettext_noop("Read configuration from all the files in this directory."), NULL },
+  { LOPT_CONF_SCRIPT, ARG_DUP, "<path>", gettext_noop("Execute file and read configuration from stdin."), NULL },
   { '8', ARG_ONE, "<facility>|<file>", gettext_noop("Log to this syslog facility or file. (defaults to DAEMON)"), NULL },
   { '9', OPT_LEASE_RO, NULL, gettext_noop("Do not use leasefile."), NULL },
   { '0', ARG_ONE, "<integer>", gettext_noop("Maximum number of concurrent DNS queries. (defaults to %s)"), "!" }, 
@@ -493,7 +520,9 @@ static struct {
   { LOPT_PXE_SERV, ARG_DUP, "<service>", gettext_noop("Boot service for PXE menu."), NULL },
   { LOPT_TEST, 0, NULL, gettext_noop("Check configuration syntax."), NULL },
   { LOPT_ADD_MAC, ARG_DUP, "[=base64|text]", gettext_noop("Add requestor's MAC address to forwarded DNS queries."), NULL },
+  { LOPT_STRIP_MAC, OPT_STRIP_MAC, NULL, gettext_noop("Strip MAC information from queries."), NULL },
   { LOPT_ADD_SBNET, ARG_ONE, "<v4 pref>[,<v6 pref>]", gettext_noop("Add specified IP subnet to forwarded DNS queries."), NULL },
+  { LOPT_STRIP_SBNET, OPT_STRIP_ECS, NULL, gettext_noop("Strip ECS information from queries."), NULL },
   { LOPT_CPE_ID, ARG_ONE, "<text>", gettext_noop("Add client identification to forwarded DNS queries."), NULL },
   { LOPT_DNSSEC, OPT_DNSSEC_PROXY, NULL, gettext_noop("Proxy DNSSEC validation results from upstream nameservers."), NULL },
   { LOPT_INCR_ADDR, OPT_CONSEC_ADDR, NULL, gettext_noop("Attempt to allocate sequential IP addresses to DHCP clients."), NULL },
@@ -514,6 +543,7 @@ static struct {
   { LOPT_AUTHSFS, ARG_DUP, "<NS>[,<NS>...]", gettext_noop("Secondary authoritative nameservers for forward domains"), NULL },
   { LOPT_AUTHPEER, ARG_DUP, "<ipaddr>[,<ipaddr>...]", gettext_noop("Peers which are allowed to do zone transfer"), NULL },
   { LOPT_IPSET, ARG_DUP, "/<domain>[/<domain>...]/<ipset>...", gettext_noop("Specify ipsets to which matching domains should be added"), NULL },
+  { LOPT_NFTSET, ARG_DUP, "/<domain>[/<domain>...]/<nftset>...", gettext_noop("Specify nftables sets to which matching domains should be added"), NULL },
   { LOPT_CMARK_ALST_EN, ARG_ONE, "[=<mask>]", gettext_noop("Enable filtering of DNS queries with connection-track marks."), NULL },
   { LOPT_CMARK_ALST, ARG_DUP, "<connmark>[/<mask>][,<pattern>[/<pattern>...]]", gettext_noop("Set allowed DNS patterns for a connection-track mark."), NULL },
   { LOPT_SYNTH, ARG_DUP, "<domain>,<range>,[<prefix>]", gettext_noop("Specify a domain and address range for synthesised names"), NULL },
@@ -539,6 +569,7 @@ static struct {
   { LOPT_SCRIPT_TIME, OPT_LEASE_RENEW, NULL, gettext_noop("Call dhcp-script when lease expiry changes."), NULL },
   { LOPT_UMBRELLA, ARG_ONE, "[=<optspec>]", gettext_noop("Send Cisco Umbrella identifiers including remote IP."), NULL },
   { LOPT_QUIET_TFTP, OPT_QUIET_TFTP, NULL, gettext_noop("Do not log routine TFTP."), NULL },
+  { LOPT_NORR, OPT_NORR, NULL, gettext_noop("Suppress round-robin ordering of DNS records."), NULL },
   { 0, 0, NULL, NULL, NULL }
 }; 
 
@@ -654,7 +685,7 @@ static char *canonicalise_opt(char *s)
     return 0;
 
   if (strlen(s) == 0)
-    return opt_string_alloc("");
+    return opt_malloc(1); /* Heap-allocated empty string */
 
   unhide_metas(s);
   if (!(ret = canonicalise(s, &nomem)) && nomem)
@@ -798,7 +829,7 @@ static void do_usage(void)
             
       if (usage[i].arg)
        {
-         strcpy(buff, usage[i].arg);
+         safe_strncpy(buff, usage[i].arg, sizeof(buff));
          for (j = 0; tab[j].handle; j++)
            if (tab[j].handle == *(usage[i].arg))
              sprintf(buff, "%d", tab[j].val);
@@ -824,163 +855,407 @@ static char *parse_mysockaddr(char *arg, union mysockaddr *addr)
   return NULL;
 }
 
-char *parse_server(char *arg, union mysockaddr *addr, union mysockaddr *source_addr, char *interface, u16 *flags)
+char *parse_server(char *arg, struct server_details *sdetails)
 {
-  int source_port = 0, serv_port = NAMESERVER_PORT;
-  char *portno, *source;
-  char *interface_opt = NULL;
-  int scope_index = 0;
-  char *scope_id;
-
-  *interface = 0;
+  sdetails->serv_port = NAMESERVER_PORT;
+  char *portno;
+  int ecode = 0;
+  struct addrinfo hints;
 
+  memset(&hints, 0, sizeof(struct addrinfo));
+  
+  *sdetails->interface = 0;
+  sdetails->addr_type = AF_UNSPEC;
+     
   if (strcmp(arg, "#") == 0)
     {
-      if (flags)
-       *flags |= SERV_USE_RESOLV;
+      if (sdetails->flags)
+       *sdetails->flags |= SERV_USE_RESOLV;
+      sdetails->addr_type = AF_LOCAL;
+      sdetails->valid = 1;
       return NULL;
     }
   
-  if ((source = split_chr(arg, '@')) && /* is there a source. */
-      (portno = split_chr(source, '#')) &&
-      !atoi_check16(portno, &source_port))
+  if ((sdetails->source = split_chr(arg, '@')) && /* is there a source. */
+      (portno = split_chr(sdetails->source, '#')) &&
+      !atoi_check16(portno, &sdetails->source_port))
     return _("bad port");
   
   if ((portno = split_chr(arg, '#')) && /* is there a port no. */
-      !atoi_check16(portno, &serv_port))
+      !atoi_check16(portno, &sdetails->serv_port))
     return _("bad port");
   
-  scope_id = split_chr(arg, '%');
+  sdetails->scope_id = split_chr(arg, '%');
   
-  if (source) {
-    interface_opt = split_chr(source, '@');
+  if (sdetails->source) {
+    sdetails->interface_opt = split_chr(sdetails->source, '@');
 
-    if (interface_opt)
+    if (sdetails->interface_opt)
       {
 #if defined(SO_BINDTODEVICE)
-       safe_strncpy(interface, source, IF_NAMESIZE);
-       source = interface_opt;
+       safe_strncpy(sdetails->interface, sdetails->source, IF_NAMESIZE);
+       sdetails->source = sdetails->interface_opt;
 #else
        return _("interface binding not supported");
 #endif
       }
   }
 
-  if (inet_pton(AF_INET, arg, &addr->in.sin_addr) > 0)
+  if (inet_pton(AF_INET, arg, &sdetails->addr->in.sin_addr) > 0)
+      sdetails->addr_type = AF_INET;
+  else if (inet_pton(AF_INET6, arg, &sdetails->addr->in6.sin6_addr) > 0)
+      sdetails->addr_type = AF_INET6;
+  else 
+    {
+      /* if the argument is neither an IPv4 not an IPv6 address, it might be a
+        hostname and we should try to resolve it to a suitable address. */
+      memset(&hints, 0, sizeof(hints));
+      /* The AI_ADDRCONFIG flag ensures that then IPv4 addresses are returned in
+         the result only if the local system has at least one IPv4 address
+         configured, and IPv6 addresses are returned only if the local system
+         has at least one IPv6 address configured. The loopback address is not
+         considered for this case as valid as a configured address. This flag is
+         useful on, for example, IPv4-only systems, to ensure that getaddrinfo()
+         does not return IPv6 socket addresses that would always fail in
+         subsequent connect() or bind() attempts. */
+      hints.ai_flags = AI_ADDRCONFIG;
+#if defined(HAVE_IDN) && defined(AI_IDN)
+      /* If the AI_IDN flag is specified and we have glibc 2.3.4 or newer, then
+         the node name given in node is converted to IDN format if necessary.
+         The source encoding is that of the current locale. */
+      hints.ai_flags |= AI_IDN;
+#endif
+      /* The value AF_UNSPEC indicates that getaddrinfo() should return socket
+         addresses for any address family (either IPv4 or IPv6, for example)
+         that can be used with node <arg> and service "domain". */
+      hints.ai_family = AF_UNSPEC;
+
+      /* Get addresses suitable for sending datagrams. We assume that we can use the
+        same addresses for TCP connections. Settting this to zero gets each address
+        threes times, for SOCK_STREAM, SOCK_RAW and SOCK_DGRAM, which is not useful. */
+      hints.ai_socktype = SOCK_DGRAM;
+
+      /* Get address associated with this hostname */
+      ecode = getaddrinfo(arg, NULL, &hints, &sdetails->hostinfo);
+      if (ecode == 0)
+       {
+         /* The getaddrinfo() function allocated and initialized a linked list of
+            addrinfo structures, one for each network address that matches node
+            and service, subject to the restrictions imposed by our <hints>
+            above, and returns a pointer to the start of the list in <hostinfo>.
+            The items in the linked list are linked by the <ai_next> field. */
+         sdetails->valid = 1;
+         sdetails->orig_hostinfo = sdetails->hostinfo;
+         return NULL;
+       }
+      else
+       {
+         /* Lookup failed, return human readable error string */
+         if (ecode == EAI_AGAIN)
+           return _("Cannot resolve server name");
+         else
+           return _((char*)gai_strerror(ecode));
+       }
+    }
+  
+  sdetails->valid = 1;
+  return NULL;
+}
+
+char *parse_server_addr(struct server_details *sdetails)
+{
+  if (sdetails->addr_type == AF_INET)
     {
-      addr->in.sin_port = htons(serv_port);    
-      addr->sa.sa_family = source_addr->sa.sa_family = AF_INET;
+      sdetails->addr->in.sin_port = htons(sdetails->serv_port);
+      sdetails->addr->sa.sa_family = sdetails->source_addr->sa.sa_family = AF_INET;
 #ifdef HAVE_SOCKADDR_SA_LEN
-      source_addr->in.sin_len = addr->in.sin_len = sizeof(struct sockaddr_in);
+      sdetails->source_addr->in.sin_len = sdetails->addr->in.sin_len = sizeof(struct sockaddr_in);
 #endif
-      source_addr->in.sin_addr.s_addr = INADDR_ANY;
-      source_addr->in.sin_port = htons(daemon->query_port);
+      sdetails->source_addr->in.sin_addr.s_addr = INADDR_ANY;
+      sdetails->source_addr->in.sin_port = htons(daemon->query_port);
       
-      if (source)
+      if (sdetails->source)
        {
-         if (flags)
-           *flags |= SERV_HAS_SOURCE;
-         source_addr->in.sin_port = htons(source_port);
-         if (!(inet_pton(AF_INET, source, &source_addr->in.sin_addr) > 0))
+         if (sdetails->flags)
+           *sdetails->flags |= SERV_HAS_SOURCE;
+         sdetails->source_addr->in.sin_port = htons(sdetails->source_port);
+         if (inet_pton(AF_INET, sdetails->source, &sdetails->source_addr->in.sin_addr) == 0)
            {
+             if (inet_pton(AF_INET6, sdetails->source, &sdetails->source_addr->in6.sin6_addr) == 1)
+               {
+                 sdetails->source_addr->sa.sa_family = AF_INET6;
+                 /* When resolving a server IP by hostname, we can simply skip mismatching
+                    server / source IP pairs. Otherwise, when an IP address is given directly,
+                    this is a fatal error. */
+                 if (!sdetails->orig_hostinfo)
+                   return _("cannot use IPv4 server address with IPv6 source address");
+               }
+             else
+               {
 #if defined(SO_BINDTODEVICE)
-             if (interface_opt)
-               return _("interface can only be specified once");
-             
-             source_addr->in.sin_addr.s_addr = INADDR_ANY;
-             safe_strncpy(interface, source, IF_NAMESIZE);
+                 if (sdetails->interface_opt)
+                   return _("interface can only be specified once");
+
+                 sdetails->source_addr->in.sin_addr.s_addr = INADDR_ANY;
+                 safe_strncpy(sdetails->interface, sdetails->source, IF_NAMESIZE);
 #else
-             return _("interface binding not supported");
+                 return _("interface binding not supported");
 #endif
+               }
            }
        }
     }
-  else if (inet_pton(AF_INET6, arg, &addr->in6.sin6_addr) > 0)
+  else if (sdetails->addr_type == AF_INET6)
     {
-      if (scope_id && (scope_index = if_nametoindex(scope_id)) == 0)
+      if (sdetails->scope_id && (sdetails->scope_index = if_nametoindex(sdetails->scope_id)) == 0)
        return _("bad interface name");
-      
-      addr->in6.sin6_port = htons(serv_port);
-      addr->in6.sin6_scope_id = scope_index;
-      source_addr->in6.sin6_addr = in6addr_any; 
-      source_addr->in6.sin6_port = htons(daemon->query_port);
-      source_addr->in6.sin6_scope_id = 0;
-      addr->sa.sa_family = source_addr->sa.sa_family = AF_INET6;
-      addr->in6.sin6_flowinfo = source_addr->in6.sin6_flowinfo = 0;
+
+      sdetails->addr->in6.sin6_port = htons(sdetails->serv_port);
+      sdetails->addr->in6.sin6_scope_id = sdetails->scope_index;
+      sdetails->source_addr->in6.sin6_addr = in6addr_any;
+      sdetails->source_addr->in6.sin6_port = htons(daemon->query_port);
+      sdetails->source_addr->in6.sin6_scope_id = 0;
+      sdetails->addr->sa.sa_family = sdetails->source_addr->sa.sa_family = AF_INET6;
+      sdetails->addr->in6.sin6_flowinfo = sdetails->source_addr->in6.sin6_flowinfo = 0;
 #ifdef HAVE_SOCKADDR_SA_LEN
-      addr->in6.sin6_len = source_addr->in6.sin6_len = sizeof(addr->in6);
+      sdetails->addr->in6.sin6_len = sdetails->source_addr->in6.sin6_len = sizeof(sdetails->addr->in6);
 #endif
-      if (source)
+      if (sdetails->source)
        {
-         if (flags)
-           *flags |= SERV_HAS_SOURCE;
-         source_addr->in6.sin6_port = htons(source_port);
-         if (inet_pton(AF_INET6, source, &source_addr->in6.sin6_addr) == 0)
+         if (sdetails->flags)
+           *sdetails->flags |= SERV_HAS_SOURCE;
+         sdetails->source_addr->in6.sin6_port = htons(sdetails->source_port);
+         if (inet_pton(AF_INET6, sdetails->source, &sdetails->source_addr->in6.sin6_addr) == 0)
            {
+             if (inet_pton(AF_INET, sdetails->source, &sdetails->source_addr->in.sin_addr) == 1)
+               {
+                 sdetails->source_addr->sa.sa_family = AF_INET;
+                 /* When resolving a server IP by hostname, we can simply skip mismatching
+                    server / source IP pairs. Otherwise, when an IP address is given directly,
+                    this is a fatal error. */
+                 if(!sdetails->orig_hostinfo)
+                   return _("cannot use IPv6 server address with IPv4 source address");
+               }
+             else
+               {
 #if defined(SO_BINDTODEVICE)
-             if (interface_opt)
-               return _("interface can only be specified once");
-             
-             source_addr->in6.sin6_addr = in6addr_any;
-             safe_strncpy(interface, source, IF_NAMESIZE);
+                 if (sdetails->interface_opt)
+                 return _("interface can only be specified once");
+
+                 sdetails->source_addr->in6.sin6_addr = in6addr_any;
+                 safe_strncpy(sdetails->interface, sdetails->source, IF_NAMESIZE);
 #else
-             return _("interface binding not supported");
+                 return _("interface binding not supported");
 #endif
+               }
            }
        }
     }
-  else
+  else if (sdetails->addr_type != AF_LOCAL)
     return _("bad address");
-
+  
   return NULL;
 }
 
-static int domain_rev4(char *domain, struct in_addr addr, int msize)
+int parse_server_next(struct server_details *sdetails)
+{
+  /* Looping over resolved addresses? */
+  if (sdetails->hostinfo)
+    {
+      /* Get address type */
+      sdetails->addr_type = sdetails->hostinfo->ai_family;
+
+      /* Get address */
+      if (sdetails->addr_type == AF_INET)
+       memcpy(&sdetails->addr->in.sin_addr,
+               &((struct sockaddr_in *) sdetails->hostinfo->ai_addr)->sin_addr,
+               sizeof(sdetails->addr->in.sin_addr));
+      else if (sdetails->addr_type == AF_INET6)
+       memcpy(&sdetails->addr->in6.sin6_addr,
+               &((struct sockaddr_in6 *) sdetails->hostinfo->ai_addr)->sin6_addr,
+               sizeof(sdetails->addr->in6.sin6_addr));
+
+      /* Iterate to the next available address */
+      sdetails->valid = sdetails->hostinfo->ai_next != NULL;
+      sdetails->hostinfo = sdetails->hostinfo->ai_next;
+      return 1;
+    }
+  else if (sdetails->valid)
+    {
+      /* When using an IP address, we return the address only once */
+      sdetails->valid = 0;
+      return 1;
+    }
+  /* Stop iterating here, we used all available addresses */
+  return 0;
+}
+
+static char *domain_rev4(int from_file, char *server, struct in_addr *addr4, int size)
 {
-  in_addr_t a = ntohl(addr.s_addr);
+  int i, j;
+  char *string;
+  int msize;
+  u16 flags = 0;
+  char domain[29]; /* strlen("xxx.yyy.zzz.ttt.in-addr.arpa")+1 */
+  union mysockaddr serv_addr, source_addr;
+  char interface[IF_NAMESIZE+1];
+  int count = 1, rem, addrbytes, addrbits;
+  struct server_details sdetails;
+
+  memset(&sdetails, 0, sizeof(struct server_details));
+  sdetails.addr = &serv_addr;
+  sdetails.source_addr = &source_addr;
+  sdetails.interface = interface;
+  sdetails.flags = &flags;
+    
+  if (!server)
+    flags = SERV_LITERAL_ADDRESS;
+  else if ((string = parse_server(server, &sdetails)))
+    return string;
+  
+  if (from_file)
+    flags |= SERV_FROM_FILE;
  
-  *domain = 0;
+  rem = size & 0x7;
+  addrbytes = (32 - size) >> 3;
+  addrbits = (32 - size) & 7;
+  
+  if (size > 32 || size < 1)
+    return _("bad IPv4 prefix length");
+  
+  /* Zero out last address bits according to CIDR mask */
+  ((u8 *)addr4)[3-addrbytes] &= ~((1 << addrbits)-1);
+  
+  size = size & ~0x7;
   
-  switch (msize)
+  if (rem != 0)
+    count = 1 << (8 - rem);
+  
+  for (i = 0; i < count; i++)
     {
-    case 32:
-      domain += sprintf(domain, "%u.", a & 0xff);
-      /* fall through */
-    case 24:
-      domain += sprintf(domain, "%d.", (a >> 8) & 0xff);
-      /* fall through */
-    case 16:
-      domain += sprintf(domain, "%d.", (a >> 16) & 0xff);
-      /* fall through */
-    case 8:
-      domain += sprintf(domain, "%d.", (a >> 24) & 0xff);
-      break;
-    default:
-      return 0;
+      *domain = 0;
+      string = domain;
+      msize = size/8;
+      
+      for (j = (rem == 0) ? msize-1 : msize; j >= 0; j--)
+       { 
+         int dig = ((unsigned char *)addr4)[j];
+         
+         if (j == msize)
+           dig += i;
+         
+         string += sprintf(string, "%d.", dig);
+       }
+      
+      sprintf(string, "in-addr.arpa");
+
+      if (flags & SERV_LITERAL_ADDRESS)
+       {
+         if (!add_update_server(flags, &serv_addr, &source_addr, interface, domain, NULL))
+           return  _("error");
+       }
+      else
+       {
+         while (parse_server_next(&sdetails))
+           {
+             if ((string = parse_server_addr(&sdetails)))
+               return string;
+             
+             if (!add_update_server(flags, &serv_addr, &source_addr, interface, domain, NULL))
+               return  _("error");
+           }
+
+         if (sdetails.orig_hostinfo)
+           freeaddrinfo(sdetails.orig_hostinfo);
+       }
     }
   
-  domain += sprintf(domain, "in-addr.arpa");
-  
-  return 1;
+  return NULL;
 }
 
-static int domain_rev6(char *domain, struct in6_addr *addr, int msize)
+static char *domain_rev6(int from_file, char *server, struct in6_addr *addr6, int size)
 {
-  int i;
+  int i, j;
+  char *string;
+  int msize;
+  u16 flags = 0;
+  char domain[73]; /* strlen("32*<n.>ip6.arpa")+1 */
+  union mysockaddr serv_addr, source_addr;
+  char interface[IF_NAMESIZE+1];
+  int count = 1, rem, addrbytes, addrbits;
+  struct server_details sdetails;
+  
+  memset(&sdetails, 0, sizeof(struct server_details));
+  sdetails.addr = &serv_addr;
+  sdetails.source_addr = &source_addr;
+  sdetails.interface = interface;
+  sdetails.flags = &flags;
+   
+  if (!server)
+    flags = SERV_LITERAL_ADDRESS;
+  else if ((string = parse_server(server, &sdetails)))
+    return string;
 
-  if (msize > 128 || msize%4)
-    return 0;
+  if (from_file)
+    flags |= SERV_FROM_FILE;
+  
+  rem = size & 0x3;
+  addrbytes = (128 - size) >> 3;
+  addrbits = (128 - size) & 7;
+  
+  if (size > 128 || size < 1)
+    return _("bad IPv6 prefix length");
+  
+  /* Zero out last address bits according to CIDR mask */
+  addr6->s6_addr[15-addrbytes] &= ~((1 << addrbits) - 1);
+  
+  size = size & ~0x3;
+  
+  if (rem != 0)
+    count = 1 << (4 - rem);
+      
+  for (i = 0; i < count; i++)
+    {
+      *domain = 0;
+      string = domain;
+      msize = size/4;
   
-  *domain = 0;
+      for (j = (rem == 0) ? msize-1 : msize; j >= 0; j--)
+       { 
+         int dig = ((unsigned char *)addr6)[j>>1];
+         
+         dig = j & 1 ? dig & 15 : dig >> 4;
+         
+         if (j == msize)
+           dig += i;
+         
+         string += sprintf(string, "%.1x.", dig);
+       }
+      
+      sprintf(string, "ip6.arpa");
 
-  for (i = msize-1; i >= 0; i -= 4)
-    { 
-      int dig = ((unsigned char *)addr)[i>>3];
-      domain += sprintf(domain, "%.1x.", (i>>2) & 1 ? dig & 15 : dig >> 4);
+      if (flags & SERV_LITERAL_ADDRESS)
+       {
+         if (!add_update_server(flags, &serv_addr, &source_addr, interface, domain, NULL))
+           return  _("error");
+       }
+      else
+       {
+         while (parse_server_next(&sdetails))
+           {
+             if ((string = parse_server_addr(&sdetails)))
+               return string;
+             
+             if (!add_update_server(flags, &serv_addr, &source_addr, interface, domain, NULL))
+               return  _("error");
+           }
+
+         if (sdetails.orig_hostinfo)
+           freeaddrinfo(sdetails.orig_hostinfo);
+       }
     }
-  domain += sprintf(domain, "ip6.arpa");
   
-  return 1;
+  return NULL;
 }
 
 #ifdef HAVE_DHCP
@@ -1723,6 +1998,17 @@ static int one_opt(int option, char *arg, char *errstr, char *gen_err, int comma
        break;
       }
 
+    case LOPT_CONF_SCRIPT: /* --conf-script */
+      {
+       char *file = opt_string_alloc(arg);
+       if (file)
+         {
+           one_file(file, LOPT_CONF_SCRIPT);
+           free(file);
+         }
+       break;
+      }
+
     case '7': /* --conf-dir */       
       {
        DIR *dir_stream;
@@ -1829,6 +2115,8 @@ static int one_opt(int option, char *arg, char *errstr, char *gen_err, int comma
                new->next = li;
                *up = new;
              }
+           else
+             free(path);
 
          }
 
@@ -1995,7 +2283,11 @@ static int one_opt(int option, char *arg, char *errstr, char *gen_err, int comma
        
        if (!(name = canonicalise_opt(arg)) || 
            (comma && !(target = canonicalise_opt(comma))))
-         ret_err(_("bad MX name"));
+         {
+           free(name);
+           free(target);
+           ret_err(_("bad MX name"));
+         }
        
        new = opt_malloc(sizeof(struct mx_srv_record));
        new->next = daemon->mxnames;
@@ -2045,15 +2337,11 @@ static int one_opt(int option, char *arg, char *errstr, char *gen_err, int comma
 
     case LOPT_DHCP_HOST:     /* --dhcp-hostsfile */
     case LOPT_DHCP_OPTS:     /* --dhcp-optsfile */
-    case LOPT_DHCP_INOTIFY:  /* --dhcp-hostsdir */
-    case LOPT_DHOPT_INOTIFY: /* --dhcp-optsdir */
-    case LOPT_HOST_INOTIFY:  /* --hostsdir */
     case 'H':                /* --addn-hosts */
       {
        struct hostsfile *new = opt_malloc(sizeof(struct hostsfile));
-       static unsigned int hosts_index = SRC_AH;
        new->fname = opt_string_alloc(arg);
-       new->index = hosts_index++;
+       new->index = daemon->host_index++;
        new->flags = 0;
        if (option == 'H')
          {
@@ -2069,21 +2357,29 @@ static int one_opt(int option, char *arg, char *errstr, char *gen_err, int comma
          {
            new->next = daemon->dhcp_opts_file;
            daemon->dhcp_opts_file = new;
-         }       
-       else 
-         {
-           new->next = daemon->dynamic_dirs;
-           daemon->dynamic_dirs = new; 
-           if (option == LOPT_DHCP_INOTIFY)
-             new->flags |= AH_DHCP_HST;
-           else if (option == LOPT_DHOPT_INOTIFY)
-             new->flags |= AH_DHCP_OPT;
-           else if (option == LOPT_HOST_INOTIFY)
-             new->flags |= AH_HOSTS;
          }
        
        break;
       }
+
+    case LOPT_DHCP_INOTIFY:  /* --dhcp-hostsdir */
+    case LOPT_DHOPT_INOTIFY: /* --dhcp-optsdir */
+    case LOPT_HOST_INOTIFY:  /* --hostsdir */
+      {
+       struct dyndir *new = opt_malloc(sizeof(struct dyndir));
+       new->dname = opt_string_alloc(arg);
+       new->flags = 0;
+       new->next = daemon->dynamic_dirs;
+       daemon->dynamic_dirs = new; 
+       if (option == LOPT_DHCP_INOTIFY)
+       new->flags |= AH_DHCP_HST;
+       else if (option == LOPT_DHOPT_INOTIFY)
+       new->flags |= AH_DHCP_OPT;
+       else if (option == LOPT_HOST_INOTIFY)
+       new->flags |= AH_HOSTS;
+
+       break;
+      }
       
     case LOPT_AUTHSERV: /* --auth-server */
       comma = split(arg);
@@ -2146,8 +2442,10 @@ static int one_opt(int option, char *arg, char *errstr, char *gen_err, int comma
        comma = split(arg);
                
        new = opt_malloc(sizeof(struct auth_zone));
-       new->domain = opt_string_alloc(arg);
-       new->subnet = NULL;
+       new->domain = canonicalise_opt(arg);
+       if (!new->domain)
+         ret_err_free(_("invalid auth-zone"), new);
+       new->subnet = NULL;
        new->exclude = NULL;
        new->interface_names = NULL;
        new->next = daemon->auth_zones;
@@ -2302,17 +2600,13 @@ static int one_opt(int option, char *arg, char *errstr, char *gen_err, int comma
                                      strlen(new->prefix) > MAXLABEL - INET_ADDRSTRLEN)
                                    ret_err_free(_("bad prefix"), new);
                                }
-                             else if (strcmp(arg, "local") != 0 ||
-                                      (msize != 8 && msize != 16 && msize != 24))
+                             else if (strcmp(arg, "local") != 0)
                                ret_err_free(gen_err, new);
                              else
                                {
-                                 char domain[29]; /* strlen("xxx.yyy.zzz.ttt.in-addr.arpa")+1 */
                                  /* local=/xxx.yyy.zzz.in-addr.arpa/ */
-                                 /* domain_rev4 can't fail here, msize checked above. */
-                                 domain_rev4(domain, new->start, msize);
-                                 add_update_server(SERV_LITERAL_ADDRESS, NULL, NULL, NULL, domain, NULL);
-                                 
+                                 domain_rev4(0, NULL, &new->start, msize);
+                                                                 
                                  /* local=/<domain>/ */
                                  /* d_raw can't failed to canonicalise here, checked above. */
                                  add_update_server(SERV_LITERAL_ADDRESS, NULL, NULL, NULL, d_raw, NULL);
@@ -2347,16 +2641,14 @@ static int one_opt(int option, char *arg, char *errstr, char *gen_err, int comma
                                      strlen(new->prefix) > MAXLABEL - INET6_ADDRSTRLEN)
                                    ret_err_free(_("bad prefix"), new);
                                }       
-                             else if (strcmp(arg, "local") != 0 || ((msize & 4) != 0))
+                             else if (strcmp(arg, "local") != 0)
                                ret_err_free(gen_err, new);
                              else 
                                {
-                                 char domain[73]; /* strlen("32*<n.>ip6.arpa")+1 */
                                  /* generate the equivalent of
                                     local=/xxx.yyy.zzz.ip6.arpa/ */
-                                 domain_rev6(domain, &new->start6, msize);
-                                 add_update_server(SERV_LITERAL_ADDRESS, NULL, NULL, NULL, domain, NULL);
-
+                                 domain_rev6(0, NULL, &new->start6, msize);
+                                 
                                  /* local=/<domain>/ */
                                  /* d_raw can't failed to canonicalise here, checked above. */
                                  add_update_server(SERV_LITERAL_ADDRESS, NULL, NULL, NULL, d_raw, NULL);
@@ -2388,9 +2680,15 @@ static int one_opt(int option, char *arg, char *errstr, char *gen_err, int comma
                          else if (!inet_pton(AF_INET6, arg, &new->end6))
                            ret_err_free(gen_err, new);
                        }
-                     else 
+                     else if (option == 's')
+                       {
+                         /* subnet from interface. */
+                         new->interface = opt_string_alloc(comma);
+                         new->al = NULL;
+                       }
+                     else
                        ret_err_free(gen_err, new);
-
+                     
                      if (option != 's' && prefstr)
                        {
                          if (!(new->prefix = canonicalise_opt(prefstr)) ||
@@ -2436,39 +2734,48 @@ static int one_opt(int option, char *arg, char *errstr, char *gen_err, int comma
 
     case LOPT_UMBRELLA: /* --umbrella */
       set_option_bool(OPT_UMBRELLA);
-      while (arg) {
-        comma = split(arg);
-        if (strstr(arg, "deviceid:")) {
-          arg += 9;
-          if (strlen(arg) != 16)
-              ret_err(gen_err);
-          for (char *p = arg; *p; p++) {
-            if (!isxdigit((int)*p))
-              ret_err(gen_err);
-          }
-          set_option_bool(OPT_UMBRELLA_DEVID);
-
-          u8 *u = daemon->umbrella_device;
-          char word[3];
-          for (u8 i = 0; i < sizeof(daemon->umbrella_device); i++, arg+=2) {
-            memcpy(word, &(arg[0]), 2);
-            *u++ = strtoul(word, NULL, 16);
-          }
-        }
-        else if (strstr(arg, "orgid:")) {
-          if (!strtoul_check(arg+6, &daemon->umbrella_org)) {
-            ret_err(gen_err);
-          }
-        }
-        else if (strstr(arg, "assetid:")) {
-          if (!strtoul_check(arg+8, &daemon->umbrella_asset)) {
-            ret_err(gen_err);
-          }
-        }
-        arg = comma;
-      }
+      while (arg)
+       {
+         comma = split(arg);
+         if (strstr(arg, "deviceid:"))
+           {
+             char *p;
+             u8 *u = daemon->umbrella_device;
+             char word[3];
+             
+             arg += 9;
+             if (strlen(arg) != 16)
+               ret_err(gen_err);
+             
+             for (p = arg; *p; p++)
+               if (!isxdigit((int)*p))
+                 ret_err(gen_err);
+             
+             set_option_bool(OPT_UMBRELLA_DEVID);
+             
+             for (i = 0; i < (int)sizeof(daemon->umbrella_device); i++, arg+=2)
+               {
+                 memcpy(word, &(arg[0]), 2);
+                 *u++ = strtoul(word, NULL, 16);
+               }
+           }
+         else if (strstr(arg, "orgid:"))
+           {
+             if (!strtoul_check(arg+6, &daemon->umbrella_org))
+               ret_err(gen_err);
+           }
+         else if (strstr(arg, "assetid:"))
+           {
+             if (!strtoul_check(arg+8, &daemon->umbrella_asset))
+               ret_err(gen_err);
+           }
+         else
+           ret_err(gen_err);
+         
+         arg = comma;
+       }
       break;
-
+      
     case LOPT_ADD_MAC: /* --add-mac */
       if (!arg)
        set_option_bool(OPT_ADD_MAC);
@@ -2635,7 +2942,7 @@ static int one_opt(int option, char *arg, char *errstr, char *gen_err, int comma
       
     case LOPT_NO_REBIND: /*  --rebind-domain-ok */
       {
-       struct server *new;
+       struct rebind_domain *new;
 
        unhide_metas(arg);
 
@@ -2644,9 +2951,8 @@ static int one_opt(int option, char *arg, char *errstr, char *gen_err, int comma
        
        do {
          comma = split_chr(arg, '/');
-         new = opt_malloc(sizeof(struct serv_local));
-         new->domain = opt_string_alloc(arg);
-         new->domain_len = strlen(arg);
+         new = opt_malloc(sizeof(struct  rebind_domain));
+         new->domain = canonicalise_opt(arg);
          new->next = daemon->no_rebind;
          daemon->no_rebind = new;
          arg = comma;
@@ -2659,13 +2965,20 @@ static int one_opt(int option, char *arg, char *errstr, char *gen_err, int comma
     case LOPT_LOCAL:     /*  --local */
     case 'A':            /*  --address */
       {
-       char *lastdomain = NULL, *domain = "";
+       char *lastdomain = NULL, *domain = "", *cur_domain;
        u16 flags = 0;
        char *err;
        union all_addr addr;
        union mysockaddr serv_addr, source_addr;
        char interface[IF_NAMESIZE+1];
+       struct server_details sdetails;
 
+       memset(&sdetails, 0, sizeof(struct server_details));
+       sdetails.addr = &serv_addr;
+       sdetails.source_addr = &source_addr;
+       sdetails.interface = interface;
+       sdetails.flags = &flags;
+                       
        unhide_metas(arg);
        
        /* split the domain args, if any and skip to the end of them. */
@@ -2698,29 +3011,55 @@ static int one_opt(int option, char *arg, char *errstr, char *gen_err, int comma
          }
        else
          {
-           if ((err = parse_server(arg, &serv_addr, &source_addr, interface, &flags)))
+           if ((err = parse_server(arg, &sdetails)))
              ret_err(err);
          }
 
        if (servers_only && option == 'S')
          flags |= SERV_FROM_FILE;
-       
-       while (1)
+
+       cur_domain = domain;
+       while ((flags & SERV_LITERAL_ADDRESS) || parse_server_next(&sdetails))
          {
-           /* server=//1.2.3.4 is special. */
-           if (strlen(domain) == 0 && lastdomain)
-             flags |= SERV_FOR_NODOTS;
-           else
-             flags &= ~SERV_FOR_NODOTS;
+           cur_domain = domain;
 
-           if (!add_update_server(flags, &serv_addr, &source_addr, interface, domain, &addr))
-             ret_err(gen_err);
-           
-           if (!lastdomain || domain == lastdomain)
+           if (!(flags & SERV_LITERAL_ADDRESS) && (err = parse_server_addr(&sdetails)))
+             ret_err(err);
+
+           /* When source is set only use DNS records of the same type and skip all others */
+           if (flags & SERV_HAS_SOURCE && sdetails.addr_type != sdetails.source_addr->sa.sa_family)
+             continue;
+
+           while (1)
+             {
+               /* server=//1.2.3.4 is special. */
+               if (lastdomain)
+                 {
+                   if (strlen(cur_domain) == 0)
+                     flags |= SERV_FOR_NODOTS;
+                   else
+                     flags &= ~SERV_FOR_NODOTS;
+                   
+                   /* address=/#/ matches the same as without domain */
+                   if (option == 'A' && cur_domain[0] == '#' && cur_domain[1] == 0)
+                     cur_domain[0] = 0;
+                 }
+               
+               if (!add_update_server(flags, sdetails.addr, sdetails.source_addr, sdetails.interface, cur_domain, &addr))
+                 ret_err(gen_err);
+               
+               if (!lastdomain || cur_domain == lastdomain)
+                 break;
+
+               cur_domain += strlen(cur_domain) + 1;
+             }
+
+           if (flags & SERV_LITERAL_ADDRESS)
              break;
-           
-           domain += strlen(domain) + 1;
          }
+
+       if (sdetails.orig_hostinfo)
+         freeaddrinfo(sdetails.orig_hostinfo);
        
        break;
       }
@@ -2729,57 +3068,62 @@ static int one_opt(int option, char *arg, char *errstr, char *gen_err, int comma
       {
        char *string;
        int size;
-       u16 flags = 0;
-       char domain[73]; /* strlen("32*<n.>ip6.arpa")+1 */
        struct in_addr addr4;
        struct in6_addr addr6;
-       union mysockaddr serv_addr, source_addr;
-       char interface[IF_NAMESIZE+1];
-       
+       
        unhide_metas(arg);
        if (!arg)
          ret_err(gen_err);
        
        comma=split(arg);
-
-       if (!(string = split_chr(arg, '/')) || !atoi_check(string, &size))
-         ret_err(gen_err);
        
+       if (!(string = split_chr(arg, '/')) || !atoi_check(string, &size))
+         size = -1;
+
        if (inet_pton(AF_INET, arg, &addr4))
          {
-           if (!domain_rev4(domain, addr4, size))
-             ret_err(_("bad IPv4 prefix"));
+          if (size == -1)
+            size = 32;
+
+          if ((string = domain_rev4(servers_only, comma, &addr4, size)))
+             ret_err(string);
          }
        else if (inet_pton(AF_INET6, arg, &addr6))
          {
-           if (!domain_rev6(domain, &addr6, size))
-             ret_err(_("bad IPv6 prefix"));
+            if (size == -1)
+              size = 128;
+
+            if ((string = domain_rev6(servers_only, comma, &addr6, size)))
+             ret_err(string);
          }
        else
          ret_err(gen_err);
        
-       if (!comma)
-         flags |= SERV_LITERAL_ADDRESS;
-       else if ((string = parse_server(comma, &serv_addr, &source_addr, interface, &flags)))
-         ret_err(string);
-       
-       if (servers_only)
-         flags |= SERV_FROM_FILE;
-
-       if (!add_update_server(flags, &serv_addr, &source_addr, interface, domain, NULL))
-         ret_err(gen_err);
-       
        break;
       }
 
     case LOPT_IPSET: /* --ipset */
+    case LOPT_NFTSET: /* --nftset */
 #ifndef HAVE_IPSET
-      ret_err(_("recompile with HAVE_IPSET defined to enable ipset directives"));
-      break;
-#else
+      if (option == LOPT_IPSET)
+        {
+          ret_err(_("recompile with HAVE_IPSET defined to enable ipset directives"));
+          break;
+        }
+#endif
+#ifndef HAVE_NFTSET
+      if (option == LOPT_NFTSET)
+        {
+          ret_err(_("recompile with HAVE_NFTSET defined to enable nftset directives"));
+          break;
+        }
+#endif
+
       {
         struct ipsets ipsets_head;
         struct ipsets *ipsets = &ipsets_head;
+         struct ipsets **daemon_sets =
+           (option == LOPT_IPSET) ? &daemon->ipsets : &daemon->nftsets;
         int size;
         char *end;
         char **sets, **sets_pos;
@@ -2824,19 +3168,24 @@ static int one_opt(int option, char *arg, char *errstr, char *gen_err, int comma
         sets = sets_pos = opt_malloc(sizeof(char *) * size);
         
         do {
+          char *p;
           end = split(arg);
-          *sets_pos++ = opt_string_alloc(arg);
+          *sets_pos = opt_string_alloc(arg);
+          /* Use '#' to delimit table and set */
+          if (option == LOPT_NFTSET)
+            while ((p = strchr(*sets_pos, '#')))
+              *p = ' ';
+          sets_pos++;
           arg = end;
         } while (end);
         *sets_pos = 0;
         for (ipsets = &ipsets_head; ipsets->next; ipsets = ipsets->next)
           ipsets->next->sets = sets;
-        ipsets->next = daemon->ipsets;
-        daemon->ipsets = ipsets_head.next;
+        ipsets->next = *daemon_sets;
+        *daemon_sets = ipsets_head.next;
         
         break;
       }
-#endif
       
     case LOPT_CMARK_ALST_EN: /* --connmark-allowlist-enable */
 #ifndef HAVE_CONNTRACK
@@ -3044,6 +3393,11 @@ static int one_opt(int option, char *arg, char *errstr, char *gen_err, int comma
       if (daemon->query_port == 0)
        daemon->osport = 1;
       break;
+
+    case LOPT_RANDPORT_LIM: /* --port-limit */
+      if (!atoi_check(arg, &daemon->randport_limit) || (daemon->randport_limit < 1))
+       ret_err(gen_err);
+      break;
       
     case 'T':         /* --local-ttl */
     case LOPT_NEGTTL: /* --neg-ttl */
@@ -3079,7 +3433,30 @@ static int one_opt(int option, char *arg, char *errstr, char *gen_err, int comma
          daemon->local_ttl = (unsigned long)ttl;
        break;
       }
+
+    case LOPT_FAST_RETRY:
+      daemon->fast_retry_timeout = TIMEOUT;
       
+      if (!arg)
+       daemon->fast_retry_time = DEFAULT_FAST_RETRY;
+      else
+       {
+         int retry;
+         
+         comma = split(arg);
+         if (!atoi_check(arg, &retry) || retry < 50)
+           ret_err(gen_err);
+         daemon->fast_retry_time = retry;
+         
+         if (comma)
+           {
+             if (!atoi_check(comma, &retry))
+               ret_err(gen_err);
+             daemon->fast_retry_timeout = retry/1000;
+           }
+       }
+      break;
+            
 #ifdef HAVE_DHCP
     case 'X': /* --dhcp-lease-max */
       if (!atoi_check(arg, &daemon->dhcp_max))
@@ -3616,6 +3993,7 @@ static int one_opt(int option, char *arg, char *errstr, char *gen_err, int comma
                      inet_ntop(AF_INET, &in, daemon->addrbuff, ADDRSTRLEN);
                      sprintf(errstr, _("duplicate dhcp-host IP address %s"),
                              daemon->addrbuff);
+                     dhcp_config_free(new);
                      return 0;
                    }         
              }
@@ -3779,16 +4157,16 @@ static int one_opt(int option, char *arg, char *errstr, char *gen_err, int comma
 
     case LOPT_NAME_MATCH: /* --dhcp-name-match */
       {
-       struct dhcp_match_name *new = opt_malloc(sizeof(struct dhcp_match_name));
-       struct dhcp_netid *id = opt_malloc(sizeof(struct dhcp_netid));
+       struct dhcp_match_name *new;
        ssize_t len;
        
        if (!(comma = split(arg)) || (len = strlen(comma)) == 0)
          ret_err(gen_err);
 
+       new = opt_malloc(sizeof(struct dhcp_match_name));
        new->wildcard = 0;
-       new->netid = id;
-       id->net = opt_string_alloc(set_prefix(arg));
+       new->netid = opt_malloc(sizeof(struct dhcp_netid));
+       new->netid->net = opt_string_alloc(set_prefix(arg));
 
        if (comma[len-1] == '*')
          {
@@ -3992,6 +4370,8 @@ static int one_opt(int option, char *arg, char *errstr, char *gen_err, int comma
               }
           }
         
+        dhcp_netid_free(new->netid);
+        free(new);
         ret_err(gen_err);
        }
         
@@ -4026,7 +4406,7 @@ static int one_opt(int option, char *arg, char *errstr, char *gen_err, int comma
     case LOPT_SUBSCR:   /* --dhcp-subscrid */
       {
         unsigned char *p;
-        int dig = 0;
+        int dig, colon;
         struct dhcp_vendor *new = opt_malloc(sizeof(struct dhcp_vendor));
         
         if (!(comma = split(arg)))
@@ -4050,13 +4430,16 @@ static int one_opt(int option, char *arg, char *errstr, char *gen_err, int comma
         else
           comma = arg;
         
-        for (p = (unsigned char *)comma; *p; p++)
+        for (dig = 0, colon = 0, p = (unsigned char *)comma; *p; p++)
           if (isxdigit(*p))
             dig = 1;
-          else if (*p != ':')
+          else if (*p == ':')
+            colon = 1;
+          else
             break;
+        
         unhide_metas(comma);
-        if (option == 'U' || option == 'j' || *p || !dig)
+        if (option == 'U' || option == 'j' || *p || !dig || !colon)
           {
             new->len = strlen(comma);  
             new->data = opt_malloc(new->len);
@@ -4179,26 +4562,66 @@ static int one_opt(int option, char *arg, char *errstr, char *gen_err, int comma
        }
       }
       break;
-
+      
     case LOPT_RELAY: /* --dhcp-relay */
       {
        struct dhcp_relay *new = opt_malloc(sizeof(struct dhcp_relay));
-       comma = split(arg);
-       new->interface = opt_string_alloc(split(comma));
+       char *two = split(arg);
+       char *three = split(two);
+       
        new->iface_index = 0;
-       if (comma && inet_pton(AF_INET, arg, &new->local) && inet_pton(AF_INET, comma, &new->server))
+
+       if (two)
          {
-           new->next = daemon->relay4;
-           daemon->relay4 = new;
-         }
+           if (inet_pton(AF_INET, arg, &new->local))
+             {
+               char *hash = split_chr(two, '#');
+
+               if (!hash || !atoi_check16(hash, &new->port))
+                 new->port = DHCP_SERVER_PORT;
+               
+               if (!inet_pton(AF_INET, two, &new->server))
+                 {
+                   new->server.addr4.s_addr = 0;
+                                   
+                   /* Fail for three arg version where there are not two addresses. 
+                      Also fail when broadcasting to wildcard address. */
+                   if (three || strchr(two, '*'))
+                     two = NULL;
+                   else
+                     three = two;
+                 }
+               
+               new->next = daemon->relay4;
+               daemon->relay4 = new;
+             }
 #ifdef HAVE_DHCP6
-       else if (comma && inet_pton(AF_INET6, arg, &new->local) && inet_pton(AF_INET6, comma, &new->server))
-         {
-           new->next = daemon->relay6;
-           daemon->relay6 = new;
-         }
+           else if (inet_pton(AF_INET6, arg, &new->local))
+             {
+               char *hash = split_chr(two, '#');
+
+               if (!hash || !atoi_check16(hash, &new->port))
+                 new->port = DHCPV6_SERVER_PORT;
+
+               if (!inet_pton(AF_INET6, two, &new->server))
+                 {
+                   inet_pton(AF_INET6, ALL_SERVERS, &new->server.addr6);
+                   /* Fail for three arg version where there are not two addresses.
+                      Also fail when multicasting to wildcard address. */
+                   if (three || strchr(two, '*'))
+                     two = NULL;
+                   else
+                     three = two;
+                 }
+               new->next = daemon->relay6;
+               daemon->relay6 = new;
+             }
 #endif
-       else
+
+           new->interface = opt_string_alloc(three);
+         }
+       
+       if (!two)
          {
            free(new->interface);
            ret_err_free(_("Bad dhcp-relay"), new);
@@ -4367,7 +4790,7 @@ err:
     case LOPT_CNAME: /* --cname */
       {
        struct cname *new;
-       char *alias, *target, *last, *pen;
+       char *alias, *target=NULL, *last, *pen;
        int ttl = -1;
 
        for (last = pen = NULL, comma = arg; comma; comma = split(comma))
@@ -4382,13 +4805,13 @@ err:
        if (pen != arg && atoi_check(last, &ttl))
          last = pen;
                
-       target = canonicalise_opt(last);
-
        while (arg != last)
          {
            int arglen = strlen(arg);
            alias = canonicalise_opt(arg);
 
+           if (!target)
+             target = canonicalise_opt(last);
            if (!alias || !target)
              {
                free(target);
@@ -4691,7 +5114,7 @@ err:
                struct name_list *nl;
                if (!canon)
                   {
-                   struct name_list *tmp = new->names, *next;
+                   struct name_list *tmp, *next;
                    for (tmp = new->names; tmp; tmp = next)
                      {
                        next = tmp->next;
@@ -4728,6 +5151,24 @@ err:
        break;
       }
 
+    case LOPT_STALE_CACHE:
+      {
+       int max_expiry = STALE_CACHE_EXPIRY;
+       if (arg)
+         {
+           /* Don't accept negative TTLs here, they'd have the counter-intuitive
+              side-effect of evicting cache records before they expire */
+           if (!atoi_check(arg, &max_expiry) || max_expiry < 0)
+             ret_err(gen_err);
+           /* Store "serve expired forever" as -1 internally, the option isn't
+              active for daemon->cache_max_expiry == 0 */
+           if (max_expiry == 0)
+             max_expiry = -1;
+         }
+       daemon->cache_max_expiry = max_expiry;
+       break;
+      }
+
 #ifdef HAVE_DNSSEC
     case LOPT_DNSSEC_STAMP: /* --dnssec-timestamp */
       daemon->timestamp_file = opt_string_alloc(arg); 
@@ -4809,7 +5250,7 @@ err:
   return 1;
 }
 
-static void read_file(char *file, FILE *f, int hard_opt      
+static void read_file(char *file, FILE *f, int hard_opt, int from_script)      
 {
   volatile int lineno = 0;
   char *buff = daemon->namebuff;
@@ -4817,10 +5258,12 @@ static void read_file(char *file, FILE *f, int hard_opt)
   while (fgets(buff, MAXDNAME, f))
     {
       int white, i;
-      volatile int option = (hard_opt == LOPT_REV_SERV) ? 0 : hard_opt;
+      volatile int option;
       char *errmess, *p, *arg, *start;
       size_t len;
 
+      option = (hard_opt == LOPT_REV_SERV) ? 0 : hard_opt;
+
       /* Memory allocation failure longjmps here if mem_recover == 1 */ 
       if (option != 0 || hard_opt == LOPT_REV_SERV)
        {
@@ -4828,7 +5271,7 @@ static void read_file(char *file, FILE *f, int hard_opt)
            continue;
          mem_recover = 1;
        }
-      
+
       arg = NULL;
       lineno++;
       errmess = NULL;
@@ -4934,7 +5377,11 @@ static void read_file(char *file, FILE *f, int hard_opt)
          
       if (errmess || !one_opt(option, arg, daemon->namebuff, _("error"), 0, hard_opt == LOPT_REV_SERV))
        {
-         sprintf(daemon->namebuff + strlen(daemon->namebuff), _(" at line %d of %s"), lineno, file);
+         if (from_script)
+           sprintf(daemon->namebuff + strlen(daemon->namebuff), _(" in output from %s"), file);
+         else
+           sprintf(daemon->namebuff + strlen(daemon->namebuff), _(" at line %d of %s"), lineno, file);
+         
          if (hard_opt != 0)
            my_syslog(LOG_ERR, "%s", daemon->namebuff);
          else
@@ -4943,7 +5390,6 @@ static void read_file(char *file, FILE *f, int hard_opt)
     }
 
   mem_recover = 0;
-  fclose(f);
 }
 
 #if defined(HAVE_DHCP) && defined(HAVE_INOTIFY)
@@ -4963,7 +5409,7 @@ int option_read_dynfile(char *file, int flags)
 static int one_file(char *file, int hard_opt)
 {
   FILE *f;
-  int nofile_ok = 0;
+  int nofile_ok = 0, do_popen = 0;
   static int read_stdin = 0;
   static struct fileread {
     dev_t dev;
@@ -4971,14 +5417,20 @@ static int one_file(char *file, int hard_opt)
     struct fileread *next;
   } *filesread = NULL;
   
-  if (hard_opt == '7')
+  if (hard_opt == LOPT_CONF_OPT)
     {
       /* default conf-file reading */
       hard_opt = 0;
       nofile_ok = 1;
     }
 
-  if (hard_opt == 0 && strcmp(file, "-") == 0)
+   if (hard_opt == LOPT_CONF_SCRIPT)
+     {
+       hard_opt = 0;
+       do_popen = 1;
+     }
+   
+   if (hard_opt == 0 && !do_popen && strcmp(file, "-") == 0)
     {
       if (read_stdin == 1)
        return 1;
@@ -5005,8 +5457,13 @@ static int one_file(char *file, int hard_opt)
          r->dev = statbuf.st_dev;
          r->ino = statbuf.st_ino;
        }
-      
-      if (!(f = fopen(file, "r")))
+
+      if (do_popen)
+       {
+         if (!(f = popen(file, "r")))
+           die(_("cannot execute %s: %s"), file, EC_FILE);
+       }
+      else if (!(f = fopen(file, "r")))
        {   
          if (errno == ENOENT && nofile_ok)
            return 1; /* No conffile, all done. */
@@ -5024,7 +5481,21 @@ static int one_file(char *file, int hard_opt)
        } 
     }
   
-  read_file(file, f, hard_opt);
+   read_file(file, f, hard_opt, do_popen);
+
+  if (do_popen)
+    {
+      int rc;
+
+      if ((rc = pclose(f)) == -1)
+       die(_("error executing %s: %s"), file, EC_MISC);
+
+      if (rc != 0)
+       die(_("%s returns non-zero error code"), file, rc+10);
+    }
+  else
+    fclose(f);
+       
   return 1;
 }
 
@@ -5164,7 +5635,8 @@ void read_servers_file(void)
     }
   
   mark_servers(SERV_FROM_FILE);
-  read_file(daemon->servers_file, f, LOPT_REV_SERV);
+  read_file(daemon->servers_file, f, LOPT_REV_SERV, 0);
+  fclose(f);
   cleanup_servers();
   check_servers(0);
 }
@@ -5282,6 +5754,8 @@ void read_opts(int argc, char **argv, char *compile_opts)
   daemon->soa_refresh = SOA_REFRESH;
   daemon->soa_retry = SOA_RETRY;
   daemon->soa_expiry = SOA_EXPIRY;
+  daemon->randport_limit = 1;
+  daemon->host_index = SRC_AH;
   
 #ifndef NO_ID
   add_txt("version.bind", "dnsmasq-" VERSION, 0 );
@@ -5297,7 +5771,10 @@ void read_opts(int argc, char **argv, char *compile_opts)
 #endif
   add_txt("servers.bind", NULL, TXT_STAT_SERVERS);
 #endif
-
+  
+  /* See comment above make_servers(). Optimises server-read code. */
+  mark_servers(0);
+  
   while (1) 
     {
 #ifdef HAVE_GETOPT_LONG
@@ -5390,7 +5867,7 @@ void read_opts(int argc, char **argv, char *compile_opts)
       free(conffile);
     }
   else
-    one_file(CONFFILE, '7');
+    one_file(CONFFILE, LOPT_CONF_OPT);
 
   /* port might not be known when the address is parsed - fill in here */
   if (daemon->servers)