]> git.ipfire.org Git - people/ms/dnsmasq.git/commitdiff
Check that unsigned replies come from unsigned zones if --dnssec-check-unsigned set.
authorSimon Kelley <simon@thekelleys.org.uk>
Fri, 28 Feb 2014 18:10:55 +0000 (18:10 +0000)
committerSimon Kelley <simon@thekelleys.org.uk>
Fri, 28 Feb 2014 18:10:55 +0000 (18:10 +0000)
man/dnsmasq.8
src/dnsmasq.h
src/dnssec.c
src/forward.c
src/option.c
src/rfc1035.c

index ed71ebe3056325c5076dfd9cce289d619f3dcd66..7bf1db71dda4b96f9d711ff459b2f1773cb1f091 100644 (file)
@@ -608,6 +608,15 @@ key(s) of the root zone,
 but trust anchors for limited domains are also possible. The current
 root-zone trust anchors may be donwloaded from https://data.iana.org/root-anchors/root-anchors.xml 
 .TP
+.B --dnssec-check-unsigned
+As a default, dnsmasq does not check that unsigned DNS replies are
+legitimate: they are assumed to be valid and passed on (without the
+"authentic data" bit set, of course). This does not protect against an
+attacker forging unsigned replies for signed DNS zones, but it is
+fast. If this flag is set, dnsmasq will check the zones of unsigned
+replies, to ensure that unsigned replies are allowed in those
+zones. The cost of this is more upstream queries and slower performance.
+.TP
 .B --proxy-dnssec
 Copy the DNSSEC Authenticated Data bit from upstream servers to downstream clients and cache it.  This is an 
 alternative to having dnsmasq validate DNSSEC, but it depends on the security of the network between 
