Imported Upstream version 2.83 69/252369/1 upstream/2.83
authorSeonah Moon <seonah1.moon@samsung.com>
Wed, 27 Jan 2021 07:36:06 +0000 (16:36 +0900)
committerSeonah Moon <seonah1.moon@samsung.com>
Wed, 27 Jan 2021 07:36:14 +0000 (16:36 +0900)
Change-Id: I906d516832a250a16fa9ac3e877fb10f42278c6e

17 files changed:
CHANGELOG
Makefile
VERSION
bld/Android.mk
bld/pkg-wrapper
man/dnsmasq.8
src/config.h
src/crypto.c
src/dnsmasq.h
src/dnssec.c
src/edns0.c
src/forward.c
src/hash_questions.c [new file with mode: 0644]
src/network.c
src/option.c
src/rfc1035.c
src/rfc2131.c

index e6a2231..b70bf26 100644 (file)
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,3 +1,37 @@
+version 2.83
+       Use the values of --min-port and --max-port in outgoing
+       TCP connections to upstream DNS servers.
+
+       Fix a remote buffer overflow problem in the DNSSEC code. Any
+       dnsmasq with DNSSEC compiled in and enabled is vulnerable to this,
+       referenced by CVE-2020-25681, CVE-2020-25682, CVE-2020-25683
+       CVE-2020-25687.
+
+       Be sure to only accept UDP DNS query replies at the address
+       from which the query was originated. This keeps as much entropy
+       in the {query-ID, random-port} tuple as possible, to help defeat
+       cache poisoning attacks. Refer: CVE-2020-25684.
+
+       Use the SHA-256 hash function to verify that DNS answers
+       received are for the questions originally asked. This replaces
+       the slightly insecure SHA-1 (when compiled with DNSSEC) or
+       the very insecure CRC32 (otherwise). Refer: CVE-2020-25685.
+
+       Handle multiple identical near simultaneous DNS queries better.
+       Previously, such queries would all be forwarded
+       independently. This is, in theory, inefficent but in practise
+       not a problem, _except_ that is means that an answer for any
+       of the forwarded queries will be accepted and cached.
+       An attacker can send a query multiple times, and for each repeat,
+       another {port, ID} becomes capable of accepting the answer he is
+       sending in the blind, to random IDs and ports. The chance of a
+       succesful attack is therefore multiplied by the number of repeats
+       of the query. The new behaviour detects repeated queries and
+       merely stores the clients sending repeats so that when the
+       first query completes, the answer can be sent to all the
+       clients who asked. Refer: CVE-2020-25686.
+       
+
 version 2.82
        Improve behaviour in the face of network interfaces which come
        and go and change index. Thanks to Petr Mensik for the patch.
index 78e25f0..7d2afd1 100644 (file)
--- a/Makefile
+++ b/Makefile
@@ -53,7 +53,7 @@ top?=$(CURDIR)
 
 dbus_cflags =   `echo $(COPTS) | $(top)/bld/pkg-wrapper HAVE_DBUS $(PKG_CONFIG) --cflags dbus-1` 
 dbus_libs =     `echo $(COPTS) | $(top)/bld/pkg-wrapper HAVE_DBUS $(PKG_CONFIG) --libs dbus-1` 
-ubus_libs =     `echo $(COPTS) | $(top)/bld/pkg-wrapper HAVE_UBUS "" --copy -lubox -lubus`
+ubus_libs =     `echo $(COPTS) | $(top)/bld/pkg-wrapper HAVE_UBUS "" --copy '-lubox -lubus'`
 idn_cflags =    `echo $(COPTS) | $(top)/bld/pkg-wrapper HAVE_IDN $(PKG_CONFIG) --cflags libidn` 
 idn_libs =      `echo $(COPTS) | $(top)/bld/pkg-wrapper HAVE_IDN $(PKG_CONFIG) --libs libidn` 
 idn2_cflags =   `echo $(COPTS) | $(top)/bld/pkg-wrapper HAVE_LIBIDN2 $(PKG_CONFIG) --cflags libidn2`