index f984f3bb18fd151993ffb1985dc85362c0565f13..a00d95cfa288df921b7ec6ddfd6f67e76ca9e37c 100644 (file)
@@ -232,7 +232,8 @@ struct event_desc {
 #define OPT_DNSSEC_VALID   45
 #define OPT_DNSSEC_PERMISS 46
 #define OPT_DNSSEC_DEBUG   47
-#define OPT_LAST           48
+#define OPT_DNSSEC_NO_SIGN 48 
+#define OPT_LAST           49
 
 /* extra flags for my_syslog, we use a couple of facilities since they are known 
    not to occupy the same bits as priorities, no matter how syslog.h is set up. */
@@ -535,6 +536,10 @@ struct hostsfile {
 #define STAT_NEED_KEY           5
 #define STAT_TRUNCATED          6
 #define STAT_SECURE_WILDCARD    7
+#define STAT_NO_SIG             8
+#define STAT_NO_DS              9
+#define STAT_NEED_DS_NEG       10
+#define STAT_CHASE_CNAME       11
 
 #define FREC_NOREBIND           1
 #define FREC_CHECKING_DISABLED  2
@@ -544,6 +549,7 @@ struct hostsfile {
 #define FREC_AD_QUESTION       32
 #define FREC_DO_QUESTION       64
 #define FREC_ADDED_PHEADER    128
+#define FREC_CHECK_NOSIGN     256
 
 #ifdef HAVE_DNSSEC
 #define HASH_SIZE 20 /* SHA-1 digest size */
@@ -1085,7 +1091,8 @@ int in_zone(struct auth_zone *zone, char *name, char **cut);
 size_t dnssec_generate_query(struct dns_header *header, char *end, char *name, int class, int type, union mysockaddr *addr);
 int dnssec_validate_by_ds(time_t now, struct dns_header *header, size_t n, char *name, char *keyname, int class);
 int dnssec_validate_ds(time_t now, struct dns_header *header, size_t plen, char *name, char *keyname, int class);
-int dnssec_validate_reply(time_t now, struct dns_header *header, size_t plen, char *name, char *keyname, int *class);
+int dnssec_validate_reply(time_t now, struct dns_header *header, size_t plen, char *name, char *keyname, int *class, int *neganswer);
+int dnssec_chase_cname(time_t now, struct dns_header *header, size_t plen, char *name, char *keyname);
 int dnskey_keytag(int alg, int flags, unsigned char *rdata, int rdlen);
 size_t filter_rrsigs(struct dns_header *header, size_t plen);
 unsigned char* hash_questions(struct dns_header *header, size_t plen, char *name);
index 1a1c0e4fb429904357fe668f6b2b6a92e3a48dcc..35926918c98c59c2ae9486eb8a0022a2dd2534aa 100644 (file)
@@ -496,6 +496,8 @@ static int expand_workspace(unsigned char ***wkspc, int *sz, int new)
   
   *wkspc = p;
   *sz = new_sz;
+
+  return 1;
 }
 
 /* Bubble sort the RRset into the canonical order. 
@@ -588,6 +590,7 @@ static void sort_rrset(struct dns_header *header, size_t plen, u16 *rr_desc, int
    Return code:
    STAT_SECURE   if it validates.
    STAT_SECURE_WILDCARD if it validates and is the result of wildcard expansion.
+   STAT_NO_SIG no RRsigs found.
    STAT_INSECURE can't validate (no RRSIG, bad packet).
    STAT_BOGUS    signature is wrong.
    STAT_NEED_KEY need DNSKEY to complete validation (name is returned in keyname)
@@ -670,9 +673,13 @@ static int validate_rrset(time_t now, struct dns_header *header, size_t plen, in
        return STAT_INSECURE;
     }
   
-  /* RRset empty, no RRSIGs */
-  if (rrsetidx == 0 || sigidx == 0)
+  /* RRset empty */
+  if (rrsetidx == 0)
     return STAT_INSECURE; 
+
+  /* no RRSIGs */
+  if (sigidx == 0)
+    return STAT_NO_SIG; 
   
   /* Sort RRset records into canonical order. 
      Note that at this point keyname and daemon->workspacename buffs are
@@ -1058,6 +1065,7 @@ int dnssec_validate_by_ds(time_t now, struct dns_header *header, size_t plen, ch
    return codes:
    STAT_INSECURE    bad packet, no DS in reply, proven no DS in reply.
    STAT_SECURE      At least one valid DS found and in cache.
+   STAT_NO_DS       It's proved there's no DS here.
    STAT_BOGUS       At least one DS found, which fails validation.
    STAT_NEED_DNSKEY DNSKEY records to validate a DS not found, name in keyname
 */
@@ -1065,7 +1073,7 @@ int dnssec_validate_by_ds(time_t now, struct dns_header *header, size_t plen, ch
 int dnssec_validate_ds(time_t now, struct dns_header *header, size_t plen, char *name, char *keyname, int class)
 {
   unsigned char *p = (unsigned char *)(header+1);
-  int qtype, qclass, val, i;
+  int qtype, qclass, val, i, neganswer;
 
   if (ntohs(header->qdcount) != 1 ||
       !(p = skip_name(p, header, plen, 4)))
@@ -1077,25 +1085,36 @@ int dnssec_validate_ds(time_t now, struct dns_header *header, size_t plen, char
   if (qtype != T_DS || qclass != class)
     val = STAT_BOGUS;
   else
-    val = dnssec_validate_reply(now, header, plen, name, keyname, NULL);
+    val = dnssec_validate_reply(now, header, plen, name, keyname, NULL, &neganswer);
+
+  if (val == STAT_NO_SIG)
+    val = STAT_INSECURE;
   
   p = (unsigned char *)(header+1);
   extract_name(header, plen, &p, name, 1, 4);
   p += 4; /* qtype, qclass */
   
+  if (!(p = skip_section(p, ntohs(header->ancount), header, plen)))
+    return STAT_INSECURE;
+  
   if (val == STAT_BOGUS)
     log_query(F_UPSTREAM, name, NULL, "BOGUS DS");
   
-  /* proved that no DS exists, cache neg answer, can't validate */
-  if (val == STAT_SECURE && ntohs(header->ancount) == 0)
+  if ((val == STAT_SECURE || val == STAT_INSECURE) && neganswer)
     {
-      int  rdlen, rc;
+      int rdlen, flags =  F_FORWARD | F_DS | F_NEG ;
       unsigned long ttl, minttl = ULONG_MAX;
       struct all_addr a;
+
+      if (RCODE(header) == NXDOMAIN)
+       flags |= F_NXDOMAIN;
+      
+      if (val == STAT_SECURE)
+       flags |= F_DNSSECOK;
       
       for (i = ntohs(header->nscount); i != 0; i--)
        {
-         if (!(rc = extract_name(header, plen, &p, name, 0, 10)))
+         if (!(p = skip_name(p, header, plen, 0)))
            return STAT_INSECURE;
          
          GETSHORT(qtype, p); 
@@ -1103,10 +1122,10 @@ int dnssec_validate_ds(time_t now, struct dns_header *header, size_t plen, char
          GETLONG(ttl, p);
          GETSHORT(rdlen, p);
 
-         if (!CHECK_LEN(header, p, plen, rdlen) || rdlen < 4)
+         if (!CHECK_LEN(header, p, plen, rdlen))
            return STAT_INSECURE; /* bad packet */
-         
-         if (qclass != class || qtype != T_SOA || rc ==2)
+           
+         if (qclass != class || qtype != T_SOA)
            {
              p += rdlen;
              continue;
@@ -1126,16 +1145,21 @@ int dnssec_validate_ds(time_t now, struct dns_header *header, size_t plen, char
          GETLONG(ttl, p); /* minTTL */
          if (ttl < minttl)
            minttl = ttl;
+         
+         break;
        }
       
-      cache_start_insert();
-
-      a.addr.dnssec.class = class;
-      cache_insert(name, &a, now, ttl, F_FORWARD | F_DS | F_DNSSECOK | F_NEG | (RCODE(header) == NXDOMAIN ? F_NXDOMAIN : 0));
-       
-      cache_end_insert();
+      if (i != 0)
+       {
+         cache_start_insert();
+         
+         a.addr.dnssec.class = class;
+         cache_insert(name, &a, now, ttl, flags);
+         
+         cache_end_insert(); 
+       }
 
-      return STAT_INSECURE; 
+      return (val == STAT_SECURE) ? STAT_NO_DS : STAT_INSECURE; 
     }
 
   return val;
@@ -1624,22 +1648,76 @@ static int prove_non_existence_nsec3(struct dns_header *header, size_t plen, uns
     
 /* Validate all the RRsets in the answer and authority sections of the reply (4035:3.2.3) */
 /* Returns are the same as validate_rrset, plus the class if the missing key is in *class */
-int dnssec_validate_reply(time_t now, struct dns_header *header, size_t plen, char *name, char *keyname, int *class)
+int dnssec_validate_reply(time_t now, struct dns_header *header, size_t plen, char *name, char *keyname, int *class, int *neganswer)
 {
-  unsigned char *ans_start, *p1, *p2, **nsecs;
-  int type1, class1, rdlen1, type2, class2, rdlen2;
+  unsigned char *ans_start, *qname, *p1, *p2, **nsecs;
+  int type1, class1, rdlen1, type2, class2, rdlen2, qclass, qtype;
   int i, j, rc, nsec_count, cname_count = 10;
-  int nsec_type = 0;
+  int nsec_type = 0, have_answer = 0;
 
+  if (neganswer)
+    *neganswer = 0;
+  
   if (RCODE(header) == SERVFAIL)
     return STAT_BOGUS;
   
   if ((RCODE(header) != NXDOMAIN && RCODE(header) != NOERROR) || ntohs(header->qdcount) != 1)
     return STAT_INSECURE;
+
+  qname = p1 = (unsigned char *)(header+1);
   
-  if (!(ans_start = skip_questions(header, plen)))
+  if (!extract_name(header, plen, &p1, name, 1, 4))
+    return STAT_INSECURE;
+
+  GETSHORT(qtype, p1);
+  GETSHORT(qclass, p1);
+  ans_start = p1;
+  /* Can't validate an RRISG query */
+  if (qtype == T_RRSIG)
     return STAT_INSECURE;
+ cname_loop:
+  for (j = ntohs(header->ancount); j != 0; j--) 
+    {
+      /* leave pointer to missing name in qname */
+           
+      if (!(rc = extract_name(header, plen, &p1, name, 0, 10)))
+       return STAT_INSECURE; /* bad packet */
+      
+      GETSHORT(type2, p1); 
+      GETSHORT(class2, p1);
+      p1 += 4; /* TTL */
+      GETSHORT(rdlen2, p1);
+
+      if (rc == 1 && qclass == class2)
+       {
+         /* Do we have an answer for the question? */
+         if (type2 == qtype)
+           {
+             have_answer = 1;
+             break;
+           }
+         else if (type2 == T_CNAME)
+           {
+             qname = p1;
+             
+             /* looped CNAMES */
+             if (!cname_count-- || !extract_name(header, plen, &p1, name, 1, 0))
+               return STAT_INSECURE;
+              
+             p1 = ans_start;
+             goto cname_loop;
+           }
+       } 
+
+      if (!ADD_RDLEN(header, p1, plen, rdlen2))
+       return STAT_INSECURE;
+    }
    
+  if (neganswer && !have_answer)
+    *neganswer = 1;
+  
   for (p1 = ans_start, i = 0; i < ntohs(header->ancount) + ntohs(header->nscount); i++)
     {
       if (!extract_name(header, plen, &p1, name, 1, 10))
@@ -1812,70 +1890,98 @@ int dnssec_validate_reply(time_t now, struct dns_header *header, size_t plen, ch
     }
 
   /* OK, all the RRsets validate, now see if we have a NODATA or NXDOMAIN reply */
-
-  p1 = (unsigned char *)(header+1);
-  
-  if (!extract_name(header, plen, &p1, name, 1, 4))
-    return STAT_INSECURE;
-
-  GETSHORT(type1, p1);
-  GETSHORT(class1, p1);
-
-  /* Can't validate RRSIG query */
-  if (type1 == T_RRSIG)
-    return STAT_INSECURE;
-
- cname_loop:
-  for (j = ntohs(header->ancount); j != 0; j--) 
-    {
-      if (!(rc = extract_name(header, plen, &p1, name, 0, 10)))
-       return STAT_INSECURE; /* bad packet */
-      
-      GETSHORT(type2, p1); 
-      GETSHORT(class2, p1);
-      p1 += 4; /* TTL */
-      GETSHORT(rdlen2, p1);
-
-      if (rc == 1 && class1 == class2)
-       {
-         /* Do we have an answer for the question? */
-         if (type1 == type2)
-           return RCODE(header) == NXDOMAIN ? STAT_BOGUS : STAT_SECURE;
-         else if (type2 == T_CNAME)
-           {
-             /* looped CNAMES */
-             if (!cname_count-- || 
-                 !extract_name(header, plen, &p1, name, 1, 0) ||
-                 !(p1 = skip_questions(header, plen)))
-               return STAT_INSECURE;
-             
-             goto cname_loop;
-           }
-       } 
-
-      if (!ADD_RDLEN(header, p1, plen, rdlen2))
-       return STAT_INSECURE;
-    }
-
+  if (have_answer)
+    return STAT_SECURE;
+     
   /* NXDOMAIN or NODATA reply, prove that (name, class1, type1) can't exist */
-  
   /* First marshall the NSEC records, if we've not done it previously */
   if (!nsec_type)
     {
-      nsec_type = find_nsec_records(header, plen, &nsecs, &nsec_count, class1);
+      nsec_type = find_nsec_records(header, plen, &nsecs, &nsec_count, qclass);
       
       if (nsec_type == 0)
        return STAT_INSECURE; /* Bad packet */
       if (nsec_type == -1)
        return STAT_BOGUS; /* No NSECs */
     }
+   
+  /* Get name of missing answer */
+  if (!extract_name(header, plen, &qname, name, 1, 0))
+    return STAT_INSECURE;
   
   if (nsec_type == T_NSEC)
-    return prove_non_existence_nsec(header, plen, nsecs, nsec_count, daemon->workspacename, keyname, name, type1);
+    return prove_non_existence_nsec(header, plen, nsecs, nsec_count, daemon->workspacename, keyname, name, qtype);
   else
-    return prove_non_existence_nsec3(header, plen, nsecs, nsec_count, daemon->workspacename, keyname, name, type1);
+    return prove_non_existence_nsec3(header, plen, nsecs, nsec_count, daemon->workspacename, keyname, name, qtype);
 }
 
+/* Chase the CNAME chain in the packet until the first record which _doesn't validate.
+   Needed for proving answer in unsigned space.
+   Return STAT_NEED_* 
+          STAT_BOGUS - error
+          STAT_INSECURE - name of first non-secure record in name 
+*/
+int dnssec_chase_cname(time_t now, struct dns_header *header, size_t plen, char *name, char *keyname)
+{
+  unsigned char *p = (unsigned char *)(header+1);
+  int type, class, qtype, qclass, rdlen, j, rc;
+  int cname_count = 10;
+
+  /* Get question */
+  if (!extract_name(header, plen, &p, name, 1, 4))
+    return STAT_BOGUS;
+  
+  GETSHORT(qtype, p);
+  GETSHORT(qclass, p);
+
+  while (1)
+    {
+      for (j = ntohs(header->ancount); j != 0; j--) 
+       {
+         if (!(rc = extract_name(header, plen, &p, name, 0, 10)))
+           return STAT_BOGUS; /* bad packet */
+         
+         GETSHORT(type, p); 
+         GETSHORT(class, p);
+         p += 4; /* TTL */
+         GETSHORT(rdlen, p);
+
+         /* Not target, loop */
+         if (rc == 2 || qclass != class)
+           {
+             if (!ADD_RDLEN(header, p, plen, rdlen))
+               return STAT_BOGUS;
+             continue;
+           }
+         
+         /* Got to end of CNAME chain. */
+         if (type != T_CNAME)
+           return STAT_INSECURE;
+         
+         /* validate CNAME chain, return if insecure or need more data */
+         rc = validate_rrset(now, header, plen, class, type, name, keyname, NULL, 0, 0, 0);
+         if (rc != STAT_SECURE)
+           {
+             if (rc == STAT_NO_SIG)
+               rc = STAT_INSECURE;
+             return rc;
+           }
+
+         /* Loop down CNAME chain/ */
+         if (!cname_count-- || 
+             !extract_name(header, plen, &p, name, 1, 0) ||
+             !(p = skip_questions(header, plen)))
+           return STAT_BOGUS;
+         
+         break;
+       }
+
+      /* End of CNAME chain */
+      return STAT_INSECURE;    
+    }
+}
+
+
 /* Compute keytag (checksum to quickly index a key). See RFC4034 */
 int dnskey_keytag(int alg, int flags, unsigned char *key, int keylen)
 {
@@ -1951,7 +2057,8 @@ static int check_name(unsigned char **namep, struct dns_header *header, size_t p
       if (label_type == 0xc0)
        {
          /* pointer for compression. */
-         unsigned int offset, i;
+         unsigned int offset;
+         int i;
          unsigned char *p;
          
          if (!CHECK_LEN(header, ansp, plen, 2))
@@ -1971,7 +2078,7 @@ static int check_name(unsigned char **namep, struct dns_header *header, size_t p
 
          /* does the pointer end up in an elided RR? */
          if (i & 1)
-           return -1;
+           return 0;
 
          /* No, scale the pointer */
          if (fixup)
@@ -2023,27 +2130,26 @@ static int check_name(unsigned char **namep, struct dns_header *header, size_t p
 static int check_rrs(unsigned char *p, struct dns_header *header, size_t plen, int fixup, unsigned char **rrs, int rr_count)
 {
   int i, type, class, rdlen;
+  unsigned char *pp;
   
   for (i = 0; i < ntohs(header->ancount) + ntohs(header->nscount); i++)
     {
-       if (type != T_NSEC && type != T_NSEC3 && type != T_RRSIG)
-        {
-          if (!check_name(&p, header, plen, fixup, rrs, rr_count))
-            return 0;
-        }
-       else
-        {
-          if (!(p = skip_name(p, header, plen, 10)))
-            return 0;
-        }
+      pp = p;
+
+      if (!(p = skip_name(p, header, plen, 10)))
+       return 0;
       
       GETSHORT(type, p); 
       GETSHORT(class, p);
       p += 4; /* TTL */
       GETSHORT(rdlen, p);
-      
+
       if (type != T_NSEC && type != T_NSEC3 && type != T_RRSIG)
        {
+         /* fixup name of RR */
+         if (!check_name(&pp, header, plen, fixup, rrs, rr_count))
+           return 0;
+         
          if (class == C_IN)
            {
              u16 *d;
index c05efe4c404688588f3e5d7e44eccd819f909db8..79167167c7854f3a3dd84f8076432a1d5b9e7c4a 100644 (file)
@@ -24,6 +24,14 @@ static unsigned short get_id(void);
 static void free_frec(struct frec *f);
 static struct randfd *allocate_rfd(int family);
 
+#ifdef HAVE_DNSSEC
+static int tcp_key_recurse(time_t now, int status, struct dns_header *header, size_t n, 
+                          int class, char *name, char *keyname, struct server *server, int *keycount);
+static int do_check_sign(time_t now, struct dns_header *header, size_t plen, char *name, char *keyname, int class);
+static int send_check_sign(time_t now, struct dns_header *header, size_t plen, char *name, char *keyname);
+#endif
+
+
 /* Send a UDP packet with its source address set as "source" 
    unless nowild is true, when we just send it with the kernel default */
 int send_from(int fd, int nowild, char *packet, size_t len, 
@@ -250,6 +258,8 @@ static int forward_query(int udpfd, union mysockaddr *udpaddr,
 #endif
  unsigned int gotname = extract_request(header, plen, daemon->namebuff, NULL);
 
+ (void)do_bit;
+
   /* may be no servers available. */
   if (!daemon->servers)
     forward = NULL;
@@ -522,6 +532,7 @@ static size_t process_reply(struct dns_header *header, time_t now, struct server
   size_t plen; 
 
   (void)ad_reqd;
+  (void) do_bit;
 
 #ifdef HAVE_IPSET
   /* Similar algorithm to search_servers. */
@@ -793,13 +804,27 @@ void reply_query(int fd, int family, time_t now)
          else if (forward->flags & FREC_DNSKEY_QUERY)
            status = dnssec_validate_by_ds(now, header, n, daemon->namebuff, daemon->keyname, forward->class);
          else if (forward->flags & FREC_DS_QUERY)
-           status = dnssec_validate_ds(now, header, n, daemon->namebuff, daemon->keyname, forward->class);
+           {
+             status = dnssec_validate_ds(now, header, n, daemon->namebuff, daemon->keyname, forward->class);
+             if (status == STAT_NO_DS)
+               status = STAT_INSECURE;
+           }
+         else if (forward->flags & FREC_CHECK_NOSIGN)
+           status = do_check_sign(now, header, n, daemon->namebuff, daemon->keyname, forward->class);
          else
-           status = dnssec_validate_reply(now, header, n, daemon->namebuff, daemon->keyname, &forward->class);
-
+           {
+             status = dnssec_validate_reply(now, header, n, daemon->namebuff, daemon->keyname, &forward->class, NULL);
+             if (status == STAT_NO_SIG)
+               {
+                 if (option_bool(OPT_DNSSEC_NO_SIGN))
+                   status = send_check_sign(now, header, n, daemon->namebuff, daemon->keyname);
+                 else
+                   status = STAT_INSECURE;
+               }
+           }
          /* Can't validate, as we're missing key data. Put this
             answer aside, whilst we get that. */     
-         if (status == STAT_NEED_DS || status == STAT_NEED_KEY)
+         if (status == STAT_NEED_DS || status == STAT_NEED_DS_NEG || status == STAT_NEED_KEY)
            {
              struct frec *new, *orig;
              
@@ -829,7 +854,7 @@ void reply_query(int fd, int family, time_t now)
 #ifdef HAVE_IPV6
                  new->rfd6 = NULL;
 #endif
-                 new->flags &= ~(FREC_DNSKEY_QUERY | FREC_DS_QUERY);
+                 new->flags &= ~(FREC_DNSKEY_QUERY | FREC_DS_QUERY | FREC_CHECK_NOSIGN);
                  
                  new->dependent = forward; /* to find query awaiting new one. */
                  forward->blocking_query = new; /* for garbage cleaning */
@@ -842,7 +867,10 @@ void reply_query(int fd, int family, time_t now)
                    }
                  else 
                    {
-                     new->flags |= FREC_DS_QUERY;
+                     if (status == STAT_NEED_DS_NEG)
+                       new->flags |= FREC_CHECK_NOSIGN;
+                     else
+                       new->flags |= FREC_DS_QUERY;
                      nn = dnssec_generate_query(header,((char *) header) + daemon->packet_buff_sz,
                                                 daemon->keyname, forward->class, T_DS, &server->addr);
                    }
@@ -906,11 +934,26 @@ void reply_query(int fd, int family, time_t now)
                  if (forward->flags & FREC_DNSKEY_QUERY)
                    status = dnssec_validate_by_ds(now, header, n, daemon->namebuff, daemon->keyname, forward->class);
                  else if (forward->flags & FREC_DS_QUERY)
-                   status = dnssec_validate_ds(now, header, n, daemon->namebuff, daemon->keyname, forward->class);
+                   {
+                     status = dnssec_validate_ds(now, header, n, daemon->namebuff, daemon->keyname, forward->class);
+                     if (status == STAT_NO_DS)
+                       status = STAT_INSECURE;
+                   }
+                 else if (forward->flags & FREC_CHECK_NOSIGN)
+                   status = do_check_sign(now, header, n, daemon->namebuff, daemon->keyname, forward->class);
                  else
-                   status = dnssec_validate_reply(now, header, n, daemon->namebuff, daemon->keyname, &forward->class); 
-                 
-                 if (status == STAT_NEED_DS || status == STAT_NEED_KEY)
+                   {
+                     status = dnssec_validate_reply(now, header, n, daemon->namebuff, daemon->keyname, &forward->class, NULL); 
+                     if (status == STAT_NO_SIG)
+                       {
+                         if (option_bool(OPT_DNSSEC_NO_SIGN))
+                           status = send_check_sign(now, header, n, daemon->namebuff, daemon->keyname);
+                         else
+                           status = STAT_INSECURE;
+                       }
+                   }
+              
+                 if (status == STAT_NEED_DS || status == STAT_NEED_DS_NEG || status == STAT_NEED_KEY)
                    goto anotherkey;
                }
            }
@@ -1207,6 +1250,164 @@ void receive_query(struct listener *listen, time_t now)
 }
 
 #ifdef HAVE_DNSSEC
+
+/* UDP: we've got an unsigned answer, return STAT_INSECURE if we can prove there's no DS
+   and therefore the answer shouldn't be signed, or STAT_BOGUS if it should be, or 
+   STAT_NEED_DS_NEG and keyname if we need to do the query. */
+static int send_check_sign(time_t now, struct dns_header *header, size_t plen, char *name, char *keyname)
+{
+  struct crec *crecp;
+  char *name_start = name;
+  int status = dnssec_chase_cname(now, header, plen, name, keyname);
+  
+  if (status != STAT_INSECURE)
+    return status;
+
+  while (1)
+    {
+      crecp = cache_find_by_name(NULL, name_start, now, F_DS);
+      
+      if (crecp && (crecp->flags & F_DNSSECOK))
+       return (crecp->flags & F_NEG) ? STAT_INSECURE : STAT_BOGUS;
+       
+      if (crecp && (crecp->flags & F_NEG) && (name_start = strchr(name_start, '.')))
+       {
+         name_start++; /* chop a label off and try again */
+         continue;
+       }
+
+      strcpy(keyname, name_start);
+      return STAT_NEED_DS_NEG;
+    }
+}
+
+/* Got answer to DS query from send_check_sign, check for proven non-existence, or make the next DS query to try. */
+static int do_check_sign(time_t now, struct dns_header *header, size_t plen, char *name, char *keyname, int class)
+  
+{ 
+  char *name_start;
+  unsigned char *p;
+  int status = dnssec_validate_ds(now, header, plen, name, keyname, class);
+  
+  if (status != STAT_INSECURE)
+    {
+      if (status == STAT_NO_DS)
+       status = STAT_INSECURE;
+      return status;
+    }
+  
+  p = (unsigned char *)(header+1);
+  
+  if (extract_name(header, plen, &p, name, 1, 4) &&
+      (name_start = strchr(name, '.')))
+    {
+      name_start++; /* chop a label off and try again */
+      strcpy(keyname, name_start);
+      return STAT_NEED_DS_NEG;
+    }
+  
+  return STAT_BOGUS;
+}
+
+/* Move toward the root, until we find a signed non-existance of a DS, in which case
+   an unsigned answer is OK, or we find a signed DS, in which case there should be 
+   a signature, and the answer is BOGUS */
+static int  tcp_check_for_unsigned_zone(time_t now, struct dns_header *header, size_t plen, int class, char *name, 
+                                       char *keyname, struct server *server, int *keycount)
+{
+  size_t m;
+  unsigned char *packet, *payload;
+  u16 *length;
+  unsigned char *p = (unsigned char *)(header+1);
+  int status;
+  char *name_start = name;
+
+  /* Get first insecure entry in CNAME chain */
+  status = tcp_key_recurse(now, STAT_CHASE_CNAME, header, plen, class, name, keyname, server, keycount);
+  if (status == STAT_BOGUS)
+    return STAT_BOGUS;
+  
+  if (!(packet = whine_malloc(65536 + MAXDNAME + RRFIXEDSZ + sizeof(u16))))
+    return STAT_BOGUS;
+  
+  payload = &packet[2];
+  header = (struct dns_header *)payload;
+  length = (u16 *)packet;
+  
+  while (1)
+    {
+      unsigned char *newhash, hash[HASH_SIZE];
+      unsigned char c1, c2;
+      struct crec *crecp = cache_find_by_name(NULL, name_start, now, F_DS);
+      if (--(*keycount) == 0)
+       return STAT_BOGUS;    
+      
+      if (crecp && (crecp->flags & F_DNSSECOK))
+       {
+         free(packet);
+         return (crecp->flags & F_NEG) ? STAT_INSECURE : STAT_BOGUS;
+       }
+      
+      /* If we have cached insecurely that a DS doesn't exist, 
+        ise that is a hit for where to start looking for the secure one */
+      if (crecp && (crecp->flags & F_NEG) && (name_start = strchr(name_start, '.')))
+       {
+         name_start++; /* chop a label off and try again */
+         continue;
+       }
+
+      m = dnssec_generate_query(header, ((char *) header) + 65536, name_start, class, T_DS, &server->addr);
+      
+      /* We rely on the question section coming back unchanged, ensure it is with the hash. */
+      if ((newhash = hash_questions(header, (unsigned int)m, name)))
+       memcpy(hash, newhash, HASH_SIZE);
+      
+      *length = htons(m);
+      
+      if (read_write(server->tcpfd, packet, m + sizeof(u16), 0) &&
+         read_write(server->tcpfd, &c1, 1, 1) &&
+         read_write(server->tcpfd, &c2, 1, 1) &&
+         read_write(server->tcpfd, payload, (c1 << 8) | c2, 1))
+       {
+         m = (c1 << 8) | c2;
+         
+         newhash = hash_questions(header, (unsigned int)m, name);
+         if (newhash && memcmp(hash, newhash, HASH_SIZE) == 0)
+           {
+             /* Note this trashes all three name workspaces */
+             status = tcp_key_recurse(now, STAT_NEED_DS_NEG, header, m, class, name, keyname, server, keycount);
+                  
+             /* We've found a DS which proves the bit of the DNS where the
+                original query is, is unsigned, so the answer is OK, 
+                if unvalidated. */
+             if (status == STAT_NO_DS)
+               {
+                 free(packet);
+                 return STAT_INSECURE;
+               }
+             
+             /* No DS, not got to DNSSEC-land yet, go up. */
+             if (status == STAT_INSECURE)
+               {
+                 p = (unsigned char *)(header+1);
+                 
+                 if (extract_name(header, plen, &p, name, 1, 4) &&
+                     (name_start = strchr(name, '.')))
+                   {
+                     name_start++; /* chop a label off and try again */
+                     continue;
+                   }
+               }
+           }
+       }
+      
+      free(packet);
+
+      return STAT_BOGUS;
+    }
+}
+
 static int tcp_key_recurse(time_t now, int status, struct dns_header *header, size_t n, 
                           int class, char *name, char *keyname, struct server *server, int *keycount)
 {
@@ -1219,11 +1420,27 @@ static int tcp_key_recurse(time_t now, int status, struct dns_header *header, si
   
   if (status == STAT_NEED_KEY)
     new_status = dnssec_validate_by_ds(now, header, n, name, keyname, class);
-  else if (status == STAT_NEED_DS)
-    new_status = dnssec_validate_ds(now, header, n, name, keyname, class);
-  else
-    new_status = dnssec_validate_reply(now, header, n, name, keyname, &class);
-  
+  else if (status == STAT_NEED_DS || status == STAT_NEED_DS_NEG)
+    {
+      new_status = dnssec_validate_ds(now, header, n, name, keyname, class);
+      if (status == STAT_NEED_DS  && new_status == STAT_NO_DS)
+       new_status = STAT_INSECURE;
+    }
+  else if (status == STAT_CHASE_CNAME)
+    new_status = dnssec_chase_cname(now, header, n, name, keyname);
+  else 
+    {
+      new_status = dnssec_validate_reply(now, header, n, name, keyname, &class, NULL);
+      
+      if (new_status == STAT_NO_SIG)
+       {
+         if (option_bool(OPT_DNSSEC_NO_SIGN))
+           new_status = tcp_check_for_unsigned_zone(now, header, n, class, name, keyname, server, keycount);
+         else
+           new_status = STAT_INSECURE;
+       }
+    }
+
   /* Can't validate because we need a key/DS whose name now in keyname.
      Make query for same, and recurse to validate */
   if (new_status == STAT_NEED_DS || new_status == STAT_NEED_KEY)
@@ -1253,7 +1470,9 @@ static int tcp_key_recurse(time_t now, int status, struct dns_header *header, si
        {
          m = (c1 << 8) | c2;
          
-         if (tcp_key_recurse(now, new_status, new_header, m, class, name, keyname, server, keycount) == STAT_SECURE)
+         new_status = tcp_key_recurse(now, new_status, new_header, m, class, name, keyname, server, keycount);
+         
+         if (new_status == STAT_SECURE)
            {
              /* Reached a validated record, now try again at this level.
                 Note that we may get ANOTHER NEED_* if an answer needs more than one key.
@@ -1261,11 +1480,27 @@ static int tcp_key_recurse(time_t now, int status, struct dns_header *header, si
              
              if (status == STAT_NEED_KEY)
                new_status = dnssec_validate_by_ds(now, header, n, name, keyname, class);
-             else if (status == STAT_NEED_DS)
-               new_status = dnssec_validate_ds(now, header, n, name, keyname, class);
-             else
-               new_status = dnssec_validate_reply(now, header, n, name, keyname, &class);
-
+             else if (status == STAT_NEED_DS || status == STAT_NEED_DS_NEG)
+               {
+                 new_status = dnssec_validate_ds(now, header, n, name, keyname, class);
+                 if (status == STAT_NEED_DS && new_status == STAT_NO_DS)
+                   new_status = STAT_INSECURE; /* Validated no DS */
+               }
+             else if (status == STAT_CHASE_CNAME)
+               new_status = dnssec_chase_cname(now, header, n, name, keyname);
+             else 
+               {
+                 new_status = dnssec_validate_reply(now, header, n, name, keyname, &class, NULL);
+                 
+                 if (new_status == STAT_NO_SIG)
+                   {
+                     if (option_bool(OPT_DNSSEC_NO_SIGN))
+                       new_status = tcp_check_for_unsigned_zone(now, header, n, class, name, keyname, server, keycount);
+                     else
+                       new_status = STAT_INSECURE;
+                   }
+               }
+             
              if (new_status == STAT_NEED_DS || new_status == STAT_NEED_KEY)
                goto another_tcp_key;
            }
@@ -1273,7 +1508,6 @@ static int tcp_key_recurse(time_t now, int status, struct dns_header *header, si
 
       free(packet);
     }
-  
   return new_status;
 }
 #endif
index 2d860d55636bc59db5f08920e78e3cc98a60c968..b8982319955da87f754bfaa4cf5a2473ff134e6e 100644 (file)
@@ -143,6 +143,7 @@ struct myoption {
 #define LOPT_DNSSEC_DEBUG 331
 #define LOPT_REV_SERV     332
 #define LOPT_SERVERS_FILE 333
+#define LOPT_DNSSEC_CHECK 334
 
 #ifdef HAVE_GETOPT_LONG
 static const struct option opts[] =  
@@ -283,6 +284,7 @@ static const struct myoption opts[] =
     { "dnssec", 0, 0, LOPT_SEC_VALID },
     { "trust-anchor", 1, 0, LOPT_TRUST_ANCHOR },
     { "dnssec-debug", 0, 0, LOPT_DNSSEC_DEBUG },
+    { "dnssec-check-unsigned", 0, 0, LOPT_DNSSEC_CHECK },
 #ifdef OPTION6_PREFIX_CLASS 
     { "dhcp-prefix-class", 1, 0, LOPT_PREF_CLSS },
 #endif
@@ -438,6 +440,7 @@ static struct {
   { LOPT_SEC_VALID, OPT_DNSSEC_VALID, NULL, gettext_noop("Activate DNSSEC validation"), NULL },
   { LOPT_TRUST_ANCHOR, ARG_DUP, "<domain>,[<class>],...", gettext_noop("Specify trust anchor key digest."), NULL },
   { LOPT_DNSSEC_DEBUG, OPT_DNSSEC_DEBUG, NULL, gettext_noop("Disable upstream checking for DNSSEC debugging."), NULL },
+  { LOPT_DNSSEC_CHECK, OPT_DNSSEC_NO_SIGN, NULL, gettext_noop("Ensure answers without DNSSEC are in unsigned zones."), NULL },
 #ifdef OPTION6_PREFIX_CLASS 
   { LOPT_PREF_CLSS, ARG_DUP, "set:tag,<class>", gettext_noop("Specify DHCPv6 prefix class"), NULL },
 #endif
index 77156e49361561698fce841c8a4a3b2bce2389c3..2fd43e21aab3baebba0d1752fbb13c838919eb86 100644 (file)
@@ -927,7 +927,7 @@ int extract_addresses(struct dns_header *header, size_t qlen, char *name, time_t
   
   for (i = ntohs(header->qdcount); i != 0; i--)
     {
-      int found = 0, cname_count = 5;
+      int found = 0, cname_count = 10;
       struct crec *cpp = NULL;
       int flags = RCODE(header) == NXDOMAIN ? F_NXDOMAIN : 0;
       int secflag = secure ?  F_DNSSECOK : 0;