@@ -62,8 +62,10 @@ ct_cflags =     `echo $(COPTS) | $(top)/bld/pkg-wrapper HAVE_CONNTRACK $(PKG_CON
 ct_libs =       `echo $(COPTS) | $(top)/bld/pkg-wrapper HAVE_CONNTRACK $(PKG_CONFIG) --libs libnetfilter_conntrack`
 lua_cflags =    `echo $(COPTS) | $(top)/bld/pkg-wrapper HAVE_LUASCRIPT $(PKG_CONFIG) --cflags lua5.2` 
 lua_libs =      `echo $(COPTS) | $(top)/bld/pkg-wrapper HAVE_LUASCRIPT $(PKG_CONFIG) --libs lua5.2` 
-nettle_cflags = `echo $(COPTS) | $(top)/bld/pkg-wrapper HAVE_DNSSEC $(PKG_CONFIG) --cflags nettle hogweed`
-nettle_libs =   `echo $(COPTS) | $(top)/bld/pkg-wrapper HAVE_DNSSEC $(PKG_CONFIG) --libs nettle hogweed`
+nettle_cflags = `echo $(COPTS) | $(top)/bld/pkg-wrapper HAVE_DNSSEC     $(PKG_CONFIG) --cflags 'nettle hogweed' \
+                                                        HAVE_NETTLEHASH $(PKG_CONFIG) --cflags nettle`
+nettle_libs =   `echo $(COPTS) | $(top)/bld/pkg-wrapper HAVE_DNSSEC     $(PKG_CONFIG) --libs 'nettle hogweed' \
+                                                        HAVE_NETTLEHASH $(PKG_CONFIG) --libs nettle`
 gmp_libs =      `echo $(COPTS) | $(top)/bld/pkg-wrapper HAVE_DNSSEC NO_GMP --copy -lgmp`
 sunos_libs =    `if uname | grep SunOS >/dev/null 2>&1; then echo -lsocket -lnsl -lposix4; fi`
 version =     -DVERSION='\"`$(top)/bld/get-version $(top)`\"'
@@ -77,7 +79,8 @@ objs = cache.o rfc1035.o util.o option.o forward.o network.o \
        helper.o tftp.o log.o conntrack.o dhcp6.o rfc3315.o \
        dhcp-common.o outpacket.o radv.o slaac.o auth.o ipset.o \
        domain.o dnssec.o blockdata.o tables.o loop.o inotify.o \
-       poll.o rrfilter.o edns0.o arp.o crypto.o dump.o ubus.o metrics.o
+       poll.o rrfilter.o edns0.o arp.o crypto.o dump.o ubus.o \
+       metrics.o hash_questions.o
 
 hdrs = dnsmasq.h config.h dhcp-protocol.h dhcp6-protocol.h \
        dns-protocol.h radv-protocol.h ip6addr.h metrics.h
diff --git a/VERSION b/VERSION
index bfe3ec9..243b1db 100644 (file)
--- a/VERSION
+++ b/VERSION
@@ -1 +1 @@
- (HEAD -> master, tag: v2.82, origin/master, origin/HEAD)
+ (HEAD -> master, tag: v2.83)
index 080a615..f924be9 100644 (file)
@@ -11,7 +11,7 @@ LOCAL_SRC_FILES :=  bpf.c cache.c dbus.c dhcp.c dnsmasq.c \
                    radv.c slaac.c auth.c ipset.c domain.c \
                    dnssec.c dnssec-openssl.c blockdata.c tables.c \
                    loop.c inotify.c poll.c rrfilter.c edns0.c arp.c \
-                   crypto.c dump.c ubus.c
+                   crypto.c dump.c ubus.c metrics.c hash_questions.c
 
 LOCAL_MODULE := dnsmasq
 
index 704bdd3..89713b4 100755 (executable)
@@ -1,35 +1,37 @@
 #!/bin/sh
 
-search=$1
-shift
-pkg=$1
-shift
-op=$1
-shift
-
 in=`cat`
 
-if grep "^\#[[:space:]]*define[[:space:]]*$search" config.h >/dev/null 2>&1 || \
-    echo $in | grep $search >/dev/null 2>&1; then
+search()
+{
+    grep "^\#[[:space:]]*define[[:space:]]*$1" config.h >/dev/null 2>&1 || \
+    echo $in | grep $1 >/dev/null 2>&1
+}
+
+while [ "$#" -gt 0 ]; do
+    search=$1
+    pkg=$2
+    op=$3
+    lib=$4
+    shift 4
+if search "$search"; then
+
 # Nasty, nasty, in --copy, arg 2 (if non-empty) is another config to search for, used with NO_GMP
     if [ $op = "--copy" ]; then
        if [ -z "$pkg" ]; then
-           pkg="$*"
-       elif grep "^\#[[:space:]]*define[[:space:]]*$pkg" config.h >/dev/null 2>&1 || \
-                echo $in | grep $pkg >/dev/null 2>&1; then
+           pkg="$lib"
+       elif search "$pkg"; then
            pkg=""
        else 
-           pkg="$*"
+           pkg="$lib"
        fi
-    elif grep "^\#[[:space:]]*define[[:space:]]*${search}_STATIC" config.h >/dev/null 2>&1 || \
-            echo $in | grep ${search}_STATIC >/dev/null 2>&1; then
-       pkg=`$pkg  --static $op $*`
+    elif search "${search}_STATIC"; then
+       pkg=`$pkg  --static $op $lib`
     else
-       pkg=`$pkg $op $*`
+       pkg=`$pkg $op $lib`
     fi
     
-    if grep "^\#[[:space:]]*define[[:space:]]*${search}_STATIC" config.h >/dev/null 2>&1 || \
-          echo $in | grep ${search}_STATIC >/dev/null 2>&1; then
+    if search "${search}_STATIC"; then
        if [ $op = "--libs" ] || [ $op = "--copy" ]; then
            echo "-Wl,-Bstatic $pkg -Wl,-Bdynamic"
        else
@@ -40,3 +42,4 @@ if grep "^\#[[:space:]]*define[[:space:]]*$search" config.h >/dev/null 2>&1 || \
     fi
 fi
 
+done
index 7b0e106..ac7c9fa 100644 (file)
@@ -692,8 +692,8 @@ still marks the request so that no upstream nameserver will add client
 address information either. The default is zero for both IPv4 and
 IPv6. Note that upstream nameservers may be configured to return
 different results based on this information, but the dnsmasq cache
-does not take account. If a dnsmasq instance is configured such that
-different results may be encountered, caching should be disabled.
+does not take account. Caching is therefore disabled for such replies,
+unless the subnet address being added is constant.
 
 For example,
 .B --add-subnet=24,96
@@ -1480,6 +1480,22 @@ to allow netbooting. This mode is enabled using the
 .B proxy
 keyword in
 .B --dhcp-range.
+.TP
+.B --dhcp-pxe-vendor=<vendor>[,...]
+According to UEFI and PXE specifications, DHCP packets between PXE clients and
+proxy PXE servers should have 
+.I PXEClient 
+in their vendor-class field. However, the firmware of computers from a few
+vendors is customized to carry a different identifier in that field. This option
+is used to consider such identifiers valid for identifying PXE clients. For 
+instance
+
+.B --dhcp-pxe-vendor=PXEClient,HW-Client
+
+will enable dnsmasq to also provide proxy PXE service to those PXE clients with
+.I HW-Client
+in as their identifier.
+>>>>>>> 907def3... pxe: support pxe clients with custom vendor-class
 .TP  
 .B \-X, --dhcp-lease-max=<number>
 Limits dnsmasq to the specified maximum number of DHCP leases. The
index 7187ffa..e71a117 100644 (file)
@@ -120,6 +120,9 @@ HAVE_AUTH
    define this to include the facility to act as an authoritative DNS
    server for one or more zones.
 
+HAVE_NETTLEHASH
+   include just hash function from nettle, but no DNSSEC.
+
 HAVE_DNSSEC
    include DNSSEC validator.
 
@@ -187,6 +190,7 @@ RESOLVFILE
 /* #define HAVE_IDN */
 /* #define HAVE_LIBIDN2 */
 /* #define HAVE_CONNTRACK */
+/* #define HAVE_NETTLEHASH */
 /* #define HAVE_DNSSEC */
 
 
@@ -420,6 +424,10 @@ static char *compile_opts =
 "no-"
 #endif
 "auth "
+#if !defined(HAVE_NETTLEHASH) && !defined(HAVE_DNSSEC)
+"no-"
+#endif
+"nettlehash "
 #ifndef HAVE_DNSSEC
 "no-"
 #endif
index ca63111..09525d2 100644 (file)
@@ -25,6 +25,9 @@
 #if NETTLE_VERSION_MAJOR == 3 && NETTLE_VERSION_MINOR >= 6
 #  include <nettle/gostdsa.h>
 #endif
+#endif
+
+#if defined(HAVE_DNSSEC) || defined(HAVE_NETTLEHASH)
 #include <nettle/nettle-meta.h>
 #include <nettle/bignum.h>
 
@@ -167,6 +170,10 @@ int hash_init(const struct nettle_hash *hash, void **ctxp, unsigned char **diges
 
   return 1;
 }
+
+#endif
+
+#ifdef HAVE_DNSSEC
   
 static int dnsmasq_rsa_verify(struct blockdata *key_data, unsigned int key_len, unsigned char *sig, size_t sig_len,
                              unsigned char *digest, size_t digest_len, int algo)
index 4220798..914f469 100644 (file)
@@ -157,7 +157,7 @@ extern int capget(cap_user_header_t header, cap_user_data_t data);
 #include <priv.h>
 #endif
 
-#ifdef HAVE_DNSSEC
+#if defined(HAVE_DNSSEC) || defined(HAVE_NETTLEHASH)
 #  include <nettle/nettle-meta.h>
 #endif
 
@@ -653,23 +653,25 @@ struct hostsfile {
 #define FREC_DO_QUESTION       64
 #define FREC_ADDED_PHEADER    128
 #define FREC_TEST_PKTSZ       256
-#define FREC_HAS_EXTRADATA    512        
+#define FREC_HAS_EXTRADATA    512
+#define FREC_HAS_PHEADER     1024
+#define FREC_NO_CACHE        2048
 
-#ifdef HAVE_DNSSEC
-#define HASH_SIZE 20 /* SHA-1 digest size */
-#else
-#define HASH_SIZE sizeof(int)
-#endif
+#define HASH_SIZE 32 /* SHA-256 digest size */
 
 struct frec {
-  union mysockaddr source;
-  union all_addr dest;
+  struct frec_src {
+    union mysockaddr source;
+    union all_addr dest;
+    unsigned int iface, log_id;
+    unsigned short orig_id;
+    struct frec_src *next;
+  } frec_src;
   struct server *sentto; /* NULL means free */
   struct randfd *rfd4;
   struct randfd *rfd6;
-  unsigned int iface;
-  unsigned short orig_id, new_id;
-  int log_id, fd, forwardall, flags;
+  unsigned short new_id;
+  int fd, forwardall, flags;
   time_t time;
   unsigned char *hash[HASH_SIZE];
 #ifdef HAVE_DNSSEC 
@@ -829,6 +831,7 @@ struct dhcp_opt {
 #define DHOPT_RFC3925         2048
 #define DHOPT_TAGOK           4096
 #define DHOPT_ADDR6           8192
+#define DHOPT_VENDOR_PXE     16384
 
 struct dhcp_boot {
   char *file, *sname, *tftp_sname;
@@ -852,6 +855,8 @@ struct pxe_service {
   struct pxe_service *next;
 };
 
+#define DHCP_PXE_DEF_VENDOR      "PXEClient"
+
 #define MATCH_VENDOR     1
 #define MATCH_USER       2
 #define MATCH_CIRCUIT    3
@@ -867,6 +872,11 @@ struct dhcp_vendor {
   struct dhcp_vendor *next;
 };
 
+struct dhcp_pxe_vendor {
+  char *data;
+  struct dhcp_pxe_vendor *next;
+};
+
 struct dhcp_mac {
   unsigned int mask;
   int hwaddr_len, hwaddr_type;
@@ -1040,6 +1050,7 @@ extern struct daemon {
   struct dhcp_config *dhcp_conf;
   struct dhcp_opt *dhcp_opts, *dhcp_match, *dhcp_opts6, *dhcp_match6;
   struct dhcp_match_name *dhcp_name_match;
+  struct dhcp_pxe_vendor *dhcp_pxe_vendors;
   struct dhcp_vendor *dhcp_vendors;
   struct dhcp_mac *dhcp_macs;
   struct dhcp_boot *boot_config;
@@ -1088,6 +1099,8 @@ extern struct daemon {
   int back_to_the_future;
 #endif
   struct frec *frec_list;
+  struct frec_src *free_frec_src;
+  int frec_src_count;
   struct serverfd *sfds;
   struct irec *interfaces;
   struct listener *listeners;
@@ -1220,7 +1233,6 @@ int check_for_bogus_wildcard(struct dns_header *header, size_t qlen, char *name,
                             struct bogus_addr *baddr, time_t now);
 int check_for_ignored_address(struct dns_header *header, size_t qlen, struct bogus_addr *baddr);
 int check_for_local_domain(char *name, time_t now);
-unsigned int questions_crc(struct dns_header *header, size_t plen, char *name);
 size_t resize_packet(struct dns_header *header, size_t plen, 
                  unsigned char *pheader, size_t hlen);
 int add_resource_record(struct dns_header *header, char *limit, int *truncp,
@@ -1245,9 +1257,11 @@ int dnssec_validate_reply(time_t now, struct dns_header *header, size_t plen, ch
                          int check_unsigned, int *neganswer, int *nons, int *nsec_ttl);
 int dnskey_keytag(int alg, int flags, unsigned char *key, int keylen);
 size_t filter_rrsigs(struct dns_header *header, size_t plen);
-unsigned char* hash_questions(struct dns_header *header, size_t plen, char *name);
 int setup_timestamp(void);
 
+/* hash_questions.c */
+unsigned char *hash_questions(struct dns_header *header, size_t plen, char *name);
+
 /* crypto.c */
 const struct nettle_hash *hash_find(char *name);
 int hash_init(const struct nettle_hash *hash, void **ctxp, unsigned char **digestp);
@@ -1645,7 +1659,7 @@ size_t add_pseudoheader(struct dns_header *header, size_t plen, unsigned char *l
                        unsigned short udp_sz, int optno, unsigned char *opt, size_t optlen, int set_do, int replace);
 size_t add_do_bit(struct dns_header *header, size_t plen, unsigned char *limit);
 size_t add_edns0_config(struct dns_header *header, size_t plen, unsigned char *limit, 
-                       union mysockaddr *source, time_t now, int *check_subnet);
+                       union mysockaddr *source, time_t now, int *check_subnet, int *cacheable);
 int check_source(struct dns_header *header, size_t plen, unsigned char *pseudoheader, union mysockaddr *peer);
 
 /* arp.c */
index db5c2d1..93cc7bf 100644 (file)
@@ -223,138 +223,147 @@ static int check_date_range(unsigned long curtime, u32 date_start, u32 date_end)
     && serial_compare_32(curtime, date_end) == SERIAL_LT;
 }
 
-/* Return bytes of canonicalised rdata, when the return value is zero, the remaining 
-   data, pointed to by *p, should be used raw. */
-static int get_rdata(struct dns_header *header, size_t plen, unsigned char *end, char *buff, int bufflen,
-                    unsigned char **p, u16 **desc)
+/* Return bytes of canonicalised rrdata one by one.
+   Init state->ip with the RR, and state->end with the end of same.
+   Init state->op to NULL.
+   Init state->desc to RR descriptor.
+   Init state->buff with a MAXDNAME * 2 buffer.
+   
+   After each call which returns 1, state->op points to the next byte of data.
+   On returning 0, the end has been reached.
+*/
+struct rdata_state {
+  u16 *desc;
+  size_t c;
+  unsigned char *end, *ip, *op;
+  char *buff;
+};
+
+static int get_rdata(struct dns_header *header, size_t plen, struct rdata_state *state)
 {
-  int d = **desc;
+  int d;
   
-  /* No more data needs mangling */
-  if (d == (u16)-1)
+  if (state->op && state->c != 1)
     {
-      /* If there's more data than we have space for, just return what fits,
-        we'll get called again for more chunks */
-      if (end - *p > bufflen)
-       {
-         memcpy(buff, *p, bufflen);
-         *p += bufflen;
-         return bufflen;
-       }
-      
-      return 0;
+      state->op++;
+      state->c--;
+      return 1;
     }
-  (*desc)++;
-  
-  if (d == 0 && extract_name(header, plen, p, buff, 1, 0))
-    /* domain-name, canonicalise */
-    return to_wire(buff);
-  else
-    { 
-      /* plain data preceding a domain-name, don't run off the end of the data */
-      if ((end - *p) < d)
-       d = end - *p;
+
+  while (1)
+    {
+      d = *(state->desc);
       
-      if (d != 0)
+      if (d == (u16)-1)
        {
-         memcpy(buff, *p, d);
-         *p += d;
+         /* all the bytes to the end. */
+         if ((state->c = state->end - state->ip) != 0)
+           {
+             state->op = state->ip;
+             state->ip = state->end;;
+           }
+         else
+           return 0;
+       }
+      else
+       {
+         state->desc++;
+         
+         if (d == (u16)0)
+           {
+             /* domain-name, canonicalise */
+             int len;
+             
+             if (!extract_name(header, plen, &state->ip, state->buff, 1, 0) ||
+                 (len = to_wire(state->buff)) == 0)
+               continue;
+             
+             state->c = len;
+             state->op = (unsigned char *)state->buff;
+           }
+         else
+           {
+             /* plain data preceding a domain-name, don't run off the end of the data */
+             if ((state->end - state->ip) < d)
+               d = state->end - state->ip;
+             
+             if (d == 0)
+               continue;
+                 
+             state->op = state->ip;
+             state->c = d;
+             state->ip += d;
+           }
        }
       
-      return d;
+      return 1;
     }
 }
 
-/* Bubble sort the RRset into the canonical order. 
-   Note that the byte-streams from two RRs may get unsynced: consider 
-   RRs which have two domain-names at the start and then other data.
-   The domain-names may have different lengths in each RR, but sort equal
-
-   ------------
-   |abcde|fghi|
-   ------------
-   |abcd|efghi|
-   ------------
-
-   leaving the following bytes as deciding the order. Hence the nasty left1 and left2 variables.
-*/
+/* Bubble sort the RRset into the canonical order. */
 
 static int sort_rrset(struct dns_header *header, size_t plen, u16 *rr_desc, int rrsetidx, 
                      unsigned char **rrset, char *buff1, char *buff2)
 {
-  int swap, quit, i, j;
+  int swap, i, j;
   
   do
     {
       for (swap = 0, i = 0; i < rrsetidx-1; i++)
        {
-         int rdlen1, rdlen2, left1, left2, len1, len2, len, rc;
-         u16 *dp1, *dp2;
-         unsigned char *end1, *end2;
+         int rdlen1, rdlen2;
+         struct rdata_state state1, state2;
+         
          /* Note that these have been determined to be OK previously,
             so we don't need to check for NULL return here. */
-         unsigned char *p1 = skip_name(rrset[i], header, plen, 10);
-         unsigned char *p2 = skip_name(rrset[i+1], header, plen, 10);
-         
-         p1 += 8; /* skip class, type, ttl */
-         GETSHORT(rdlen1, p1);
-         end1 = p1 + rdlen1;
+         state1.ip = skip_name(rrset[i], header, plen, 10);
+         state2.ip = skip_name(rrset[i+1], header, plen, 10);
+         state1.op = state2.op = NULL;
+         state1.buff = buff1;
+         state2.buff = buff2;
+         state1.desc = state2.desc = rr_desc;
          
-         p2 += 8; /* skip class, type, ttl */
-         GETSHORT(rdlen2, p2);
-         end2 = p2 + rdlen2; 
-         
-         dp1 = dp2 = rr_desc;
+         state1.ip += 8; /* skip class, type, ttl */
+         GETSHORT(rdlen1, state1.ip);
+         if (!CHECK_LEN(header, state1.ip, plen, rdlen1))
+           return rrsetidx; /* short packet */
+         state1.end = state1.ip + rdlen1;
          
-         for (quit = 0, left1 = 0, left2 = 0, len1 = 0, len2 = 0; !quit;)
+         state2.ip += 8; /* skip class, type, ttl */
+         GETSHORT(rdlen2, state2.ip);
+         if (!CHECK_LEN(header, state2.ip, plen, rdlen2))
+           return rrsetidx; /* short packet */
+         state2.end = state2.ip + rdlen2; 
+                 
+         while (1)
            {
-             if (left1 != 0)
-               memmove(buff1, buff1 + len1 - left1, left1);
+             int ok1, ok2;
              
-             if ((len1 = get_rdata(header, plen, end1, buff1 + left1, (MAXDNAME * 2) - left1, &p1, &dp1)) == 0)
-               {
-                 quit = 1;
-                 len1 = end1 - p1;
-                 memcpy(buff1 + left1, p1, len1);
-               }
-             len1 += left1;
-             
-             if (left2 != 0)
-               memmove(buff2, buff2 + len2 - left2, left2);
-             
-             if ((len2 = get_rdata(header, plen, end2, buff2 + left2, (MAXDNAME *2) - left2, &p2, &dp2)) == 0)
-               {
-                 quit = 1;
-                 len2 = end2 - p2;
-                 memcpy(buff2 + left2, p2, len2);
-               }
-             len2 += left2;
-              
-             if (len1 > len2)
-               left1 = len1 - len2, left2 = 0, len = len2;
-             else
-               left2 = len2 - len1, left1 = 0, len = len1;
-             
-             rc = (len == 0) ? 0 : memcmp(buff1, buff2, len);
-             
-             if (rc > 0 || (rc == 0 && quit && len1 > len2))
-               {
-                 unsigned char *tmp = rrset[i+1];
-                 rrset[i+1] = rrset[i];
-                 rrset[i] = tmp;
-                 swap = quit = 1;
-               }
-             else if (rc == 0 && quit && len1 == len2)
+             ok1 = get_rdata(header, plen, &state1);
+             ok2 = get_rdata(header, plen, &state2);
+
+             if (!ok1 && !ok2)
                {
                  /* Two RRs are equal, remove one copy. RFC 4034, para 6.3 */
                  for (j = i+1; j < rrsetidx-1; j++)
                    rrset[j] = rrset[j+1];
                  rrsetidx--;
                  i--;
+                 break;
                }
-             else if (rc < 0)
-               quit = 1;
+             else if (ok1 && (!ok2 || *state1.op > *state2.op)) 
+               {
+                 unsigned char *tmp = rrset[i+1];
+                 rrset[i+1] = rrset[i];
+                 rrset[i] = tmp;
+                 swap = 1;
+                 break;
+               }
+             else if (ok2 && (!ok1 || *state2.op > *state1.op))
+               break;
+             
+             /* arrive here when bytes are equal, go round the loop again
+                and compare the next ones. */
            }
        }
     } while (swap);
@@ -569,15 +578,18 @@ static int validate_rrset(time_t now, struct dns_header *header, size_t plen, in
       wire_len = to_wire(keyname);
       hash->update(ctx, (unsigned int)wire_len, (unsigned char*)keyname);
       from_wire(keyname);
+
+#define RRBUFLEN 128 /* Most RRs are smaller than this. */
       
       for (i = 0; i < rrsetidx; ++i)
        {
-         int seg;
-         unsigned char *end, *cp;
-         u16 len, *dp;
+         int j;
+         struct rdata_state state;
+         u16 len;
+         unsigned char rrbuf[RRBUFLEN];
          
          p = rrset[i];
-                 
+         
          if (!extract_name(header, plen, &p, name, 1, 10)) 
            return STAT_BOGUS;
 
@@ -586,12 +598,11 @@ static int validate_rrset(time_t now, struct dns_header *header, size_t plen, in
          /* if more labels than in RRsig name, hash *.<no labels in rrsig labels field>  4035 5.3.2 */
          if (labels < name_labels)
            {
-             int k;
-             for (k = name_labels - labels; k != 0; k--)
+             for (j = name_labels - labels; j != 0; j--)
                {
                  while (*name_start != '.' && *name_start != 0)
                    name_start++;
-                 if (k != 1 && *name_start == '.')
+                 if (j != 1 && *name_start == '.')
                    name_start++;
                }
              
@@ -606,30 +617,66 @@ static int validate_rrset(time_t now, struct dns_header *header, size_t plen, in
          hash->update(ctx, (unsigned int)wire_len, (unsigned char *)name_start);
          hash->update(ctx, 4, p); /* class and type */
          hash->update(ctx, 4, (unsigned char *)&nsigttl);
-         
-         p += 8; /* skip class, type, ttl */
+
+         p += 8; /* skip type, class, ttl */
          GETSHORT(rdlen, p);
          if (!CHECK_LEN(header, p, plen, rdlen))
            return STAT_BOGUS; 
-         
-         end = p + rdlen;
-         
-         /* canonicalise rdata and calculate length of same, use name buffer as workspace.
-            Note that name buffer is twice MAXDNAME long in DNSSEC mode. */
-         cp = p;
-         dp = rr_desc;
-         for (len = 0; (seg = get_rdata(header, plen, end, name, MAXDNAME * 2, &cp, &dp)) != 0; len += seg);
-         len += end - cp;
-         len = htons(len);
-         hash->update(ctx, 2, (unsigned char *)&len); 
-         
-         /* Now canonicalise again and digest. */
-         cp = p;
-         dp = rr_desc;
-         while ((seg = get_rdata(header, plen, end, name, MAXDNAME * 2, &cp, &dp)))
-           hash->update(ctx, seg, (unsigned char *)name);
-         if (cp != end)
-           hash->update(ctx, end - cp, cp);
+
+         /* Optimisation for RR types which need no cannonicalisation.
+            This includes DNSKEY DS NSEC and NSEC3, which are also long, so
+            it saves lots of calls to get_rdata, and avoids the pessimal
+            segmented insertion, even with a small rrbuf[].
+            
+            If canonicalisation is not needed, a simple insertion into the hash works.
+         */
+         if (*rr_desc == (u16)-1)
+           {
+             len = htons(rdlen);
+             hash->update(ctx, 2, (unsigned char *)&len);
+             hash->update(ctx, rdlen, p);
+           }
+         else
+           {
+             /* canonicalise rdata and calculate length of same, use 
+                name buffer as workspace for get_rdata. */
+             state.ip = p;
+             state.op = NULL;
+             state.desc = rr_desc;
+             state.buff = name;
+             state.end = p + rdlen;
+             
+             for (j = 0; get_rdata(header, plen, &state); j++)
+               if (j < RRBUFLEN)
+                 rrbuf[j] = *state.op;
+             
+             len = htons((u16)j);
+             hash->update(ctx, 2, (unsigned char *)&len); 
+             
+             /* If the RR is shorter than RRBUFLEN (most of them, in practice)
+                then we can just digest it now. If it exceeds RRBUFLEN we have to
+                go back to the start and do it in chunks. */
+             if (j >= RRBUFLEN)
+               {
+                 state.ip = p;
+                 state.op = NULL;
+                 state.desc = rr_desc;
+                 
+                 for (j = 0; get_rdata(header, plen, &state); j++)
+                   {
+                     rrbuf[j] = *state.op;
+                     
+                     if (j == RRBUFLEN - 1)
+                       {
+                         hash->update(ctx, RRBUFLEN, rrbuf);
+                         j = -1;
+                       }
+                   }
+               }
+             
+             if (j != 0)
+               hash->update(ctx, j, rrbuf);
+           }
        }
      
       hash->digest(ctx, hash->digest_size, digest);
@@ -2056,35 +2103,4 @@ size_t dnssec_generate_query(struct dns_header *header, unsigned char *end, char
   return ret;
 }
 
-unsigned char* hash_questions(struct dns_header *header, size_t plen, char *name)
-{
-  int q;
-  unsigned int len;
-  unsigned char *p = (unsigned char *)(header+1);
-  const struct nettle_hash *hash;
-  void *ctx;
-  unsigned char *digest;
-  
-  if (!(hash = hash_find("sha1")) || !hash_init(hash, &ctx, &digest))
-    return NULL;
-  
-  for (q = ntohs(header->qdcount); q != 0; q--) 
-    {
-      if (!extract_name(header, plen, &p, name, 1, 4))
-       break; /* bad packet */
-      
-      len = to_wire(name);
-      hash->update(ctx, len, (unsigned char *)name);
-      /* CRC the class and type as well */
-      hash->update(ctx, 4, p);
-
-      p += 4;
-      if (!CHECK_LEN(header, p, plen, 0))
-       break; /* bad packet */
-    }
-  
-  hash->digest(ctx, hash->digest_size, digest);
-  return digest;
-}
-
 #endif /* HAVE_DNSSEC */
index d75d3cc..53cfe24 100644 (file)
@@ -264,7 +264,8 @@ static void encoder(unsigned char *in, char *out)
   out[3] = char64(in[2]);
 }
 
-static size_t add_dns_client(struct dns_header *header, size_t plen, unsigned char *limit, union mysockaddr *l3, time_t now)
+static size_t add_dns_client(struct dns_header *header, size_t plen, unsigned char *limit,
+                            union mysockaddr *l3, time_t now, int *cacheablep)
 {
   int maclen, replace = 2; /* can't get mac address, just delete any incoming. */
   unsigned char mac[DHCP_CHADDR_MAX];
@@ -273,6 +274,7 @@ static size_t add_dns_client(struct dns_header *header, size_t plen, unsigned ch
   if ((maclen = find_mac(l3, mac, 1, now)) == 6)
     {
       replace = 1;
+      *cacheablep = 0;
 
       if (option_bool(OPT_MAC_HEX))
        print_mac(encode, mac, maclen);
@@ -288,14 +290,18 @@ static size_t add_dns_client(struct dns_header *header, size_t plen, unsigned ch
 }
 
 
-static size_t add_mac(struct dns_header *header, size_t plen, unsigned char *limit, union mysockaddr *l3, time_t now)
+static size_t add_mac(struct dns_header *header, size_t plen, unsigned char *limit,
+                     union mysockaddr *l3, time_t now, int *cacheablep)
 {
   int maclen;
   unsigned char mac[DHCP_CHADDR_MAX];
 
   if ((maclen = find_mac(l3, mac, 1, now)) != 0)
-    plen = add_pseudoheader(header, plen, limit, PACKETSZ, EDNS0_OPTION_MAC, mac, maclen, 0, 0); 
-    
+    {
+      *cacheablep = 0;
+      plen = add_pseudoheader(header, plen, limit, PACKETSZ, EDNS0_OPTION_MAC, mac, maclen, 0, 0); 
+    }
+  
   return plen; 
 }
 
@@ -313,17 +319,18 @@ static void *get_addrp(union mysockaddr *addr, const short family)
   return &addr->in.sin_addr;
 }
 
-static size_t calc_subnet_opt(struct subnet_opt *opt, union mysockaddr *source)
+static size_t calc_subnet_opt(struct subnet_opt *opt, union mysockaddr *source, int *cacheablep)
 {
   /* http://tools.ietf.org/html/draft-vandergaast-edns-client-subnet-02 */
   
   int len;
   void *addrp = NULL;
   int sa_family = source->sa.sa_family;
-
+  int cacheable = 0;
+  
   opt->source_netmask = 0;
   opt->scope_netmask = 0;
-
+    
   if (source->sa.sa_family == AF_INET6 && daemon->add_subnet6)
     {
       opt->source_netmask = daemon->add_subnet6->mask;
@@ -331,6 +338,7 @@ static size_t calc_subnet_opt(struct subnet_opt *opt, union mysockaddr *source)
        {
          sa_family = daemon->add_subnet6->addr.sa.sa_family;
          addrp = get_addrp(&daemon->add_subnet6->addr, sa_family);
+         cacheable = 1;
        } 
       else 
        addrp = &source->in6.sin6_addr;
@@ -343,6 +351,7 @@ static size_t calc_subnet_opt(struct subnet_opt *opt, union mysockaddr *source)
        {
          sa_family = daemon->add_subnet4->addr.sa.sa_family;
          addrp = get_addrp(&daemon->add_subnet4->addr, sa_family);
+         cacheable = 1; /* Address is constant */
        } 
        else 
          addrp = &source->in.sin_addr;
@@ -350,8 +359,6 @@ static size_t calc_subnet_opt(struct subnet_opt *opt, union mysockaddr *source)
   
   opt->family = htons(sa_family == AF_INET6 ? 2 : 1);
   
-  len = 0;
-  
   if (addrp && opt->source_netmask != 0)
     {
       len = ((opt->source_netmask - 1) >> 3) + 1;
@@ -359,18 +366,26 @@ static size_t calc_subnet_opt(struct subnet_opt *opt, union mysockaddr *source)
       if (opt->source_netmask & 7)
        opt->addr[len-1] &= 0xff << (8 - (opt->source_netmask & 7));
     }
+  else
+    {
+      cacheable = 1; /* No address ever supplied. */
+      len = 0;
+    }
+
+  if (cacheablep)
+    *cacheablep = cacheable;
   
   return len + 4;
 }
  
-static size_t add_source_addr(struct dns_header *header, size_t plen, unsigned char *limit, union mysockaddr *source)
+static size_t add_source_addr(struct dns_header *header, size_t plen, unsigned char *limit, union mysockaddr *source, int *cacheable)
 {
   /* http://tools.ietf.org/html/draft-vandergaast-edns-client-subnet-02 */
   
   int len;
   struct subnet_opt opt;
   
-  len = calc_subnet_opt(&opt, source);
+  len = calc_subnet_opt(&opt, source, cacheable);
   return add_pseudoheader(header, plen, (unsigned char *)limit, PACKETSZ, EDNS0_OPTION_CLIENT_SUBNET, (unsigned char *)&opt, len, 0, 0);
 }
 
@@ -383,18 +398,18 @@ int check_source(struct dns_header *header, size_t plen, unsigned char *pseudohe
   unsigned char *p;
   int code, i, rdlen;
   
-   calc_len = calc_subnet_opt(&opt, peer);
-   
-   if (!(p = skip_name(pseudoheader, header, plen, 10)))
-     return 1;
-   
-   p += 8; /* skip UDP length and RCODE */
+  calc_len = calc_subnet_opt(&opt, peer, NULL);
    
-   GETSHORT(rdlen, p);
-   if (!CHECK_LEN(header, p, plen, rdlen))
-     return 1; /* bad packet */
-   
-   /* check if option there */
+  if (!(p = skip_name(pseudoheader, header, plen, 10)))
+    return 1;
+  
+  p += 8; /* skip UDP length and RCODE */
+  
+  GETSHORT(rdlen, p);
+  if (!CHECK_LEN(header, p, plen, rdlen))
+    return 1; /* bad packet */
+  
+  /* check if option there */
    for (i = 0; i + 4 < rdlen; i += len + 4)
      {
        GETSHORT(code, p);
@@ -412,24 +427,28 @@ int check_source(struct dns_header *header, size_t plen, unsigned char *pseudohe
    return 1;
 }
 
+/* Set *check_subnet if we add a client subnet option, which needs to checked 
+   in the reply. Set *cacheable to zero if we add an option which the answer
+   may depend on. */
 size_t add_edns0_config(struct dns_header *header, size_t plen, unsigned char *limit, 
-                       union mysockaddr *source, time_t now, int *check_subnet)    
+                       union mysockaddr *source, time_t now, int *check_subnet, int *cacheable)    
 {
   *check_subnet = 0;
-
+  *cacheable = 1;
+  
   if (option_bool(OPT_ADD_MAC))
-    plen  = add_mac(header, plen, limit, source, now);
+    plen  = add_mac(header, plen, limit, source, now, cacheable);
   
   if (option_bool(OPT_MAC_B64) || option_bool(OPT_MAC_HEX))
-    plen = add_dns_client(header, plen, limit, source, now);
-
+    plen = add_dns_client(header, plen, limit, source, now, cacheable);
+  
   if (daemon->dns_client_id)
     plen = add_pseudoheader(header, plen, limit, PACKETSZ, EDNS0_OPTION_NOMCPEID, 
                            (unsigned char *)daemon->dns_client_id, strlen(daemon->dns_client_id), 0, 1);
   
   if (option_bool(OPT_CLIENT_SUBNET))
     {
-      plen = add_source_addr(header, plen, limit, source); 
+      plen = add_source_addr(header, plen, limit, source, cacheable); 
       *check_subnet = 1;
     }
          
index 9c2b2c6..7a95ddf 100644 (file)
 
 #include "dnsmasq.h"
 
-static struct frec *lookup_frec(unsigned short id, void *hash);
+static struct frec *lookup_frec(unsigned short id, int fd, int family, void *hash);
 static struct frec *lookup_frec_by_sender(unsigned short id,
                                          union mysockaddr *addr,
                                          void *hash);
+static struct frec *lookup_frec_by_query(void *hash, unsigned int flags);
+
 static unsigned short get_id(void);
 static void free_frec(struct frec *f);
 
@@ -255,20 +257,29 @@ static int forward_query(int udpfd, union mysockaddr *udpaddr,
   int type = SERV_DO_DNSSEC, norebind = 0;
   union all_addr *addrp = NULL;
   unsigned int flags = 0;
+  unsigned int fwd_flags = 0;
   struct server *start = NULL;
-#ifdef HAVE_DNSSEC
   void *hash = hash_questions(header, plen, daemon->namebuff);
+#ifdef HAVE_DNSSEC
   int do_dnssec = 0;
-#else
-  unsigned int crc = questions_crc(header, plen, daemon->namebuff);
-  void *hash = &crc;
 #endif
   unsigned int gotname = extract_request(header, plen, daemon->namebuff, NULL);
   unsigned char *oph = find_pseudoheader(header, plen, NULL, NULL, NULL, NULL);
   (void)do_bit;
-
+  
+  if (header->hb4 & HB4_CD)
+    fwd_flags |= FREC_CHECKING_DISABLED;
+  if (ad_reqd)
+    fwd_flags |= FREC_AD_QUESTION;
+  if (oph)
+    fwd_flags |= FREC_HAS_PHEADER;
+#ifdef HAVE_DNSSEC
+  if (do_bit)
+    fwd_flags |= FREC_DO_QUESTION;
+#endif
+  
   /* may be no servers available. */
-  if (forward || (hash && (forward = lookup_frec_by_sender(ntohs(header->id), udpaddr, hash))))
+  if (forward || (forward = lookup_frec_by_sender(ntohs(header->id), udpaddr, hash)))
     {
       /* If we didn't get an answer advertising a maximal packet in EDNS,
         fall back to 1280, which should work everywhere on IPv6.
@@ -339,6 +350,39 @@ static int forward_query(int udpfd, union mysockaddr *udpaddr,
     }
   else 
     {
+      /* Query from new source, but the same query may be in progress
+        from another source. If so, just add this client to the
+        list that will get the reply.*/
+        
+      if (!option_bool(OPT_ADD_MAC) && !option_bool(OPT_MAC_B64) &&
+         (forward = lookup_frec_by_query(hash, fwd_flags)))
+       {
+         /* Note whine_malloc() zeros memory. */
+         if (!daemon->free_frec_src &&
+             daemon->frec_src_count < daemon->ftabsize &&
+             (daemon->free_frec_src = whine_malloc(sizeof(struct frec_src))))
+           {
+             daemon->frec_src_count++;
+             daemon->free_frec_src->next = NULL;
+           }
+         
+         /* If we've been spammed with many duplicates, just drop the query. */
+         if (daemon->free_frec_src)
+           {
+             struct frec_src *new = daemon->free_frec_src;
+             daemon->free_frec_src = new->next;
+             new->next = forward->frec_src.next;
+             forward->frec_src.next = new;
+             new->orig_id = ntohs(header->id);
+             new->source = *udpaddr;
+             new->dest = *dst_addr;
+             new->log_id = daemon->log_id;
+             new->iface = dst_iface;
+           }
+         
+         return 1;
+       }
+       
       if (gotname)
        flags = search_servers(now, &addrp, gotname, daemon->namebuff, &type, &domain, &norebind);
       
@@ -346,22 +390,23 @@ static int forward_query(int udpfd, union mysockaddr *udpaddr,
       do_dnssec = type & SERV_DO_DNSSEC;
 #endif
       type &= ~SERV_DO_DNSSEC;      
-
+      
       if (daemon->servers && !flags)
        forward = get_new_frec(now, NULL, NULL);
       /* table full - flags == 0, return REFUSED */
       
       if (forward)
        {
-         forward->source = *udpaddr;
-         forward->dest = *dst_addr;
-         forward->iface = dst_iface;
-         forward->orig_id = ntohs(header->id);
+         forward->frec_src.source = *udpaddr;
+         forward->frec_src.orig_id = ntohs(header->id);
+         forward->frec_src.dest = *dst_addr;
+         forward->frec_src.iface = dst_iface;
+         forward->frec_src.next = NULL;
          forward->new_id = get_id();
          forward->fd = udpfd;
          memcpy(forward->hash, hash, HASH_SIZE);
          forward->forwardall = 0;
-         forward->flags = 0;
+         forward->flags = fwd_flags;
          if (norebind)
            forward->flags |= FREC_NOREBIND;
          if (header->hb4 & HB4_CD)
@@ -411,18 +456,21 @@ static int forward_query(int udpfd, union mysockaddr *udpaddr,
   if (!flags && forward)
     {
       struct server *firstsentto = start;
-      int subnet, forwarded = 0;
+      int subnet, cacheable, forwarded = 0;
       size_t edns0_len;
       unsigned char *pheader;
       
       /* If a query is retried, use the log_id for the retry when logging the answer. */
-      forward->log_id = daemon->log_id;
+      forward->frec_src.log_id = daemon->log_id;
       
-      plen = add_edns0_config(header, plen, ((unsigned char *)header) + PACKETSZ, &forward->source, now, &subnet);
+      plen = add_edns0_config(header, plen, ((unsigned char *)header) + PACKETSZ, &forward->frec_src.source, now, &subnet, &cacheable);
       
       if (subnet)
        forward->flags |= FREC_HAS_SUBNET;
-      
+
+      if (!cacheable)
+       forward->flags |= FREC_NO_CACHE;
+
 #ifdef HAVE_DNSSEC
       if (option_bool(OPT_DNSSEC_VALID) && do_dnssec)
        {
@@ -490,7 +538,7 @@ static int forward_query(int udpfd, union mysockaddr *udpaddr,
                  if (option_bool(OPT_CONNTRACK))
                    {
                      unsigned int mark;
-                     if (get_incoming_mark(&forward->source, &forward->dest, 0, &mark))
+                     if (get_incoming_mark(&forward->frec_src.source, &forward->frec_src.dest, 0, &mark))
                        setsockopt(fd, SOL_SOCKET, SO_MARK, &mark, sizeof(unsigned int));
                    }
 #endif
@@ -555,7 +603,7 @@ static int forward_query(int udpfd, union mysockaddr *udpaddr,
        return 1;
       
       /* could not send on, prepare to return */ 
-      header->id = htons(forward->orig_id);
+      header->id = htons(forward->frec_src.orig_id);
       free_frec(forward); /* cancel */
     }    
   
@@ -606,7 +654,7 @@ static size_t process_reply(struct dns_header *header, time_t now, struct server
        }
     }
 #endif
-  
+
   if ((pheader = find_pseudoheader(header, n, &plen, &sizep, &is_sign, NULL)))
     {
       /* Get extended RCODE. */
@@ -769,9 +817,6 @@ void reply_query(int fd, int family, time_t now)
   size_t nn;
   struct server *server;
   void *hash;
-#ifndef HAVE_DNSSEC
-  unsigned int crc;
-#endif
 
   /* packet buffer overwritten */
   daemon->srv_save = NULL;
@@ -798,14 +843,9 @@ void reply_query(int fd, int family, time_t now)
   if (difftime(now, server->pktsz_reduced) > UDP_TEST_TIME)
     server->edns_pktsz = daemon->edns_pktsz;
 
-#ifdef HAVE_DNSSEC
   hash = hash_questions(header, n, daemon->namebuff);
-#else
-  hash = &crc;
-  crc = questions_crc(header, n, daemon->namebuff);
-#endif
   
-  if (!(forward = lookup_frec(ntohs(header->id), hash)))
+  if (!(forward = lookup_frec(ntohs(header->id), fd, family, hash)))
     return;
   
 #ifdef HAVE_DUMPFILE
@@ -815,8 +855,8 @@ void reply_query(int fd, int family, time_t now)
 
   /* log_query gets called indirectly all over the place, so 
      pass these in global variables - sorry. */
-  daemon->log_display_id = forward->log_id;
-  daemon->log_source_addr = &forward->source;
+  daemon->log_display_id = forward->frec_src.log_id;
+  daemon->log_source_addr = &forward->frec_src.source;
   
   if (daemon->ignore_addr && RCODE(header) == NOERROR &&
       check_for_ignored_address(header, n, daemon->ignore_addr))
@@ -834,7 +874,6 @@ void reply_query(int fd, int family, time_t now)
       int is_sign;
 
 #ifdef HAVE_DNSSEC
-      /* For DNSSEC originated queries, just retry the query to the same server. */
       if (forward->flags & (FREC_DNSKEY_QUERY | FREC_DS_QUERY))
        {
          struct server *start;
@@ -860,6 +899,8 @@ void reply_query(int fd, int family, time_t now)
              }
            
          
+         fd = -1;
+
          if (start->sfd)
            fd = start->sfd->fd;
          else
@@ -867,19 +908,21 @@ void reply_query(int fd, int family, time_t now)
              if (start->addr.sa.sa_family == AF_INET6)
                {
                  /* may have changed family */
-                 if (!forward->rfd6)
-                   forward->rfd6 = allocate_rfd(AF_INET6);
-                 fd = forward->rfd6->fd;
+                 if (forward->rfd6 || (forward->rfd6 = allocate_rfd(AF_INET6)))
+                   fd = forward->rfd6->fd;
                }
              else
                {
                  /* may have changed family */
-                 if (!forward->rfd4)
-                   forward->rfd4 = allocate_rfd(AF_INET);
-                 fd = forward->rfd4->fd;
+                 if (forward->rfd4 || (forward->rfd4 = allocate_rfd(AF_INET)))
+                   fd = forward->rfd4->fd;
                }
            }
 
+         /* Can't get socket. */
+         if (fd == -1)
+           return;
+         
 #ifdef HAVE_DUMPFILE
          dump_packet(DUMP_SEC_QUERY, (void *)header, (size_t)plen, NULL, &start->addr);
 #endif
@@ -1088,6 +1131,7 @@ void reply_query(int fd, int family, time_t now)
                      new->sentto = server;
                      new->rfd4 = NULL;
                      new->rfd6 = NULL;
+                     new->frec_src.next = NULL;
                      new->flags &= ~(FREC_DNSKEY_QUERY | FREC_DS_QUERY | FREC_HAS_EXTRADATA);
                      new->forwardall = 0;
                      
@@ -1115,8 +1159,7 @@ void reply_query(int fd, int family, time_t now)
                        log_query(F_NOEXTRA | F_DNSSEC | F_IPV6, daemon->keyname, (union all_addr *)&(server->addr.in6.sin6_addr),
                                  querystr("dnssec-query", querytype));
   
-                     if ((hash = hash_questions(header, nn, daemon->namebuff)))
-                       memcpy(new->hash, hash, HASH_SIZE);
+                     memcpy(new->hash, hash_questions(header, nn, daemon->namebuff), HASH_SIZE);
                      new->new_id = get_id();
                      header->id = htons(new->new_id);
                      /* Save query for retransmission */
@@ -1150,7 +1193,7 @@ void reply_query(int fd, int family, time_t now)
                          if (option_bool(OPT_CONNTRACK))
                            {
                              unsigned int mark;
-                             if (get_incoming_mark(&orig->source, &orig->dest, 0, &mark))
+                             if (get_incoming_mark(&orig->frec_src.source, &orig->frec_src.dest, 0, &mark))
                                setsockopt(fd, SOL_SOCKET, SO_MARK, &mark, sizeof(unsigned int));
                            }
 #endif
@@ -1221,12 +1264,19 @@ void reply_query(int fd, int family, time_t now)
        header->hb4 |= HB4_CD;
       else
        header->hb4 &= ~HB4_CD;
+
+      /* Never cache answers which are contingent on the source or MAC address EDSN0 option,
+        since the cache is ignorant of such things. */
+      if (forward->flags & FREC_NO_CACHE)
+       no_cache_dnssec = 1;
       
       if ((nn = process_reply(header, now, forward->sentto, (size_t)n, check_rebind, no_cache_dnssec, cache_secure, bogusanswer, 
                              forward->flags & FREC_AD_QUESTION, forward->flags & FREC_DO_QUESTION, 
-                             forward->flags & FREC_ADDED_PHEADER, forward->flags & FREC_HAS_SUBNET, &forward->source)))
+                             forward->flags & FREC_ADDED_PHEADER, forward->flags & FREC_HAS_SUBNET, &forward->frec_src.source)))
        {
-         header->id = htons(forward->orig_id);
+         struct frec_src *src;
+
+         header->id = htons(forward->frec_src.orig_id);
          header->hb4 |= HB4_RA; /* recursion if available */
 #ifdef HAVE_DNSSEC
          /* We added an EDNSO header for the purpose of getting DNSSEC RRs, and set the value of the UDP payload size
@@ -1242,13 +1292,26 @@ void reply_query(int fd, int family, time_t now)
            }
 #endif
 
+         for (src = &forward->frec_src; src; src = src->next)
+           {
+             header->id = htons(src->orig_id);
+             
 #ifdef HAVE_DUMPFILE
-         dump_packet(DUMP_REPLY, daemon->packet, (size_t)nn, NULL, &forward->source);
+             dump_packet(DUMP_REPLY, daemon->packet, (size_t)nn, NULL, &src->source);
 #endif
-         
-         send_from(forward->fd, option_bool(OPT_NOWILD) || option_bool (OPT_CLEVERBIND), daemon->packet, nn, 
-                   &forward->source, &forward->dest, forward->iface);
+             
+             send_from(forward->fd, option_bool(OPT_NOWILD) || option_bool (OPT_CLEVERBIND), daemon->packet, nn, 
+                       &src->source, &src->dest, src->iface);
+
+             if (option_bool(OPT_EXTRALOG) && src != &forward->frec_src)
+               {
+                 daemon->log_display_id = src->log_id;
+                 daemon->log_source_addr = &src->source;
+                 log_query(F_UPSTREAM, "query", NULL, "duplicate");
+               }
+           }
        }
+
       free_frec(forward); /* cancel */
     }
 }
@@ -1767,7 +1830,7 @@ unsigned char *tcp_request(int confd, time_t now,
   int local_auth = 0;
 #endif
   int checking_disabled, do_bit, added_pheader = 0, have_pseudoheader = 0;
-  int check_subnet, no_cache_dnssec = 0, cache_secure = 0, bogusanswer = 0;
+  int check_subnet, cacheable, no_cache_dnssec = 0, cache_secure = 0, bogusanswer = 0;
   size_t m;
   unsigned short qtype;
   unsigned int gotname;
@@ -1938,7 +2001,7 @@ unsigned char *tcp_request(int confd, time_t now,
              char *domain = NULL;
              unsigned char *oph = find_pseudoheader(header, size, NULL, NULL, NULL, NULL);
 
-             size = add_edns0_config(header, size, ((unsigned char *) header) + 65536, &peer_addr, now, &check_subnet);
+             size = add_edns0_config(header, size, ((unsigned char *) header) + 65536, &peer_addr, now, &check_subnet, &cacheable);
 
              if (gotname)
                flags = search_servers(now, &addrp, gotname, daemon->namebuff, &type, &domain, &norebind);
@@ -1970,15 +2033,9 @@ unsigned char *tcp_request(int confd, time_t now,
              if (!flags && last_server)
                {
                  struct server *firstsendto = NULL;
-#ifdef HAVE_DNSSEC
-                 unsigned char *newhash, hash[HASH_SIZE];
-                 if ((newhash = hash_questions(header, (unsigned int)size, daemon->namebuff)))
-                   memcpy(hash, newhash, HASH_SIZE);
-                 else
-                   memset(hash, 0, HASH_SIZE);
-#else
-                 unsigned int crc = questions_crc(header, (unsigned int)size, daemon->namebuff);
-#endif           
+                 unsigned char hash[HASH_SIZE];
+                 memcpy(hash, hash_questions(header, (unsigned int)size, daemon->namebuff), HASH_SIZE);
+
                  /* Loop round available servers until we succeed in connecting to one.
                     Note that this code subtly ensures that consecutive queries on this connection
                     which can go to the same server, do so. */
@@ -2117,21 +2174,17 @@ unsigned char *tcp_request(int confd, time_t now,
                      /* If the crc of the question section doesn't match the crc we sent, then
                         someone might be attempting to insert bogus values into the cache by 
                         sending replies containing questions and bogus answers. */
-#ifdef HAVE_DNSSEC
-                     newhash = hash_questions(header, (unsigned int)m, daemon->namebuff);
-                     if (!newhash || memcmp(hash, newhash, HASH_SIZE) != 0)
+                     if (memcmp(hash, hash_questions(header, (unsigned int)m, daemon->namebuff), HASH_SIZE) != 0)
                        { 
                          m = 0;
                          break;
                        }
-#else                    
-                     if (crc != questions_crc(header, (unsigned int)m, daemon->namebuff))
-                       {
-                         m = 0;
-                         break;
-                       }
-#endif
 
+                     /* Never cache answers which are contingent on the source or MAC address EDSN0 option,
+                        since the cache is ignorant of such things. */
+                     if (!cacheable)
+                       no_cache_dnssec = 1;
+                     
                      m = process_reply(header, now, last_server, (unsigned int)m, 
                                        option_bool(OPT_NO_REBIND) && !norebind, no_cache_dnssec, cache_secure, bogusanswer,
                                        ad_reqd, do_bit, added_pheader, check_subnet, &peer_addr); 
@@ -2226,6 +2279,17 @@ void free_rfd(struct randfd *rfd)
 
 static void free_frec(struct frec *f)
 {
+  struct frec_src *last;
+  
+  /* add back to freelist if not the record builtin to every frec. */
+  for (last = f->frec_src.next; last && last->next; last = last->next) ;
+  if (last)
+    {
+      last->next = daemon->free_frec_src;
+      daemon->free_frec_src = f->frec_src.next;
+    }
+    
+  f->frec_src.next = NULL;    
   free_rfd(f->rfd4);
   f->rfd4 = NULL;
   f->sentto = NULL;
@@ -2338,15 +2402,25 @@ struct frec *get_new_frec(time_t now, int *wait, struct frec *force)
   return f; /* OK if malloc fails and this is NULL */
 }
 
-/* crc is all-ones if not known. */
-static struct frec *lookup_frec(unsigned short id, void *hash)
+static struct frec *lookup_frec(unsigned short id, int fd, int family, void *hash)
 {
   struct frec *f;
 
   for(f = daemon->frec_list; f; f = f->next)
     if (f->sentto && f->new_id == id && 
-       (!hash || memcmp(hash, f->hash, HASH_SIZE) == 0))
-      return f;
+       (memcmp(hash, f->hash, HASH_SIZE) == 0))
+      {
+       /* sent from random port */
+       if (family == AF_INET && f->rfd4 && f->rfd4->fd == fd)
+         return f;
+
+       if (family == AF_INET6 && f->rfd6 && f->rfd6->fd == fd)
+         return f;
+
+       /* sent to upstream from bound socket. */
+       if (f->sentto->sfd && f->sentto->sfd->fd == fd)
+         return f;
+      }
       
   return NULL;
 }
@@ -2356,17 +2430,42 @@ static struct frec *lookup_frec_by_sender(unsigned short id,
                                          void *hash)
 {
   struct frec *f;
+  struct frec_src *src;
+
+  for (f = daemon->frec_list; f; f = f->next)
+    if (f->sentto &&
+       !(f->flags & (FREC_DNSKEY_QUERY | FREC_DS_QUERY)) &&
+       memcmp(hash, f->hash, HASH_SIZE) == 0)
+      for (src = &f->frec_src; src; src = src->next)
+       if (src->orig_id == id && 
+           sockaddr_isequal(&src->source, addr))
+         return f;
+  
+  return NULL;
+}
+
+static struct frec *lookup_frec_by_query(void *hash, unsigned int flags)
+{
+  struct frec *f;
+
+  /* FREC_DNSKEY and FREC_DS_QUERY are never set in flags, so the test below 
+     ensures that no frec created for internal DNSSEC query can be returned here.
+     
+     Similarly FREC_NO_CACHE is never set in flags, so a query which is
+     contigent on a particular source address EDNS0 option will never be matched. */
+
+#define FLAGMASK (FREC_CHECKING_DISABLED | FREC_AD_QUESTION | FREC_DO_QUESTION \
+                 | FREC_HAS_PHEADER | FREC_DNSKEY_QUERY | FREC_DS_QUERY | FREC_NO_CACHE)
   
   for(f = daemon->frec_list; f; f = f->next)
     if (f->sentto &&
-       f->orig_id == id && 
-       memcmp(hash, f->hash, HASH_SIZE) == 0 &&
-       sockaddr_isequal(&f->source, addr))
+       (f->flags & FLAGMASK) == flags &&
+       memcmp(hash, f->hash, HASH_SIZE) == 0)
       return f;
-   
+  
   return NULL;
 }
+
 /* Send query packet again, if we can. */
 void resend_query()
 {
@@ -2407,12 +2506,20 @@ void server_gone(struct server *server)
 static unsigned short get_id(void)
 {
   unsigned short ret = 0;
+  struct frec *f;
   
-  do 
-    ret = rand16();
-  while (lookup_frec(ret, NULL));
-  
-  return ret;
+  while (1)
+    {
+      ret = rand16();
+
+      /* ensure id is unique. */
+      for (f = daemon->frec_list; f; f = f->next)
+       if (f->sentto && f->new_id == ret)
+         break;
+
+      if (!f)
+       return ret;
+    }
 }
 
 
diff --git a/src/hash_questions.c b/src/hash_questions.c
new file mode 100644 (file)
index 0000000..51d88c2
--- /dev/null
@@ -0,0 +1,281 @@
+/* Copyright (c) 2012-2020 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
+   the Free Software Foundation; version 2 dated June, 1991, or
+   (at your option) version 3 dated 29 June, 2007.
+
+   This program is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program.  If not, see <http://www.gnu.org/licenses/>.
+*/
+
+
+/* Hash the question section. This is used to safely detect query 
+   retransmission and to detect answers to questions we didn't ask, which 
+   might be poisoning attacks. Note that we decode the name rather 
+   than CRC the raw bytes, since replies might be compressed differently. 
+   We ignore case in the names for the same reason. 
+
+   The hash used is SHA-256. If we're building with DNSSEC support,
+   we use the Nettle cypto library. If not, we prefer not to
+   add a dependency on Nettle, and use a stand-alone implementaion. 
+*/
+
+#include "dnsmasq.h"
+
+#if defined(HAVE_DNSSEC) || defined(HAVE_NETTLEHASH)
+unsigned char *hash_questions(struct dns_header *header, size_t plen, char *name)
+{
+  int q;
+  unsigned char *p = (unsigned char *)(header+1);
+  const struct nettle_hash *hash;
+  void *ctx;
+  unsigned char *digest;
+  
+  if (!(hash = hash_find("sha256")) || !hash_init(hash, &ctx, &digest))
+    {
+      /* don't think this can ever happen. */
+      static unsigned char dummy[HASH_SIZE];
+      static int warned = 0;
+
+      if (!warned)
+       my_syslog(LOG_ERR, _("Failed to create SHA-256 hash object"));
+      warned = 1;
+     
+      return dummy;
+    }
+  
+  for (q = ntohs(header->qdcount); q != 0; q--) 
+    {
+      char *cp, c;
+
+      if (!extract_name(header, plen, &p, name, 1, 4))
+       break; /* bad packet */
+
+      for (cp = name; (c = *cp); cp++)
+        if (c >= 'A' && c <= 'Z')
+          *cp += 'a' - 'A';
+
+      hash->update(ctx, cp - name, (unsigned char *)name);
+      /* CRC the class and type as well */
+      hash->update(ctx, 4, p);
+
+      p += 4;
+      if (!CHECK_LEN(header, p, plen, 0))
+       break; /* bad packet */
+    }
+  
+  hash->digest(ctx, hash->digest_size, digest);
+  return digest;
+}
+
+#else /* HAVE_DNSSEC */
+
+#define SHA256_BLOCK_SIZE 32            // SHA256 outputs a 32 byte digest
+typedef unsigned char BYTE;             // 8-bit byte
+typedef unsigned int  WORD;             // 32-bit word, change to "long" for 16-bit machines
+
+typedef struct {
+  BYTE data[64];
+  WORD datalen;
+  unsigned long long bitlen;
+  WORD state[8];
+} SHA256_CTX;
+
+static void sha256_init(SHA256_CTX *ctx);
+static void sha256_update(SHA256_CTX *ctx, const BYTE data[], size_t len);
+static void sha256_final(SHA256_CTX *ctx, BYTE hash[]);
+
+
+unsigned char *hash_questions(struct dns_header *header, size_t plen, char *name)
+{
+  int q;
+  unsigned char *p = (unsigned char *)(header+1);
+  SHA256_CTX ctx;
+  static BYTE digest[SHA256_BLOCK_SIZE];
+  
+  sha256_init(&ctx);
+    
+  for (q = ntohs(header->qdcount); q != 0; q--) 
+    {
+      char *cp, c;
+
+      if (!extract_name(header, plen, &p, name, 1, 4))
+       break; /* bad packet */
+
+      for (cp = name; (c = *cp); cp++)
+        if (c >= 'A' && c <= 'Z')
+          *cp += 'a' - 'A';
+
+      sha256_update(&ctx, (BYTE *)name, cp - name);
+      /* CRC the class and type as well */
+      sha256_update(&ctx, (BYTE *)p, 4);
+
+      p += 4;
+      if (!CHECK_LEN(header, p, plen, 0))
+       break; /* bad packet */
+    }
+  
+  sha256_final(&ctx, digest);
+  return (unsigned char *)digest;
+}
+
+/* Code from here onwards comes from https://github.com/B-Con/crypto-algorithms
+   and was written by Brad Conte (brad@bradconte.com), to whom all credit is given.
+
+   This code is in the public domain, and the copyright notice at the head of this 
+   file does not apply to it.
+*/
+
+
+/****************************** MACROS ******************************/
+#define ROTLEFT(a,b) (((a) << (b)) | ((a) >> (32-(b))))
+#define ROTRIGHT(a,b) (((a) >> (b)) | ((a) << (32-(b))))
+
+#define CH(x,y,z) (((x) & (y)) ^ (~(x) & (z)))
+#define MAJ(x,y,z) (((x) & (y)) ^ ((x) & (z)) ^ ((y) & (z)))
+#define EP0(x) (ROTRIGHT(x,2) ^ ROTRIGHT(x,13) ^ ROTRIGHT(x,22))
+#define EP1(x) (ROTRIGHT(x,6) ^ ROTRIGHT(x,11) ^ ROTRIGHT(x,25))
+#define SIG0(x) (ROTRIGHT(x,7) ^ ROTRIGHT(x,18) ^ ((x) >> 3))
+#define SIG1(x) (ROTRIGHT(x,17) ^ ROTRIGHT(x,19) ^ ((x) >> 10))
+
+/**************************** VARIABLES *****************************/
+static const WORD k[64] = {
+                          0x428a2f98,0x71374491,0xb5c0fbcf,0xe9b5dba5,0x3956c25b,0x59f111f1,0x923f82a4,0xab1c5ed5,
+                          0xd807aa98,0x12835b01,0x243185be,0x550c7dc3,0x72be5d74,0x80deb1fe,0x9bdc06a7,0xc19bf174,
+                          0xe49b69c1,0xefbe4786,0x0fc19dc6,0x240ca1cc,0x2de92c6f,0x4a7484aa,0x5cb0a9dc,0x76f988da,
+                          0x983e5152,0xa831c66d,0xb00327c8,0xbf597fc7,0xc6e00bf3,0xd5a79147,0x06ca6351,0x14292967,
+                          0x27b70a85,0x2e1b2138,0x4d2c6dfc,0x53380d13,0x650a7354,0x766a0abb,0x81c2c92e,0x92722c85,
+                          0xa2bfe8a1,0xa81a664b,0xc24b8b70,0xc76c51a3,0xd192e819,0xd6990624,0xf40e3585,0x106aa070,
+                          0x19a4c116,0x1e376c08,0x2748774c,0x34b0bcb5,0x391c0cb3,0x4ed8aa4a,0x5b9cca4f,0x682e6ff3,
+                          0x748f82ee,0x78a5636f,0x84c87814,0x8cc70208,0x90befffa,0xa4506ceb,0xbef9a3f7,0xc67178f2
+};
+
+/*********************** FUNCTION DEFINITIONS ***********************/
+static void sha256_transform(SHA256_CTX *ctx, const BYTE data[])
+{
+  WORD a, b, c, d, e, f, g, h, i, j, t1, t2, m[64];
+  
+  for (i = 0, j = 0; i < 16; ++i, j += 4)
+    m[i] = (data[j] << 24) | (data[j + 1] << 16) | (data[j + 2] << 8) | (data[j + 3]);
+  for ( ; i < 64; ++i)
+    m[i] = SIG1(m[i - 2]) + m[i - 7] + SIG0(m[i - 15]) + m[i - 16];
+
+  a = ctx->state[0];
+  b = ctx->state[1];
+  c = ctx->state[2];
+  d = ctx->state[3];
+  e = ctx->state[4];
+  f = ctx->state[5];
+  g = ctx->state[6];
+  h = ctx->state[7];
+
+  for (i = 0; i < 64; ++i)
+    {
+      t1 = h + EP1(e) + CH(e,f,g) + k[i] + m[i];
+      t2 = EP0(a) + MAJ(a,b,c);
+      h = g;
+      g = f;
+      f = e;
+      e = d + t1;
+      d = c;
+      c = b;
+      b = a;
+      a = t1 + t2;
+    }
+  
+  ctx->state[0] += a;
+  ctx->state[1] += b;
+  ctx->state[2] += c;
+  ctx->state[3] += d;
+  ctx->state[4] += e;
+  ctx->state[5] += f;
+  ctx->state[6] += g;
+  ctx->state[7] += h;
+}
+
+static void sha256_init(SHA256_CTX *ctx)
+{
+  ctx->datalen = 0;
+  ctx->bitlen = 0;
+  ctx->state[0] = 0x6a09e667;
+  ctx->state[1] = 0xbb67ae85;
+  ctx->state[2] = 0x3c6ef372;
+  ctx->state[3] = 0xa54ff53a;
+  ctx->state[4] = 0x510e527f;
+  ctx->state[5] = 0x9b05688c;
+  ctx->state[6] = 0x1f83d9ab;
+  ctx->state[7] = 0x5be0cd19;
+}
+
+static void sha256_update(SHA256_CTX *ctx, const BYTE data[], size_t len)
+{
+  WORD i;
+  
+  for (i = 0; i < len; ++i)
+    {
+      ctx->data[ctx->datalen] = data[i];
+      ctx->datalen++;
+      if (ctx->datalen == 64) {
+       sha256_transform(ctx, ctx->data);
+       ctx->bitlen += 512;
+       ctx->datalen = 0;
+      }
+    }
+}
+
+static void sha256_final(SHA256_CTX *ctx, BYTE hash[])
+{
+  WORD i;
+  
+  i = ctx->datalen;
+
+  // Pad whatever data is left in the buffer.
+  if (ctx->datalen < 56)
+    {
+      ctx->data[i++] = 0x80;
+      while (i < 56)
+       ctx->data[i++] = 0x00;
+    }
+  else
+    {
+      ctx->data[i++] = 0x80;
+      while (i < 64)
+       ctx->data[i++] = 0x00;
+      sha256_transform(ctx, ctx->data);
+      memset(ctx->data, 0, 56);
+    }
+  
+  // Append to the padding the total message's length in bits and transform.
+  ctx->bitlen += ctx->datalen * 8;
+  ctx->data[63] = ctx->bitlen;
+  ctx->data[62] = ctx->bitlen >> 8;
+  ctx->data[61] = ctx->bitlen >> 16;
+  ctx->data[60] = ctx->bitlen >> 24;
+  ctx->data[59] = ctx->bitlen >> 32;
+  ctx->data[58] = ctx->bitlen >> 40;
+  ctx->data[57] = ctx->bitlen >> 48;
+  ctx->data[56] = ctx->bitlen >> 56;
+  sha256_transform(ctx, ctx->data);
+  
+  // Since this implementation uses little endian byte ordering and SHA uses big endian,
+  // reverse all the bytes when copying the final state to the output hash.
+  for (i = 0; i < 4; ++i)
+    {
+      hash[i]      = (ctx->state[0] >> (24 - i * 8)) & 0x000000ff;
+      hash[i + 4]  = (ctx->state[1] >> (24 - i * 8)) & 0x000000ff;
+      hash[i + 8]  = (ctx->state[2] >> (24 - i * 8)) & 0x000000ff;
+      hash[i + 12] = (ctx->state[3] >> (24 - i * 8)) & 0x000000ff;
+      hash[i + 16] = (ctx->state[4] >> (24 - i * 8)) & 0x000000ff;
+      hash[i + 20] = (ctx->state[5] >> (24 - i * 8)) & 0x000000ff;
+      hash[i + 24] = (ctx->state[6] >> (24 - i * 8)) & 0x000000ff;
+      hash[i + 28] = (ctx->state[7] >> (24 - i * 8)) & 0x000000ff;
+    }
+}
+
+#endif
index c7d002b..7cf2546 100644 (file)
@@ -1262,17 +1262,46 @@ int random_sock(int family)
 int local_bind(int fd, union mysockaddr *addr, char *intname, unsigned int ifindex, int is_tcp)
 {
   union mysockaddr addr_copy = *addr;
+  unsigned short port;
+  int tries = 1, done = 0;
+  unsigned int ports_avail = ((unsigned short)daemon->max_port - (unsigned short)daemon->min_port) + 1;
+  if (addr_copy.sa.sa_family == AF_INET)
+    port = addr_copy.in.sin_port;
+  else
+    port = addr_copy.in6.sin6_port;
 
   /* cannot set source _port_ for TCP connections. */
   if (is_tcp)
+    port = 0;
+
+  /* Bind a random port within the range given by min-port and max-port */
+  if (port == 0)
+    {
+      tries = ports_avail < 30 ? 3 * ports_avail : 100;
+      port = htons(daemon->min_port + (rand16() % ((unsigned short)ports_avail)));
+    }
+  
+  while (tries--)
     {
       if (addr_copy.sa.sa_family == AF_INET)
-       addr_copy.in.sin_port = 0;
+       addr_copy.in.sin_port = port;
       else
-       addr_copy.in6.sin6_port = 0;
+       addr_copy.in6.sin6_port = port;
+
+      if (bind(fd, (struct sockaddr *)&addr_copy, sa_len(&addr_copy)) != -1)
+       {
+         done = 1;
+         break;
+       }
+      
+      if (errno != EADDRINUSE && errno != EACCES)
+       return 0;
+      
+      port = htons(daemon->min_port + (rand16() % ((unsigned short)ports_avail)));
     }
-  
-  if (bind(fd, (struct sockaddr *)&addr_copy, sa_len(&addr_copy)) == -1)
+
+  if (!done)
     return 0;
 
   if (!is_tcp && ifindex > 0)
index dbe5f90..316d9c8 100644 (file)
@@ -167,6 +167,7 @@ struct myoption {
 #define LOPT_IGNORE_CLID   358
 #define LOPT_SINGLE_PORT   359
 #define LOPT_SCRIPT_TIME   360
+#define LOPT_PXE_VENDOR    361
  
 #ifdef HAVE_GETOPT_LONG
 static const struct option opts[] =  
@@ -270,6 +271,7 @@ static const struct myoption opts[] =
     { "dhcp-circuitid", 1, 0, LOPT_CIRCUIT },
     { "dhcp-remoteid", 1, 0, LOPT_REMOTE },
     { "dhcp-subscrid", 1, 0, LOPT_SUBSCR },
+    { "dhcp-pxe-vendor", 1, 0, LOPT_PXE_VENDOR },
     { "interface-name", 1, 0, LOPT_INTNAME },
     { "dhcp-hostsfile", 1, 0, LOPT_DHCP_HOST },
     { "dhcp-optsfile", 1, 0, LOPT_DHCP_OPTS },
@@ -383,6 +385,7 @@ static struct {
   { LOPT_CIRCUIT, ARG_DUP, "set:<tag>,<circuit>", gettext_noop("Map RFC3046 circuit-id to tag."), NULL },
   { LOPT_REMOTE, ARG_DUP, "set:<tag>,<remote>", gettext_noop("Map RFC3046 remote-id to tag."), NULL },
   { LOPT_SUBSCR, ARG_DUP, "set:<tag>,<remote>", gettext_noop("Map RFC3993 subscriber-id to tag."), NULL },
+  { LOPT_PXE_VENDOR, ARG_DUP, "<vendor>[,...]", gettext_noop("Specify vendor class to match for PXE requests."), NULL },
   { 'J', ARG_DUP, "tag:<tag>...", gettext_noop("Don't do DHCP for hosts with tag set."), NULL },
   { LOPT_BROADCAST, ARG_DUP, "[=tag:<tag>...]", gettext_noop("Force broadcast replies for hosts with tag set."), NULL }, 
   { 'k', OPT_NO_FORK, NULL, gettext_noop("Do NOT fork into the background, do NOT run in debug mode."), NULL },
@@ -3672,8 +3675,8 @@ static int one_opt(int option, char *arg, char *errstr, char *gen_err, int comma
             new->val = opt_malloc(new->len);
             memcpy(new->val + 1, arg, new->len - 1);
             
-            new->u.vendor_class = (unsigned char *)"PXEClient";
-            new->flags = DHOPT_VENDOR;
+            new->u.vendor_class = NULL;
+            new->flags = DHOPT_VENDOR | DHOPT_VENDOR_PXE;
             
             if (comma && atoi_check(comma, &timeout))
               *(new->val) = timeout;
@@ -3935,6 +3938,19 @@ static int one_opt(int option, char *arg, char *errstr, char *gen_err, int comma
        new->next = daemon->override_relays;
        daemon->override_relays = new;
        arg = comma;
+       }
+         break;
+
+    case LOPT_PXE_VENDOR: /* --dhcp-pxe-vendor */
+      {
+        while (arg) {
+         struct dhcp_pxe_vendor *new = opt_malloc(sizeof(struct dhcp_pxe_vendor));
+         comma = split(arg);
+          new->data = opt_string_alloc(arg);
+         new->next = daemon->dhcp_pxe_vendors;
+         daemon->dhcp_pxe_vendors = new;
+         arg = comma;
+       }
       }
       break;
 
@@ -5212,6 +5228,13 @@ void read_opts(int argc, char **argv, char *compile_opts)
       strcat(buff, daemon->authserver);
       daemon->hostmaster = opt_string_alloc(buff);
     }
+
+  if (!daemon->dhcp_pxe_vendors)
+    {
+      daemon->dhcp_pxe_vendors = opt_malloc(sizeof(struct dhcp_pxe_vendor));
+      daemon->dhcp_pxe_vendors->data = opt_string_alloc(DHCP_PXE_DEF_VENDOR);
+      daemon->dhcp_pxe_vendors->next = NULL;
+    }
   
   /* only one of these need be specified: the other defaults to the host-name */
   if (option_bool(OPT_LOCALMX) || daemon->mxnames || daemon->mxtarget)
index fefe63d..79af53f 100644 (file)
@@ -333,55 +333,6 @@ unsigned char *skip_section(unsigned char *ansp, int count, struct dns_header *h
   return ansp;
 }
 
-/* CRC the question section. This is used to safely detect query 
-   retransmission and to detect answers to questions we didn't ask, which 
-   might be poisoning attacks. Note that we decode the name rather 
-   than CRC the raw bytes, since replies might be compressed differently. 
-   We ignore case in the names for the same reason. Return all-ones
-   if there is not question section. */
-#ifndef HAVE_DNSSEC
-unsigned int questions_crc(struct dns_header *header, size_t plen, char *name)
-{
-  int q;
-  unsigned int crc = 0xffffffff;
-  unsigned char *p1, *p = (unsigned char *)(header+1);
-
-  for (q = ntohs(header->qdcount); q != 0; q--) 
-    {
-      if (!extract_name(header, plen, &p, name, 1, 4))
-       return crc; /* bad packet */
-      
-      for (p1 = (unsigned char *)name; *p1; p1++)
-       {
-         int i = 8;
-         char c = *p1;
-
-         if (c >= 'A' && c <= 'Z')
-           c += 'a' - 'A';
-
-         crc ^= c << 24;
-         while (i--)
-           crc = crc & 0x80000000 ? (crc << 1) ^ 0x04c11db7 : crc << 1;
-       }
-      
-      /* CRC the class and type as well */
-      for (p1 = p; p1 < p+4; p1++)
-       {
-         int i = 8;
-         crc ^= *p1 << 24;
-         while (i--)
-           crc = crc & 0x80000000 ? (crc << 1) ^ 0x04c11db7 : crc << 1;
-       }
-
-      p += 4;
-      if (!CHECK_LEN(header, p, plen, 0))
-       return crc; /* bad packet */
-    }
-
-  return crc;
-}
-#endif
-
 size_t resize_packet(struct dns_header *header, size_t plen, unsigned char *pheader, size_t hlen)
 {
   unsigned char *ansp = skip_questions(header, plen);
@@ -1408,6 +1359,8 @@ size_t answer_request(struct dns_header *header, char *limit, size_t qlen,
                }
 
            }
+         else
+           return 0; /* give up if any cached CNAME in chain can't be used for DNSSEC reasons. */
 
          strcpy(name, cname_target);
        }
index fc54aab..d678068 100644 (file)
@@ -30,7 +30,7 @@ static struct in_addr server_id(struct dhcp_context *context, struct in_addr ove
 static unsigned int calc_time(struct dhcp_context *context, struct dhcp_config *config, unsigned char *opt);
 static void option_put(struct dhcp_packet *mess, unsigned char *end, int opt, int len, unsigned int val);
 static void option_put_string(struct dhcp_packet *mess, unsigned char *end, 
-                             int opt, char *string, int null_term);
+                             int opt, const char *string, int null_term);
 static struct in_addr option_addr(unsigned char *opt);
 static unsigned int option_uint(unsigned char *opt, int offset, int size);
 static void log_packet(char *type, void *addr, unsigned char *ext_mac, 
@@ -54,17 +54,19 @@ static void do_options(struct dhcp_context *context,
                       int vendor_class_len,
                       time_t now,
                       unsigned int lease_time,
-                      unsigned short fuzz);
+                      unsigned short fuzz,
+                      const char *pxevendor);
 
 
 static void match_vendor_opts(unsigned char *opt, struct dhcp_opt *dopt); 
 static int do_encap_opts(struct dhcp_opt *opt, 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 void pxe_misc(struct dhcp_packet *mess, unsigned char *end, unsigned char *uuid, const char *pxevendor);
 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, time_t now);
 struct dhcp_boot *find_boot(struct dhcp_netid *netid);
 static int pxe_uefi_workaround(int pxe_arch, struct dhcp_netid *netid, struct dhcp_packet *mess, struct in_addr local, time_t now, int pxe);
 static void apply_delay(u32 xid, time_t recvtime, struct dhcp_netid *netid);
+static int is_pxe_client(struct dhcp_packet *mess, size_t sz, const char **pxe_vendor);
 
 size_t dhcp_reply(struct dhcp_context *context, char *iface_name, int int_index,
                  size_t sz, time_t now, int unicast_dest, int loopback,
@@ -76,6 +78,7 @@ size_t dhcp_reply(struct dhcp_context *context, char *iface_name, int int_index,
   struct dhcp_mac *mac;
   struct dhcp_netid_list *id_list;
   int clid_len = 0, ignore = 0, do_classes = 0, rapid_commit = 0, selecting = 0, pxearch = -1;
+  const char *pxevendor = NULL;
   struct dhcp_packet *mess = (struct dhcp_packet *)daemon->dhcp_packet.iov_base;
   unsigned char *end = (unsigned char *)(mess + 1); 
   unsigned char *real_end = (unsigned char *)(mess + 1); 
@@ -647,7 +650,7 @@ size_t dhcp_reply(struct dhcp_context *context, char *iface_name, int int_index,
              
              clear_packet(mess, end);
              do_options(context, mess, end, NULL, hostname, get_domain(mess->yiaddr), 
-                        netid, subnet_addr, 0, 0, -1, NULL, vendor_class_len, now, 0xffffffff, 0);
+                        netid, subnet_addr, 0, 0, -1, NULL, vendor_class_len, now, 0xffffffff, 0, NULL);
            }
        }
       
@@ -835,9 +838,8 @@ size_t dhcp_reply(struct dhcp_context *context, char *iface_name, int int_index,
     clid = NULL;
           
   /* Check if client is PXE client. */
-  if (daemon->enable_pxe && 
-      (opt = option_find(mess, sz, OPTION_VENDOR_ID, 9)) && 
-      strncmp(option_ptr(opt, 0), "PXEClient", 9) == 0)
+  if (daemon->enable_pxe &&
+      is_pxe_client(mess, sz, &pxevendor))
     {
       if ((opt = option_find(mess, sz, OPTION_PXE_UUID, 17)))
        {
@@ -899,7 +901,7 @@ size_t dhcp_reply(struct dhcp_context *context, char *iface_name, int int_index,
          
          option_put(mess, end, OPTION_MESSAGE_TYPE, 1, DHCPACK);
          option_put(mess, end, OPTION_SERVER_IDENTIFIER, INADDRSZ, htonl(context->local.s_addr));
-         pxe_misc(mess, end, uuid);
+         pxe_misc(mess, end, uuid, pxevendor);
          
          prune_vendor_opts(tagif_netid);
          opt71.val = save71;
@@ -979,7 +981,7 @@ size_t dhcp_reply(struct dhcp_context *context, char *iface_name, int int_index,
                  option_put(mess, end, OPTION_MESSAGE_TYPE, 1, 
                             mess_type == DHCPDISCOVER ? DHCPOFFER : DHCPACK);
                  option_put(mess, end, OPTION_SERVER_IDENTIFIER, INADDRSZ, htonl(tmp->local.s_addr));
-                 pxe_misc(mess, end, uuid);
+                 pxe_misc(mess, end, uuid, pxevendor);
                  prune_vendor_opts(tagif_netid);
                  if ((pxe && !workaround) || !redirect4011)
                    do_encap_opts(pxe_opts(pxearch, tagif_netid, tmp->local, now), OPTION_VENDOR_CLASS_OPT, DHOPT_VENDOR_MATCH, mess, end, 0);
@@ -1150,7 +1152,7 @@ size_t dhcp_reply(struct dhcp_context *context, char *iface_name, int int_index,
       option_put(mess, end, OPTION_LEASE_TIME, 4, time);
       /* T1 and T2 are required in DHCPOFFER by HP's wacky Jetdirect client. */
       do_options(context, mess, end, req_options, offer_hostname, get_domain(mess->yiaddr), 
-                netid, subnet_addr, fqdn_flags, borken_opt, pxearch, uuid, vendor_class_len, now, time, fuzz);
+                netid, subnet_addr, fqdn_flags, borken_opt, pxearch, uuid, vendor_class_len, now, time, fuzz, pxevendor);
       
       return dhcp_packet_size(mess, agent_id, real_end);
        
@@ -1499,7 +1501,7 @@ size_t dhcp_reply(struct dhcp_context *context, char *iface_name, int int_index,
          if (rapid_commit)
             option_put(mess, end, OPTION_RAPID_COMMIT, 0, 0);
           do_options(context, mess, end, req_options, hostname, get_domain(mess->yiaddr), 
-                    netid, subnet_addr, fqdn_flags, borken_opt, pxearch, uuid, vendor_class_len, now, time, fuzz);
+                    netid, subnet_addr, fqdn_flags, borken_opt, pxearch, uuid, vendor_class_len, now, time, fuzz, pxevendor);
        }
 
       return dhcp_packet_size(mess, agent_id, real_end); 
@@ -1566,7 +1568,7 @@ size_t dhcp_reply(struct dhcp_context *context, char *iface_name, int int_index,
        }
 
       do_options(context, mess, end, req_options, hostname, get_domain(mess->ciaddr),
-                netid, subnet_addr, fqdn_flags, borken_opt, pxearch, uuid, vendor_class_len, now, 0xffffffff, 0);
+                netid, subnet_addr, fqdn_flags, borken_opt, pxearch, uuid, vendor_class_len, now, 0xffffffff, 0, pxevendor);
       
       *is_inform = 1; /* handle reply differently */
       return dhcp_packet_size(mess, agent_id, real_end); 
@@ -1948,7 +1950,7 @@ 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)
+                             const char *string, int null_term)
 {
   unsigned char *p;
   size_t len = strlen(string);
@@ -2026,15 +2028,32 @@ static void match_vendor_opts(unsigned char *opt, struct dhcp_opt *dopt)
       dopt->flags &= ~DHOPT_VENDOR_MATCH;
       if (opt && (dopt->flags & DHOPT_VENDOR))
        {
-         int i, len = 0;
-         if (dopt->u.vendor_class)
-           len = strlen((char *)dopt->u.vendor_class);
-         for (i = 0; i <= (option_len(opt) - len); i++)
-           if (len == 0 || memcmp(dopt->u.vendor_class, option_ptr(opt, i), len) == 0)
-             {
-               dopt->flags |= DHOPT_VENDOR_MATCH;
-               break;
-             }
+         const struct dhcp_pxe_vendor *pv;
+         struct dhcp_pxe_vendor dummy_vendor = {
+           .data = (char *)dopt->u.vendor_class,
+           .next = NULL,
+         };
+         if (dopt->flags & DHOPT_VENDOR_PXE)
+           pv = daemon->dhcp_pxe_vendors;
+         else
+           pv = &dummy_vendor;
+         for (; pv; pv = pv->next)
+           {
+             int i, len = 0, matched = 0;
+             if (pv->data)
+               len = strlen(pv->data);
+             for (i = 0; i <= (option_len(opt) - len); i++)
+               if (len == 0 || memcmp(pv->data, option_ptr(opt, i), len) == 0)
+                 {
+                   matched = 1;
+                   break;
+                 }
+             if (matched)
+               {
+                 dopt->flags |= DHOPT_VENDOR_MATCH;
+                 break;
+               }
+           }
        }
     }
 }
@@ -2087,11 +2106,13 @@ static int do_encap_opts(struct dhcp_opt *opt, int encap, int flag,
   return ret;
 }
 
-static void pxe_misc(struct dhcp_packet *mess, unsigned char *end, unsigned char *uuid)
+static void pxe_misc(struct dhcp_packet *mess, unsigned char *end, unsigned char *uuid, const char *pxevendor)
 {
   unsigned char *p;
 
-  option_put_string(mess, end, OPTION_VENDOR_ID, "PXEClient", 0);
+  if (!pxevendor)
+    pxevendor="PXEClient";
+  option_put_string(mess, end, OPTION_VENDOR_ID, pxevendor, 0);
   if (uuid && (p = free_space(mess, end, OPTION_PXE_UUID, 17)))
     memcpy(p, uuid, 17);
 }
@@ -2308,6 +2329,29 @@ struct dhcp_boot *find_boot(struct dhcp_netid *netid)
   return boot;
 }
 
+static int is_pxe_client(struct dhcp_packet *mess, size_t sz, const char **pxe_vendor)
+{
+  const unsigned char *opt = NULL;
+  ssize_t conf_len = 0;
+  const struct dhcp_pxe_vendor *conf = daemon->dhcp_pxe_vendors;
+  opt = option_find(mess, sz, OPTION_VENDOR_ID, 0);
+  if (!opt) 
+    return 0;
+  for (; conf; conf = conf->next)
+    {
+      conf_len = strlen(conf->data);
+      if (option_len(opt) < conf_len)
+        continue;
+      if (strncmp(option_ptr(opt, 0), conf->data, conf_len) == 0)
+        {
+          if (pxe_vendor)
+            *pxe_vendor = conf->data;
+          return 1;
+        }
+    }
+  return 0;
+}
+
 static void do_options(struct dhcp_context *context,
                       struct dhcp_packet *mess,
                       unsigned char *end, 
@@ -2322,7 +2366,8 @@ static void do_options(struct dhcp_context *context,
                       int vendor_class_len,
                       time_t now,
                       unsigned int lease_time,
-                      unsigned short fuzz)
+                      unsigned short fuzz,
+                      const char *pxevendor)
 {
   struct dhcp_opt *opt, *config_opts = daemon->dhcp_opts;
   struct dhcp_boot *boot;
@@ -2696,7 +2741,7 @@ static void do_options(struct dhcp_context *context,
   
   if (context && pxe_arch != -1)
     {
-      pxe_misc(mess, end, uuid);
+      pxe_misc(mess, end, uuid, pxevendor);
       if (!pxe_uefi_workaround(pxe_arch, tagif, mess, context->local, now, 0))
        config_opts = pxe_opts(pxe_arch, tagif, context->local, now);
     }