Diff for /embedaddon/dnsmasq/src/dnssec.c between versions 1.1.1.1 and 1.1.1.2

version 1.1.1.1, 2014/06/15 16:31:38 version 1.1.1.2, 2016/11/02 09:57:01
Line 1 Line 1
 /* dnssec.c is Copyright (c) 2012 Giovanni Bajo <rasky@develer.com>  /* dnssec.c is Copyright (c) 2012 Giovanni Bajo <rasky@develer.com>
           and Copyright (c) 2012-2014 Simon Kelley           and Copyright (c) 2012-2016 Simon Kelley
   
    This program is free software; you can redistribute it and/or modify     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     it under the terms of the GNU General Public License as published by
Line 26 Line 26
 #  include <nettle/ecc-curve.h>  #  include <nettle/ecc-curve.h>
 #endif  #endif
 #include <nettle/nettle-meta.h>  #include <nettle/nettle-meta.h>
#include <gmp.h>#include <nettle/bignum.h>
   
   /* Nettle-3.0 moved to a new API for DSA. We use a name that's defined in the new API
      to detect Nettle-3, and invoke the backwards compatibility mode. */
   #ifdef dsa_params_init
   #include <nettle/dsa-compat.h>
   #endif
   
 #define SERIAL_UNDEF  -100  #define SERIAL_UNDEF  -100
 #define SERIAL_EQ        0  #define SERIAL_EQ        0
 #define SERIAL_LT       -1  #define SERIAL_LT       -1
Line 64  static char *algo_digest_name(int algo) Line 70  static char *algo_digest_name(int algo)
     default: return NULL;      default: return NULL;
     }      }
 }  }
        
 /* http://www.iana.org/assignments/dnssec-nsec3-parameters/dnssec-nsec3-parameters.xhtml */
 static char *nsec3_digest_name(int digest)
 {
   switch (digest)
     {
     case 1: return "sha1";
     default: return NULL;
     }
 }
  
 /* Find pointer to correct hash function in nettle library */  /* Find pointer to correct hash function in nettle library */
 static const struct nettle_hash *hash_find(char *name)  static const struct nettle_hash *hash_find(char *name)
 {  {
Line 120  static int hash_init(const struct nettle_hash *hash, v Line 136  static int hash_init(const struct nettle_hash *hash, v
   return 1;    return 1;
 }  }
       
static int rsa_verify(struct blockdata *key_data, unsigned int key_len, unsigned char *sig, size_t sig_len,static int dnsmasq_rsa_verify(struct blockdata *key_data, unsigned int key_len, unsigned char *sig, size_t sig_len,
                      unsigned char *digest, int algo)                              unsigned char *digest, size_t digest_len, int algo)
 {  {
   unsigned char *p;    unsigned char *p;
   size_t exp_len;    size_t exp_len;
       
   static struct rsa_public_key *key = NULL;    static struct rsa_public_key *key = NULL;
   static mpz_t sig_mpz;    static mpz_t sig_mpz;
   
     (void)digest_len;
       
   if (key == NULL)    if (key == NULL)
     {      {
Line 172  static int rsa_verify(struct blockdata *key_data, unsi Line 190  static int rsa_verify(struct blockdata *key_data, unsi
   return 0;    return 0;
 }    }  
   
static int dsa_verify(struct blockdata *key_data, unsigned int key_len, unsigned char *sig, size_t sig_len,static int dnsmasq_dsa_verify(struct blockdata *key_data, unsigned int key_len, unsigned char *sig, size_t sig_len,
                      unsigned char *digest, int algo)                              unsigned char *digest, size_t digest_len, int algo)
 {  {
   unsigned char *p;    unsigned char *p;
   unsigned int t;    unsigned int t;
Line 181  static int dsa_verify(struct blockdata *key_data, unsi Line 199  static int dsa_verify(struct blockdata *key_data, unsi
   static struct dsa_public_key *key = NULL;    static struct dsa_public_key *key = NULL;
   static struct dsa_signature *sig_struct;    static struct dsa_signature *sig_struct;
       
     (void)digest_len;
   
   if (key == NULL)    if (key == NULL)
     {      {
       if (!(sig_struct = whine_malloc(sizeof(struct dsa_signature))) ||         if (!(sig_struct = whine_malloc(sizeof(struct dsa_signature))) || 
Line 268  static int dnsmasq_ecdsa_verify(struct blockdata *key_ Line 288  static int dnsmasq_ecdsa_verify(struct blockdata *key_
     }      }
       
   if (sig_len != 2*t || key_len != 2*t ||    if (sig_len != 2*t || key_len != 2*t ||
      (p = blockdata_retrieve(key_data, key_len, NULL)))      !(p = blockdata_retrieve(key_data, key_len, NULL)))
     return 0;      return 0;
       
   mpz_import(x, t , 1, 1, 0, 0, p);    mpz_import(x, t , 1, 1, 0, 0, p);
Line 284  static int dnsmasq_ecdsa_verify(struct blockdata *key_ Line 304  static int dnsmasq_ecdsa_verify(struct blockdata *key_
 }   } 
 #endif   #endif 
   
static int verify(struct blockdata *key_data, unsigned int key_len, unsigned char *sig, size_t sig_len,static int (*verify_func(int algo))(struct blockdata *key_data, unsigned int key_len, unsigned char *sig, size_t sig_len,
                  unsigned char *digest, size_t digest_len, int algo)                                    unsigned char *digest, size_t digest_len, int algo)
 {  {
  (void)digest_len;    
  /* Enure at runtime that we have support for this digest */
   if (!hash_find(algo_digest_name(algo)))
     return NULL;
   
   /* This switch defines which sig algorithms we support, can't introspect Nettle for that. */
   switch (algo)    switch (algo)
     {      {
     case 1: case 5: case 7: case 8: case 10:      case 1: case 5: case 7: case 8: case 10:
      return rsa_verify(key_data, key_len, sig, sig_len, digest, algo);      return dnsmasq_rsa_verify;
               
     case 3: case 6:       case 3: case 6: 
      return dsa_verify(key_data, key_len, sig, sig_len, digest, algo);      return dnsmasq_dsa_verify;
     
 #ifndef NO_NETTLE_ECC     #ifndef NO_NETTLE_ECC   
     case 13: case 14:      case 13: case 14:
      return dnsmasq_ecdsa_verify(key_data, key_len, sig, sig_len, digest, digest_len, algo);      return dnsmasq_ecdsa_verify;
 #endif  #endif
     }      }
       
  return 0;  return NULL;
 }  }
   
   static int 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)
   {
   
     int (*func)(struct blockdata *key_data, unsigned int key_len, unsigned char *sig, size_t sig_len,
                 unsigned char *digest, size_t digest_len, int algo);
     
     func = verify_func(algo);
     
     if (!func)
       return 0;
   
     return (*func)(key_data, key_len, sig, sig_len, digest, digest_len, algo);
   }
   
 /* Convert from presentation format to wire format, in place.  /* Convert from presentation format to wire format, in place.
    Also map UC -> LC.     Also map UC -> LC.
    Note that using extract_name to get presentation format     Note that using extract_name to get presentation format
Line 313  static int verify(struct blockdata *key_data, unsigned Line 352  static int verify(struct blockdata *key_data, unsigned
    thus generating names in canonical form.     thus generating names in canonical form.
    Calling to_wire followed by from_wire is almost an identity,     Calling to_wire followed by from_wire is almost an identity,
    except that the UC remains mapped to LC.      except that the UC remains mapped to LC. 
   
      Note that both /000 and '.' are allowed within labels. These get
      represented in presentation format using NAME_ESCAPE as an escape
      character. In theory, if all the characters in a name were /000 or
      '.' or NAME_ESCAPE then all would have to be escaped, so the 
      presentation format would be twice as long as the spec (1024). 
      The buffers are all delcared as 2049 (allowing for the trailing zero) 
      for this reason.
 */  */
 static int to_wire(char *name)  static int to_wire(char *name)
 {  {
  unsigned char *l, *p, term;  unsigned char *l, *p, *q, term;
   int len;    int len;
   
   for (l = (unsigned char*)name; *l != 0; l = p)    for (l = (unsigned char*)name; *l != 0; l = p)
Line 324  static int to_wire(char *name) Line 371  static int to_wire(char *name)
       for (p = l; *p != '.' && *p != 0; p++)        for (p = l; *p != '.' && *p != 0; p++)
         if (*p >= 'A' && *p <= 'Z')          if (*p >= 'A' && *p <= 'Z')
           *p = *p - 'A' + 'a';            *p = *p - 'A' + 'a';
              else if (*p == NAME_ESCAPE)
           {
             for (q = p; *q; q++)
               *q = *(q+1);
             (*p)--;
           }
       term = *p;        term = *p;
               
       if ((len = p - l) != 0)        if ((len = p - l) != 0)
Line 343  static int to_wire(char *name) Line 395  static int to_wire(char *name)
 /* Note: no compression  allowed in input. */  /* Note: no compression  allowed in input. */
 static void from_wire(char *name)  static void from_wire(char *name)
 {  {
  unsigned char *l;  unsigned char *l, *p, *last;
   int len;    int len;
  
   for (last = (unsigned char *)name; *last != 0; last += *last+1);
   
   for (l = (unsigned char *)name; *l != 0; l += len+1)    for (l = (unsigned char *)name; *l != 0; l += len+1)
     {      {
       len = *l;        len = *l;
       memmove(l, l+1, len);        memmove(l, l+1, len);
         for (p = l; p < l + len; p++)
           if (*p == '.' || *p == 0 || *p == NAME_ESCAPE)
             {
               memmove(p+1, p, 1 + last - p);
               len++;
               *p++ = NAME_ESCAPE; 
               (*p)++;
             }
           
       l[len] = '.';        l[len] = '.';
     }      }
   
Line 373  static int count_labels(char *name) Line 436  static int count_labels(char *name)
 }  }
   
 /* Implement RFC1982 wrapped compare for 32-bit numbers */  /* Implement RFC1982 wrapped compare for 32-bit numbers */
static int serial_compare_32(unsigned long s1, unsigned long s2)static int serial_compare_32(u32 s1, u32 s2)
 {  {
   if (s1 == s2)    if (s1 == s2)
     return SERIAL_EQ;      return SERIAL_EQ;
Line 387  static int serial_compare_32(unsigned long s1, unsigne Line 450  static int serial_compare_32(unsigned long s1, unsigne
   return SERIAL_UNDEF;    return SERIAL_UNDEF;
 }  }
   
/* Check whether today/now is between date_start and date_end *//* Called at startup. If the timestamp file is configured and exists, put its mtime on
static int check_date_range(unsigned long date_start, unsigned long date_end)   timestamp_time. If it doesn't exist, create it, and set the mtime to 1-1-2015.
    return -1 -> Cannot create file.
            0 -> not using timestamp, or timestamp exists and is in past.
            1 -> timestamp exists and is in future.
 */
 
 static time_t timestamp_time;
 
 int setup_timestamp(void)
 {  {
  unsigned long curtime;  struct stat statbuf;
   
   daemon->back_to_the_future = 0;
   
   if (!daemon->timestamp_file)
     return 0;
   
   if (stat(daemon->timestamp_file, &statbuf) != -1)
     {
       timestamp_time = statbuf.st_mtime;
     check_and_exit:
       if (difftime(timestamp_time, time(0)) <=  0)
         {
           /* time already OK, update timestamp, and do key checking from the start. */
           if (utime(daemon->timestamp_file, NULL) == -1)
             my_syslog(LOG_ERR, _("failed to update mtime on %s: %s"), daemon->timestamp_file, strerror(errno));
           daemon->back_to_the_future = 1;
           return 0;
         }
       return 1;
     }
   
   if (errno == ENOENT)
     {
       /* NB. for explanation of O_EXCL flag, see comment on pidfile in dnsmasq.c */ 
       int fd = open(daemon->timestamp_file, O_WRONLY | O_CREAT | O_NONBLOCK | O_EXCL, 0666);
       if (fd != -1)
         {
           struct utimbuf timbuf;
   
             close(fd);
             
             timestamp_time = timbuf.actime = timbuf.modtime = 1420070400; /* 1-1-2015 */
             if (utime(daemon->timestamp_file, &timbuf) == 0)
               goto check_and_exit;
           }
       }
   
     return -1;
   }
   
   /* Check whether today/now is between date_start and date_end */
   static int check_date_range(u32 date_start, u32 date_end)
   {
     unsigned long curtime = time(0);
    
   /* Checking timestamps may be temporarily disabled */    /* Checking timestamps may be temporarily disabled */
  if (option_bool(OPT_DNSSEC_TIME))    
   /* If the current time if _before_ the timestamp
      on our persistent timestamp file, then assume the
      time if not yet correct, and don't check the
      key timestamps. As soon as the current time is
      later then the timestamp, update the timestamp
      and start checking keys */
   if (daemon->timestamp_file)
     {
       if (daemon->back_to_the_future == 0 && difftime(timestamp_time, curtime) <= 0)
         {
           if (utime(daemon->timestamp_file, NULL) != 0)
             my_syslog(LOG_ERR, _("failed to update mtime on %s: %s"), daemon->timestamp_file, strerror(errno));
           
           daemon->back_to_the_future = 1;
           set_option_bool(OPT_DNSSEC_TIME);
           queue_event(EVENT_RELOAD); /* purge cache */
         } 
 
       if (daemon->back_to_the_future == 0)
         return 1;
     }
   else if (option_bool(OPT_DNSSEC_TIME))
     return 1;      return 1;
       
   curtime = time(0);  
     
   /* We must explicitly check against wanted values, because of SERIAL_UNDEF */    /* We must explicitly check against wanted values, because of SERIAL_UNDEF */
   return serial_compare_32(curtime, date_start) == SERIAL_GT    return serial_compare_32(curtime, date_start) == SERIAL_GT
     && serial_compare_32(curtime, date_end) == SERIAL_LT;      && serial_compare_32(curtime, date_end) == SERIAL_LT;
 }  }
   
 static u16 *get_desc(int type)  
 {  
   /* List of RRtypes which include domains in the data.  
      0 -> domain  
      integer -> no of plain bytes  
      -1 -> end  
   
      zero is not a valid RRtype, so the final entry is returned for  
      anything which needs no mangling.  
   */  
     
   static u16 rr_desc[] =   
     {   
       T_NS, 0, -1,   
       T_MD, 0, -1,  
       T_MF, 0, -1,  
       T_CNAME, 0, -1,  
       T_SOA, 0, 0, -1,  
       T_MB, 0, -1,  
       T_MG, 0, -1,  
       T_MR, 0, -1,  
       T_PTR, 0, -1,  
       T_MINFO, 0, 0, -1,  
       T_MX, 2, 0, -1,  
       T_RP, 0, 0, -1,  
       T_AFSDB, 2, 0, -1,  
       T_RT, 2, 0, -1,  
       T_SIG, 18, 0, -1,  
       T_PX, 2, 0, 0, -1,  
       T_NXT, 0, -1,  
       T_KX, 2, 0, -1,  
       T_SRV, 6, 0, -1,  
       T_DNAME, 0, -1,  
       0, -1 /* wildcard/catchall */  
     };   
     
   u16 *p = rr_desc;  
     
   while (*p != type && *p != 0)  
     while (*p++ != (u16)-1);  
   
   return p+1;  
 }  
   
 /* Return bytes of canonicalised rdata, when the return value is zero, the remaining   /* Return bytes of canonicalised rdata, when the return value is zero, the remaining 
    data, pointed to by *p, should be used raw. */     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, static int get_rdata(struct dns_header *header, size_t plen, unsigned char *end, char *buff, int bufflen,
                      unsigned char **p, u16 **desc)                       unsigned char **p, u16 **desc)
 {  {
   int d = **desc;    int d = **desc;
       
   (*desc)++;  
     
   /* No more data needs mangling */    /* No more data needs mangling */
   if (d == (u16)-1)    if (d == (u16)-1)
    return 0;    {
       /* 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;
     }
  
   (*desc)++;
       
   if (d == 0 && extract_name(header, plen, p, buff, 1, 0))    if (d == 0 && extract_name(header, plen, p, buff, 1, 0))
     /* domain-name, canonicalise */      /* domain-name, canonicalise */
Line 479  static int get_rdata(struct dns_header *header, size_t Line 581  static int get_rdata(struct dns_header *header, size_t
     }      }
 }  }
   
 static int expand_workspace(unsigned char ***wkspc, int *sz, int new)  
 {  
   unsigned char **p;  
   int new_sz = *sz;  
     
   if (new_sz > new)  
     return 1;  
   
   if (new >= 100)  
     return 0;  
   
   new_sz += 5;  
     
   if (!(p = whine_malloc((new_sz) * sizeof(unsigned char **))))  
     return 0;    
     
   if (*wkspc)  
     {  
       memcpy(p, *wkspc, *sz * sizeof(unsigned char **));  
       free(*wkspc);  
     }  
     
   *wkspc = p;  
   *sz = new_sz;  
   
   return 1;  
 }  
   
 /* Bubble sort the RRset into the canonical order.   /* Bubble sort the RRset into the canonical order. 
    Note that the byte-streams from two RRs may get unsynced: consider      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.     RRs which have two domain-names at the start and then other data.
Line 553  static void sort_rrset(struct dns_header *header, size Line 627  static void sort_rrset(struct dns_header *header, size
               if (left1 != 0)                if (left1 != 0)
                 memmove(buff1, buff1 + len1 - left1, left1);                  memmove(buff1, buff1 + len1 - left1, left1);
                               
              if ((len1 = get_rdata(header, plen, end1, buff1 + left1, &p1, &dp1)) == 0)              if ((len1 = get_rdata(header, plen, end1, buff1 + left1, (MAXDNAME * 2) - left1, &p1, &dp1)) == 0)
                 {                  {
                   quit = 1;                    quit = 1;
                   len1 = end1 - p1;                    len1 = end1 - p1;
Line 564  static void sort_rrset(struct dns_header *header, size Line 638  static void sort_rrset(struct dns_header *header, size
               if (left2 != 0)                if (left2 != 0)
                 memmove(buff2, buff2 + len2 - left2, left2);                  memmove(buff2, buff2 + len2 - left2, left2);
                               
              if ((len2 = get_rdata(header, plen, end2, buff2 + left2, &p2, &dp2)) == 0)              if ((len2 = get_rdata(header, plen, end2, buff2 + left2, (MAXDNAME *2) - left2, &p2, &dp2)) == 0)
                 {                  {
                   quit = 1;                    quit = 1;
                   len2 = end2 - p2;                    len2 = end2 - p2;
Line 593  static void sort_rrset(struct dns_header *header, size Line 667  static void sort_rrset(struct dns_header *header, size
     } while (swap);      } while (swap);
 }  }
   
/* Validate a single RRset (class, type, name) in the supplied DNS reply static unsigned char **rrset = NULL, **sigs = NULL;
   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 RRset empty. 
   STAT_BOGUS    signature is wrong, bad packet. 
   STAT_NEED_KEY need DNSKEY to complete validation (name is returned in keyname) 
   
   if key is non-NULL, use that key, which has the algo and tag given in the params of those names,/* Get pointers to RRset menbers and signature(s) for same.
   otherwise find the key in the cache.   Check signatures, and return keyname associated in keyname. */
static int explore_rrset(struct dns_header *header, size_t plen, int class, int type, 
   name is unchanged on exit. keyname is used as workspace and trashed.                         char *name, char *keyname, int *sigcnt, int *rrcnt)
*/ 
static int validate_rrset(time_t now, struct dns_header *header, size_t plen, int class,  
                          int type, char *name, char *keyname, struct blockdata *key, int keylen, int algo_in, int keytag_in) 
 {  {
  static unsigned char **rrset = NULL, **sigs = NULL;  static int rrset_sz = 0, sig_sz = 0; 
  static int rrset_sz = 0, sig_sz = 0; 
   
   unsigned char *p;    unsigned char *p;
  int rrsetidx, sigidx, res, rdlen, j, name_labels;  int rrsetidx, sigidx, j, rdlen, res;
  struct crec *crecp = NULL;  int gotkey = 0;
  int type_covered, algo, labels, orig_ttl, sig_expiration, sig_inception, key_tag; 
  u16 *rr_desc = get_desc(type); 
   
   if (!(p = skip_questions(header, plen)))    if (!(p = skip_questions(header, plen)))
     return STAT_BOGUS;      return STAT_BOGUS;
   
  name_labels = count_labels(name); /* For 4035 5.3.2 check */   /* look for RRSIGs for this RRset and get pointers to each RR in the set. */
 
  /* look for RRSIGs for this RRset and get pointers to each RR in the set. */ 
   for (rrsetidx = 0, sigidx = 0, j = ntohs(header->ancount) + ntohs(header->nscount);     for (rrsetidx = 0, sigidx = 0, j = ntohs(header->ancount) + ntohs(header->nscount); 
        j != 0; j--)          j != 0; j--) 
     {      {
       unsigned char *pstart, *pdata;        unsigned char *pstart, *pdata;
      int stype, sclass;      int stype, sclass, type_covered;
   
       pstart = p;        pstart = p;
               
Line 645  static int validate_rrset(time_t now, struct dns_heade Line 703  static int validate_rrset(time_t now, struct dns_heade
       GETSHORT(rdlen, p);        GETSHORT(rdlen, p);
               
       if (!CHECK_LEN(header, p, plen, rdlen))        if (!CHECK_LEN(header, p, plen, rdlen))
        return STAT_BOGUS;         return 0; 
               
       if (res == 1 && sclass == class)        if (res == 1 && sclass == class)
         {          {
           if (stype == type)            if (stype == type)
             {              {
               if (!expand_workspace(&rrset, &rrset_sz, rrsetidx))                if (!expand_workspace(&rrset, &rrset_sz, rrsetidx))
                return STAT_BOGUS;                 return 0; 
                               
               rrset[rrsetidx++] = pstart;                rrset[rrsetidx++] = pstart;
             }              }
Line 660  static int validate_rrset(time_t now, struct dns_heade Line 718  static int validate_rrset(time_t now, struct dns_heade
           if (stype == T_RRSIG)            if (stype == T_RRSIG)
             {              {
               if (rdlen < 18)                if (rdlen < 18)
                return STAT_BOGUS; /* bad packet */                 return 0; /* bad packet */ 
                               
               GETSHORT(type_covered, p);                GETSHORT(type_covered, p);
                 p += 16; /* algo, labels, orig_ttl, sig_expiration, sig_inception, key_tag */
                               
                 if (gotkey)
                   {
                     /* If there's more than one SIG, ensure they all have same keyname */
                     if (extract_name(header, plen, &p, keyname, 0, 0) != 1)
                       return 0;
                   }
                 else
                   {
                     gotkey = 1;
                     
                     if (!extract_name(header, plen, &p, keyname, 1, 0))
                       return 0;
                     
                     /* RFC 4035 5.3.1 says that the Signer's Name field MUST equal
                        the name of the zone containing the RRset. We can't tell that
                        for certain, but we can check that  the RRset name is equal to
                        or encloses the signers name, which should be enough to stop 
                        an attacker using signatures made with the key of an unrelated 
                        zone he controls. Note that the root key is always allowed. */
                     if (*keyname != 0)
                       {
                         char *name_start;
                         for (name_start = name; !hostname_isequal(name_start, keyname); )
                           if ((name_start = strchr(name_start, '.')))
                             name_start++; /* chop a label off and try again */
                           else
                             return 0;
                       }
                   }
                     
                 
               if (type_covered == type)                if (type_covered == type)
                 {                  {
                   if (!expand_workspace(&sigs, &sig_sz, sigidx))                    if (!expand_workspace(&sigs, &sig_sz, sigidx))
                    return STAT_BOGUS;                     return 0; 
                                       
                   sigs[sigidx++] = pdata;                    sigs[sigidx++] = pdata;
                 }                   } 
Line 677  static int validate_rrset(time_t now, struct dns_heade Line 767  static int validate_rrset(time_t now, struct dns_heade
         }          }
               
       if (!ADD_RDLEN(header, p, plen, rdlen))        if (!ADD_RDLEN(header, p, plen, rdlen))
        return STAT_BOGUS;        return 0;
     }      }
       
  /* RRset empty */  *sigcnt = sigidx;
  if (rrsetidx == 0)  *rrcnt = rrsetidx;
    return STAT_INSECURE;  
   
  /* no RRSIGs */  return 1;
  if (sigidx == 0)}
    return STAT_NO_SIG; 
 /* Validate a single RRset (class, type, name) in the supplied DNS reply 
    Return code:
    STAT_SECURE   if it validates.
    STAT_SECURE_WILDCARD if it validates and is the result of wildcard expansion.
    (In this case *wildcard_out points to the "body" of the wildcard within name.) 
    STAT_BOGUS    signature is wrong, bad packet.
    STAT_NEED_KEY need DNSKEY to complete validation (name is returned in keyname)
    STAT_NEED_DS  need DS to complete validation (name is returned in keyname)
 
    If key is non-NULL, use that key, which has the algo and tag given in the params of those names,
    otherwise find the key in the cache.
 
    Name is unchanged on exit. keyname is used as workspace and trashed.
 
    Call explore_rrset first to find and count RRs and sigs.
 */
 static int validate_rrset(time_t now, struct dns_header *header, size_t plen, int class, int type, int sigidx, int rrsetidx, 
                           char *name, char *keyname, char **wildcard_out, struct blockdata *key, int keylen, int algo_in, int keytag_in)
 {
   unsigned char *p;
   int rdlen, j, name_labels, algo, labels, orig_ttl, key_tag;
   struct crec *crecp = NULL;
   u16 *rr_desc = rrfilter_desc(type);
   u32 sig_expiration, sig_inception
 ;
   if (wildcard_out)
     *wildcard_out = NULL;
       
     name_labels = count_labels(name); /* For 4035 5.3.2 check */
   
   /* Sort RRset records into canonical order.     /* Sort RRset records into canonical order. 
      Note that at this point keyname and daemon->workspacename buffs are       Note that at this point keyname and daemon->workspacename buffs are
      unused, and used as workspace by the sort. */       unused, and used as workspace by the sort. */
Line 718  static int validate_rrset(time_t now, struct dns_heade Line 836  static int validate_rrset(time_t now, struct dns_heade
       if (!extract_name(header, plen, &p, keyname, 1, 0))        if (!extract_name(header, plen, &p, keyname, 1, 0))
         return STAT_BOGUS;          return STAT_BOGUS;
   
       /* RFC 4035 5.3.1 says that the Signer's Name field MUST equal  
          the name of the zone containing the RRset. We can't tell that  
          for certain, but we can check that  the RRset name is equal to  
          or encloses the signers name, which should be enough to stop   
          an attacker using signatures made with the key of an unrelated   
          zone he controls. Note that the root key is always allowed. */  
       if (*keyname != 0)  
         {  
           int failed = 0;  
             
           for (name_start = name; !hostname_isequal(name_start, keyname); )  
             if ((name_start = strchr(name_start, '.')))  
               name_start++; /* chop a label off and try again */  
             else  
               {  
                 failed = 1;  
                 break;  
               }  
   
           /* Bad sig, try another */  
           if (failed)  
             continue;  
         }  
         
       /* Other 5.3.1 checks */  
       if (!check_date_range(sig_inception, sig_expiration) ||        if (!check_date_range(sig_inception, sig_expiration) ||
           labels > name_labels ||            labels > name_labels ||
           !(hash = hash_find(algo_digest_name(algo))) ||            !(hash = hash_find(algo_digest_name(algo))) ||
           !hash_init(hash, &ctx, &digest))            !hash_init(hash, &ctx, &digest))
         continue;          continue;
              
       /* OK, we have the signature record, see if the relevant DNSKEY is in the cache. */        /* OK, we have the signature record, see if the relevant DNSKEY is in the cache. */
       if (!key && !(crecp = cache_find_by_name(NULL, keyname, now, F_DNSKEY)))        if (!key && !(crecp = cache_find_by_name(NULL, keyname, now, F_DNSKEY)))
         return STAT_NEED_KEY;          return STAT_NEED_KEY;
Line 780  static int validate_rrset(time_t now, struct dns_heade Line 873  static int validate_rrset(time_t now, struct dns_heade
             {              {
               int k;                int k;
               for (k = name_labels - labels; k != 0; k--)                for (k = name_labels - labels; k != 0; k--)
                while (*name_start != '.' && *name_start != 0)                {
                  name_start++;                  while (*name_start != '.' && *name_start != 0)
                     name_start++;
                   if (k != 1 && *name_start == '.')
                     name_start++;
                 }
               
               if (wildcard_out)
                 *wildcard_out = name_start+1;
 
               name_start--;                name_start--;
               *name_start = '*';                *name_start = '*';
             }              }
Line 798  static int validate_rrset(time_t now, struct dns_heade Line 899  static int validate_rrset(time_t now, struct dns_heade
                       
           end = p + rdlen;            end = p + rdlen;
                       
          /* canonicalise rdata and calculate length of same, use name buffer as workspace */          /* 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;            cp = p;
           dp = rr_desc;            dp = rr_desc;
          for (len = 0; (seg = get_rdata(header, plen, end, name, &cp, &dp)) != 0; len += seg);          for (len = 0; (seg = get_rdata(header, plen, end, name, MAXDNAME * 2, &cp, &dp)) != 0; len += seg);
           len += end - cp;            len += end - cp;
           len = htons(len);            len = htons(len);
           hash->update(ctx, 2, (unsigned char *)&len);             hash->update(ctx, 2, (unsigned char *)&len); 
Line 809  static int validate_rrset(time_t now, struct dns_heade Line 911  static int validate_rrset(time_t now, struct dns_heade
           /* Now canonicalise again and digest. */            /* Now canonicalise again and digest. */
           cp = p;            cp = p;
           dp = rr_desc;            dp = rr_desc;
          while ((seg = get_rdata(header, plen, end, name, &cp, &dp)))          while ((seg = get_rdata(header, plen, end, name, MAXDNAME * 2, &cp, &dp)))
             hash->update(ctx, seg, (unsigned char *)name);              hash->update(ctx, seg, (unsigned char *)name);
           if (cp != end)            if (cp != end)
             hash->update(ctx, end - cp, cp);              hash->update(ctx, end - cp, cp);
Line 842  static int validate_rrset(time_t now, struct dns_heade Line 944  static int validate_rrset(time_t now, struct dns_heade
   return STAT_BOGUS;    return STAT_BOGUS;
 }  }
     
   
 /* The DNS packet is expected to contain the answer to a DNSKEY query.  /* The DNS packet is expected to contain the answer to a DNSKEY query.
    Put all DNSKEYs in the answer which are valid into the cache.     Put all DNSKEYs in the answer which are valid into the cache.
    return codes:     return codes:
         STAT_INSECURE No DNSKEYs in reply.         STAT_OK        Done, key(s) in cache.
         STAT_SECURE   At least one valid DNSKEY found and in cache.         STAT_BOGUS     No DNSKEYs found, which  can be validated with DS,
         STAT_BOGUS    No DNSKEYs found, which  can be validated with DS,                        or self-sign for DNSKEY RRset is not valid, bad packet.
                       or self-sign for DNSKEY RRset is not valid, bad packet.         STAT_NEED_DS   DS records to validate a key not found, name in keyname 
         STAT_NEED_DS  DS records to validate a key not found, name in keyname          STAT_NEED_KEY  DNSKEY records to validate a key not found, name in keyname 
 */  */
 int dnssec_validate_by_ds(time_t now, struct dns_header *header, size_t plen, char *name, char *keyname, int class)  int dnssec_validate_by_ds(time_t now, struct dns_header *header, size_t plen, char *name, char *keyname, int class)
 {  {
   unsigned char *psave, *p = (unsigned char *)(header+1);    unsigned char *psave, *p = (unsigned char *)(header+1);
   struct crec *crecp, *recp1;    struct crec *crecp, *recp1;
  int rc, j, qtype, qclass, ttl, rdlen, flags, algo, valid, keytag, type_covered;  int rc, j, qtype, qclass, ttl, rdlen, flags, algo, valid, keytag;
   struct blockdata *key;    struct blockdata *key;
   struct all_addr a;    struct all_addr a;
   
Line 866  int dnssec_validate_by_ds(time_t now, struct dns_heade Line 969  int dnssec_validate_by_ds(time_t now, struct dns_heade
   GETSHORT(qtype, p);    GETSHORT(qtype, p);
   GETSHORT(qclass, p);    GETSHORT(qclass, p);
       
  if (qtype != T_DNSKEY || qclass != class)  if (qtype != T_DNSKEY || qclass != class || ntohs(header->ancount) == 0)
     return STAT_BOGUS;      return STAT_BOGUS;
     
   if (ntohs(header->ancount) == 0)  
     return STAT_INSECURE;  
   
   /* See if we have cached a DS record which validates this key */    /* See if we have cached a DS record which validates this key */
   if (!(crecp = cache_find_by_name(NULL, name, now, F_DS)))    if (!(crecp = cache_find_by_name(NULL, name, now, F_DS)))
Line 879  int dnssec_validate_by_ds(time_t now, struct dns_heade Line 979  int dnssec_validate_by_ds(time_t now, struct dns_heade
       return STAT_NEED_DS;        return STAT_NEED_DS;
     }      }
       
   /* If we've cached that DS provably doesn't exist, result must be INSECURE */  
   if (crecp->flags & F_NEG)  
     return STAT_INSECURE;  
     
   /* NOTE, we need to find ONE DNSKEY which matches the DS */    /* NOTE, we need to find ONE DNSKEY which matches the DS */
   for (valid = 0, j = ntohs(header->ancount); j != 0 && !valid; j--)     for (valid = 0, j = ntohs(header->ancount); j != 0 && !valid; j--) 
     {      {
Line 935  int dnssec_validate_by_ds(time_t now, struct dns_heade Line 1031  int dnssec_validate_by_ds(time_t now, struct dns_heade
           void *ctx;            void *ctx;
           unsigned char *digest, *ds_digest;            unsigned char *digest, *ds_digest;
           const struct nettle_hash *hash;            const struct nettle_hash *hash;
                    int sigcnt, rrcnt;
 
           if (recp1->addr.ds.algo == algo &&             if (recp1->addr.ds.algo == algo && 
               recp1->addr.ds.keytag == keytag &&                recp1->addr.ds.keytag == keytag &&
               recp1->uid == (unsigned int)class &&                recp1->uid == (unsigned int)class &&
Line 953  int dnssec_validate_by_ds(time_t now, struct dns_heade Line 1050  int dnssec_validate_by_ds(time_t now, struct dns_heade
                               
               from_wire(name);                from_wire(name);
                               
              if (recp1->addr.ds.keylen == (int)hash->digest_size &&              if (!(recp1->flags & F_NEG) &&
                   recp1->addr.ds.keylen == (int)hash->digest_size &&
                   (ds_digest = blockdata_retrieve(recp1->addr.key.keydata, recp1->addr.ds.keylen, NULL)) &&                    (ds_digest = blockdata_retrieve(recp1->addr.key.keydata, recp1->addr.ds.keylen, NULL)) &&
                   memcmp(ds_digest, digest, recp1->addr.ds.keylen) == 0 &&                    memcmp(ds_digest, digest, recp1->addr.ds.keylen) == 0 &&
                  validate_rrset(now, header, plen, class, T_DNSKEY, name, keyname, key, rdlen - 4, algo, keytag) == STAT_SECURE)                  explore_rrset(header, plen, class, T_DNSKEY, name, keyname, &sigcnt, &rrcnt) &&
                   sigcnt != 0 && rrcnt != 0 &&
                   validate_rrset(now, header, plen, class, T_DNSKEY, sigcnt, rrcnt, name, keyname
                                  NULL, key, rdlen - 4, algo, keytag) == STAT_SECURE)
                 {                  {
                   valid = 1;                    valid = 1;
                   break;                    break;
Line 968  int dnssec_validate_by_ds(time_t now, struct dns_heade Line 1069  int dnssec_validate_by_ds(time_t now, struct dns_heade
   
   if (valid)    if (valid)
     {      {
      /* DNSKEY RRset determined to be OK, now cache it and the RRsigs that sign it. */      /* DNSKEY RRset determined to be OK, now cache it. */
       cache_start_insert();        cache_start_insert();
               
       p = skip_questions(header, plen);        p = skip_questions(header, plen);
Line 977  int dnssec_validate_by_ds(time_t now, struct dns_heade Line 1078  int dnssec_validate_by_ds(time_t now, struct dns_heade
         {          {
           /* Ensure we have type, class  TTL and length */            /* Ensure we have type, class  TTL and length */
           if (!(rc = extract_name(header, plen, &p, name, 0, 10)))            if (!(rc = extract_name(header, plen, &p, name, 0, 10)))
            return STAT_INSECURE; /* bad packet */            return STAT_BOGUS; /* bad packet */
                       
           GETSHORT(qtype, p);             GETSHORT(qtype, p); 
           GETSHORT(qclass, p);            GETSHORT(qclass, p);
Line 1008  int dnssec_validate_by_ds(time_t now, struct dns_heade Line 1109  int dnssec_validate_by_ds(time_t now, struct dns_heade
                   if ((key = blockdata_alloc((char*)p, rdlen - 4)))                    if ((key = blockdata_alloc((char*)p, rdlen - 4)))
                     {                      {
                       if (!(recp1 = cache_insert(name, &a, now, ttl, F_FORWARD | F_DNSKEY | F_DNSSECOK)))                        if (!(recp1 = cache_insert(name, &a, now, ttl, F_FORWARD | F_DNSKEY | F_DNSSECOK)))
                        blockdata_free(key);                        {
                           blockdata_free(key);
                           return STAT_BOGUS;
                         }
                       else                        else
                         {                          {
                          a.addr.keytag = keytag;                          a.addr.log.keytag = keytag;
                          log_query(F_KEYTAG | F_UPSTREAM, name, &a, "DNSKEY keytag %u");                          a.addr.log.algo = algo;
                           if (verify_func(algo))
                             log_query(F_NOEXTRA | F_KEYTAG | F_UPSTREAM, name, &a, "DNSKEY keytag %hu, algo %hu");
                           else
                             log_query(F_NOEXTRA | F_KEYTAG | F_UPSTREAM, name, &a, "DNSKEY keytag %hu, algo %hu (not supported)");
                                                       
                           recp1->addr.key.keylen = rdlen - 4;                            recp1->addr.key.keylen = rdlen - 4;
                           recp1->addr.key.keydata = key;                            recp1->addr.key.keydata = key;
Line 1022  int dnssec_validate_by_ds(time_t now, struct dns_heade Line 1130  int dnssec_validate_by_ds(time_t now, struct dns_heade
                         }                          }
                     }                      }
                 }                  }
              else if (qtype == T_RRSIG)                      
                { 
                  /* RRSIG, cache if covers DNSKEY RRset */ 
                  if (rdlen < 18) 
                    return STAT_BOGUS; /* bad packet */ 
                   
                  GETSHORT(type_covered, p); 
                   
                  if (type_covered == T_DNSKEY) 
                    { 
                      a.addr.dnssec.class = class; 
                      a.addr.dnssec.type = type_covered; 
                       
                      algo = *p++; 
                      p += 13; /* labels, orig_ttl, expiration, inception */ 
                      GETSHORT(keytag, p);       
                      if ((key = blockdata_alloc((char*)psave, rdlen))) 
                        { 
                          if (!(crecp = cache_insert(name, &a, now, ttl,  F_FORWARD | F_DNSKEY | F_DS))) 
                            blockdata_free(key); 
                          else 
                            { 
                              crecp->addr.sig.keydata = key; 
                              crecp->addr.sig.keylen = rdlen; 
                              crecp->addr.sig.keytag = keytag; 
                              crecp->addr.sig.type_covered = type_covered; 
                              crecp->addr.sig.algo = algo; 
                            } 
                        } 
                    } 
                } 
               
               p = psave;                p = psave;
             }              }
   
Line 1063  int dnssec_validate_by_ds(time_t now, struct dns_heade Line 1140  int dnssec_validate_by_ds(time_t now, struct dns_heade
               
       /* commit cache insert. */        /* commit cache insert. */
       cache_end_insert();        cache_end_insert();
      return STAT_SECURE;      return STAT_OK;
     }      }
   
  log_query(F_UPSTREAM, name, NULL, "BOGUS DNSKEY");  log_query(F_NOEXTRA | F_UPSTREAM, name, NULL, "BOGUS DNSKEY");
   return STAT_BOGUS;    return STAT_BOGUS;
 }  }
   
 /* The DNS packet is expected to contain the answer to a DS query  /* The DNS packet is expected to contain the answer to a DS query
    Put all DSs in the answer which are valid into the cache.     Put all DSs in the answer which are valid into the cache.
      Also handles replies which prove that there's no DS at this location, 
      either because the zone is unsigned or this isn't a zone cut. These are
      cached too.
    return codes:     return codes:
   STAT_INSECURE    no DS in reply or not signed.   STAT_OK          At least one valid DS found and in cache.
   STAT_SECURE      At least one valid DS found and in cache.   STAT_BOGUS       no DS in reply or not signed, fails validation, bad packet.
   STAT_NO_DS       It's proved there's no DS here.   STAT_NEED_KEY    DNSKEY records to validate a DS not found, name in keyname
   STAT_BOGUS       At least one DS found, which fails validation, bad packet.   STAT_NEED_DS     DS record needed.
   STAT_NEED_DNSKEY DNSKEY records to validate a DS not found, name in keyname 
 */  */
   
 int dnssec_validate_ds(time_t now, struct dns_header *header, size_t plen, 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)
 {  {
   unsigned char *p = (unsigned char *)(header+1);    unsigned char *p = (unsigned char *)(header+1);
  int qtype, qclass, val, i, neganswer;  int qtype, qclass, rc, i, neganswer, nons;
   int aclass, atype, rdlen;
   unsigned long ttl;
   struct all_addr a;
   
   if (ntohs(header->qdcount) != 1 ||    if (ntohs(header->qdcount) != 1 ||
       !(p = skip_name(p, header, plen, 4)))        !(p = skip_name(p, header, plen, 4)))
Line 1093  int dnssec_validate_ds(time_t now, struct dns_header * Line 1175  int dnssec_validate_ds(time_t now, struct dns_header *
   GETSHORT(qclass, p);    GETSHORT(qclass, p);
   
   if (qtype != T_DS || qclass != class)    if (qtype != T_DS || qclass != class)
    val = STAT_BOGUS;    rc = STAT_BOGUS;
   else    else
    val = dnssec_validate_reply(now, header, plen, name, keyname, NULL, &neganswer);    rc = dnssec_validate_reply(now, header, plen, name, keyname, NULL, 0, &neganswer, &nons);
  /* Note dnssec_validate_reply() will have cached positive answers */
  if (val == STAT_NO_SIG) 
    val = STAT_INSECURE; 
       
     if (rc == STAT_INSECURE)
       rc = STAT_BOGUS;
    
   p = (unsigned char *)(header+1);    p = (unsigned char *)(header+1);
   extract_name(header, plen, &p, name, 1, 4);    extract_name(header, plen, &p, name, 1, 4);
   p += 4; /* qtype, qclass */    p += 4; /* qtype, qclass */
       
  if (!(p = skip_section(p, ntohs(header->ancount), header, plen)))  /* If the key needed to validate the DS is on the same domain as the DS, we'll
    return STAT_BOGUS;     loop getting nowhere. Stop that now. This can happen of the DS answer comes
      from the DS's zone, and not the parent zone. */
   if (rc == STAT_BOGUS || (rc == STAT_NEED_KEY && hostname_isequal(name, keyname)))
     {
       log_query(F_NOEXTRA | F_UPSTREAM, name, NULL, "BOGUS DS");
       return STAT_BOGUS;
     }
       
  if (val == STAT_BOGUS)  if (rc != STAT_SECURE)
    log_query(F_UPSTREAM, name, NULL, "BOGUS DS");    return rc;
     
  if ((val == STAT_SECURE || val == STAT_INSECURE) && neganswer)  if (!neganswer)
     {      {
      int rdlen, flags = F_FORWARD | F_DS | F_NEG;      cache_start_insert();
      unsigned long ttl, minttl = ULONG_MAX;      
      struct all_addr a;      for (i = 0; i < ntohs(header->ancount); i++)
         {
           if (!(rc = extract_name(header, plen, &p, name, 0, 10)))
             return STAT_BOGUS; /* bad packet */
           
           GETSHORT(atype, p);
           GETSHORT(aclass, p);
           GETLONG(ttl, p);
           GETSHORT(rdlen, p);
           
           if (!CHECK_LEN(header, p, plen, rdlen))
             return STAT_BOGUS; /* bad packet */
           
           if (aclass == class && atype == T_DS && rc == 1)
             { 
               int algo, digest, keytag;
               unsigned char *psave = p;
               struct blockdata *key;
               struct crec *crecp;
   
                 if (rdlen < 4)
                   return STAT_BOGUS; /* bad packet */
                 
                 GETSHORT(keytag, p);
                 algo = *p++;
                 digest = *p++;
                 
                 /* Cache needs to known class for DNSSEC stuff */
                 a.addr.dnssec.class = class;
                 
                 if ((key = blockdata_alloc((char*)p, rdlen - 4)))
                   {
                     if (!(crecp = cache_insert(name, &a, now, ttl, F_FORWARD | F_DS | F_DNSSECOK)))
                       {
                         blockdata_free(key);
                         return STAT_BOGUS;
                       }
                     else
                       {
                         a.addr.log.keytag = keytag;
                         a.addr.log.algo = algo;
                         a.addr.log.digest = digest;
                         if (hash_find(ds_digest_name(digest)) && verify_func(algo))
                           log_query(F_NOEXTRA | F_KEYTAG | F_UPSTREAM, name, &a, "DS keytag %hu, algo %hu, digest %hu");
                         else
                           log_query(F_NOEXTRA | F_KEYTAG | F_UPSTREAM, name, &a, "DS keytag %hu, algo %hu, digest %hu (not supported)");
                         
                         crecp->addr.ds.digest = digest;
                         crecp->addr.ds.keydata = key;
                         crecp->addr.ds.algo = algo;
                         crecp->addr.ds.keytag = keytag;
                         crecp->addr.ds.keylen = rdlen - 4; 
                       } 
                   }
                 
                 p = psave;
               }
             if (!ADD_RDLEN(header, p, plen, rdlen))
               return STAT_BOGUS; /* bad packet */
           }
   
         cache_end_insert();
   
       }
     else
       {
         int flags = F_FORWARD | F_DS | F_NEG | F_DNSSECOK;
         unsigned long minttl = ULONG_MAX;
         
         if (!(p = skip_section(p, ntohs(header->ancount), header, plen)))
           return STAT_BOGUS;
         
       if (RCODE(header) == NXDOMAIN)        if (RCODE(header) == NXDOMAIN)
         flags |= F_NXDOMAIN;          flags |= F_NXDOMAIN;
               
      if (val == STAT_SECURE)      /* We only cache validated DS records, DNSSECOK flag hijacked 
        flags |= F_DNSSECOK;         to store presence/absence of NS. */
       if (nons)
         flags &= ~F_DNSSECOK;
               
       for (i = ntohs(header->nscount); i != 0; i--)        for (i = ntohs(header->nscount); i != 0; i--)
         {          {
           if (!(p = skip_name(p, header, plen, 0)))            if (!(p = skip_name(p, header, plen, 0)))
             return STAT_BOGUS;              return STAT_BOGUS;
                       
          GETSHORT(qtype, p);           GETSHORT(atype, p); 
          GETSHORT(qclass, p);          GETSHORT(aclass, p);
           GETLONG(ttl, p);            GETLONG(ttl, p);
           GETSHORT(rdlen, p);            GETSHORT(rdlen, p);
          
           if (!CHECK_LEN(header, p, plen, rdlen))            if (!CHECK_LEN(header, p, plen, rdlen))
             return STAT_BOGUS; /* bad packet */              return STAT_BOGUS; /* bad packet */
                      
          if (qclass != class || qtype != T_SOA)          if (aclass != class || atype != T_SOA)
             {              {
               p += rdlen;                p += rdlen;
               continue;                continue;
             }              }
                     
           if (ttl < minttl)            if (ttl < minttl)
             minttl = ttl;              minttl = ttl;
                       
Line 1164  int dnssec_validate_ds(time_t now, struct dns_header * Line 1325  int dnssec_validate_ds(time_t now, struct dns_header *
           cache_start_insert();            cache_start_insert();
                       
           a.addr.dnssec.class = class;            a.addr.dnssec.class = class;
          cache_insert(name, &a, now, ttl, flags);          if (!cache_insert(name, &a, now, ttl, flags))
             return STAT_BOGUS;
                       
          cache_end_insert();           cache_end_insert();  
           
           log_query(F_NOEXTRA | F_UPSTREAM, name, NULL, "no DS");
         }          }
   
       return (val == STAT_SECURE) ? STAT_NO_DS : STAT_INSECURE;   
     }      }
      
  return val;  return STAT_OK;
 }  }
   
   
 /* 4034 6.1 */  /* 4034 6.1 */
 static int hostname_cmp(const char *a, const char *b)  static int hostname_cmp(const char *a, const char *b)
 {  {
Line 1234  static int hostname_cmp(const char *a, const char *b) Line 1397  static int hostname_cmp(const char *a, const char *b)
       if (sb == b)        if (sb == b)
         return 1;          return 1;
               
      ea = sa--;      ea = --sa;
      eb = sb--;      eb = --sb;
     }      }
 }  }
   
 /* Find all the NSEC or NSEC3 records in a reply.  
    return an array of pointers to them. */  
 static int find_nsec_records(struct dns_header *header, size_t plen, unsigned char ***nsecsetp, int *nsecsetl, int class_reqd)  
 {  
   static unsigned char **nsecset = NULL;  
   static int nsecset_sz = 0;  
     
   int type_found = 0;  
   unsigned char *p = skip_questions(header, plen);  
   int type, class, rdlen, i, nsecs_found;  
   
   /* Move to NS section */  
   if (!p || !(p = skip_section(p, ntohs(header->ancount), header, plen)))  
     return 0;  
     
   for (nsecs_found = 0, i = ntohs(header->nscount); i != 0; i--)  
     {  
       unsigned char *pstart = p;  
         
       if (!(p = skip_name(p, header, plen, 10)))  
         return 0;  
         
       GETSHORT(type, p);   
       GETSHORT(class, p);  
       p += 4; /* TTL */  
       GETSHORT(rdlen, p);  
   
       if (class == class_reqd && (type == T_NSEC || type == T_NSEC3))  
         {  
           /* No mixed NSECing 'round here, thankyouverymuch */  
           if (type_found == T_NSEC && type == T_NSEC3)  
             return 0;  
           if (type_found == T_NSEC3 && type == T_NSEC)  
             return 0;  
   
           type_found = type;  
   
           if (!expand_workspace(&nsecset, &nsecset_sz, nsecs_found))  
             return 0;   
             
           nsecset[nsecs_found++] = pstart;  
         }  
         
       if (!ADD_RDLEN(header, p, plen, rdlen))  
         return 0;  
     }  
     
   *nsecsetp = nsecset;  
   *nsecsetl = nsecs_found;  
     
   return type_found;  
 }  
   
 static int prove_non_existence_nsec(struct dns_header *header, size_t plen, unsigned char **nsecs, int nsec_count,  static int prove_non_existence_nsec(struct dns_header *header, size_t plen, unsigned char **nsecs, int nsec_count,
                                    char *workspace1, char *workspace2, char *name, int type)                                    char *workspace1, char *workspace2, char *name, int type, int *nons)
 {  {
   int i, rc, rdlen;    int i, rc, rdlen;
   unsigned char *p, *psave;    unsigned char *p, *psave;
   int offset = (type & 0xff) >> 3;    int offset = (type & 0xff) >> 3;
   int mask = 0x80 >> (type & 0x07);    int mask = 0x80 >> (type & 0x07);
   
     if (nons)
       *nons = 1;
       
   /* Find NSEC record that proves name doesn't exist */    /* Find NSEC record that proves name doesn't exist */
   for (i = 0; i < nsec_count; i++)    for (i = 0; i < nsec_count; i++)
     {      {
       p = nsecs[i];        p = nsecs[i];
       if (!extract_name(header, plen, &p, workspace1, 1, 10))        if (!extract_name(header, plen, &p, workspace1, 1, 10))
        return STAT_BOGUS;        return 0;
       p += 8; /* class, type, TTL */        p += 8; /* class, type, TTL */
       GETSHORT(rdlen, p);        GETSHORT(rdlen, p);
       psave = p;        psave = p;
       if (!extract_name(header, plen, &p, workspace2, 1, 10))        if (!extract_name(header, plen, &p, workspace2, 1, 10))
        return STAT_BOGUS;        return 0;
               
       rc = hostname_cmp(workspace1, name);        rc = hostname_cmp(workspace1, name);
               
Line 1318  static int prove_non_existence_nsec(struct dns_header  Line 1431  static int prove_non_existence_nsec(struct dns_header 
         {          {
           /* 4035 para 5.4. Last sentence */            /* 4035 para 5.4. Last sentence */
           if (type == T_NSEC || type == T_RRSIG)            if (type == T_NSEC || type == T_RRSIG)
            return STAT_SECURE;            return 1;
   
           /* NSEC with the same name as the RR we're testing, check            /* NSEC with the same name as the RR we're testing, check
              that the type in question doesn't appear in the type map */               that the type in question doesn't appear in the type map */
           rdlen -= p - psave;            rdlen -= p - psave;
           /* rdlen is now length of type map, and p points to it */            /* rdlen is now length of type map, and p points to it */
                       
             /* If we can prove that there's no NS record, return that information. */
             if (nons && rdlen >= 2 && p[0] == 0 && (p[2] & (0x80 >> T_NS)) != 0)
               *nons = 0;
             
             if (rdlen >= 2 && p[0] == 0)
               {
                 /* A CNAME answer would also be valid, so if there's a CNAME is should 
                    have been returned. */
                 if ((p[2] & (0x80 >> T_CNAME)) != 0)
                   return 0;
                 
                 /* If the SOA bit is set for a DS record, then we have the
                    DS from the wrong side of the delegation. */
                 if (type == T_DS && (p[2] & (0x80 >> T_SOA)) != 0)
                   return 0;
               }
   
           while (rdlen >= 2)            while (rdlen >= 2)
             {              {
               if (!CHECK_LEN(header, p, plen, rdlen))                if (!CHECK_LEN(header, p, plen, rdlen))
                return STAT_BOGUS;                return 0;
                               
               if (p[0] == type >> 8)                if (p[0] == type >> 8)
                 {                  {
                   /* Does the NSEC say our type exists? */                    /* Does the NSEC say our type exists? */
                   if (offset < p[1] && (p[offset+2] & mask) != 0)                    if (offset < p[1] && (p[offset+2] & mask) != 0)
                    return STAT_BOGUS;                    return 0;
                                       
                   break; /* finshed checking */                    break; /* finshed checking */
                 }                  }
Line 1343  static int prove_non_existence_nsec(struct dns_header  Line 1473  static int prove_non_existence_nsec(struct dns_header 
               p +=  p[1];                p +=  p[1];
             }              }
                       
          return STAT_SECURE;          return 1;
         }          }
       else if (rc == -1)        else if (rc == -1)
         {          {
           /* Normal case, name falls between NSEC name and next domain name,            /* Normal case, name falls between NSEC name and next domain name,
              wrap around case, name falls between NSEC name (rc == -1) and end */               wrap around case, name falls between NSEC name (rc == -1) and end */
          if (hostname_cmp(workspace2, name) == 1 || hostname_cmp(workspace1, workspace2) == 1)          if (hostname_cmp(workspace2, name) >= 0 || hostname_cmp(workspace1, workspace2) >= 0)
            return STAT_SECURE;            return 1;
         }          }
       else         else 
         {          {
           /* wrap around case, name falls between start and next domain name */            /* wrap around case, name falls between start and next domain name */
          if (hostname_cmp(workspace1, workspace2) == 1 && hostname_cmp(workspace2, name) == 1)          if (hostname_cmp(workspace1, workspace2) >= 0 && hostname_cmp(workspace2, name) >=0 )
            return STAT_SECURE;            return 1;
         }          }
     }      }
       
  return STAT_BOGUS;  return 0;
 }  }
   
 /* return digest length, or zero on error */  /* return digest length, or zero on error */
Line 1425  static int base32_decode(char *in, unsigned char *out) Line 1555  static int base32_decode(char *in, unsigned char *out)
   return p - out;    return p - out;
 }  }
   
   static int check_nsec3_coverage(struct dns_header *header, size_t plen, int digest_len, unsigned char *digest, int type,
                                   char *workspace1, char *workspace2, unsigned char **nsecs, int nsec_count, int *nons)
   {
     int i, hash_len, salt_len, base32_len, rdlen, flags;
     unsigned char *p, *psave;
   
     for (i = 0; i < nsec_count; i++)
       if ((p = nsecs[i]))
         {
           if (!extract_name(header, plen, &p, workspace1, 1, 0) ||
               !(base32_len = base32_decode(workspace1, (unsigned char *)workspace2)))
             return 0;
           
           p += 8; /* class, type, TTL */
           GETSHORT(rdlen, p);
           psave = p;
           p++; /* algo */
           flags = *p++; /* flags */
           p += 2; /* iterations */
           salt_len = *p++; /* salt_len */
           p += salt_len; /* salt */
           hash_len = *p++; /* p now points to next hashed name */
           
           if (!CHECK_LEN(header, p, plen, hash_len))
             return 0;
           
           if (digest_len == base32_len && hash_len == base32_len)
             {
               int rc = memcmp(workspace2, digest, digest_len);
   
               if (rc == 0)
                 {
                   /* We found an NSEC3 whose hashed name exactly matches the query, so
                      we just need to check the type map. p points to the RR data for the record. */
                   
                   int offset = (type & 0xff) >> 3;
                   int mask = 0x80 >> (type & 0x07);
                   
                   p += hash_len; /* skip next-domain hash */
                   rdlen -= p - psave;
   
                   if (!CHECK_LEN(header, p, plen, rdlen))
                     return 0;
                   
                   if (rdlen >= 2 && p[0] == 0)
                     {
                       /* If we can prove that there's no NS record, return that information. */
                       if (nons && (p[2] & (0x80 >> T_NS)) != 0)
                         *nons = 0;
                   
                       /* A CNAME answer would also be valid, so if there's a CNAME is should 
                          have been returned. */
                       if ((p[2] & (0x80 >> T_CNAME)) != 0)
                         return 0;
                       
                       /* If the SOA bit is set for a DS record, then we have the
                          DS from the wrong side of the delegation. */
                       if (type == T_DS && (p[2] & (0x80 >> T_SOA)) != 0)
                         return 0;
                     }
   
                   while (rdlen >= 2)
                     {
                       if (p[0] == type >> 8)
                         {
                           /* Does the NSEC3 say our type exists? */
                           if (offset < p[1] && (p[offset+2] & mask) != 0)
                             return 0;
                           
                           break; /* finshed checking */
                         }
                       
                       rdlen -= p[1];
                       p +=  p[1];
                     }
                   
                   return 1;
                 }
               else if (rc < 0)
                 {
                   /* Normal case, hash falls between NSEC3 name-hash and next domain name-hash,
                      wrap around case, name-hash falls between NSEC3 name-hash and end */
                   if (memcmp(p, digest, digest_len) >= 0 || memcmp(workspace2, p, digest_len) >= 0)
                     {
                       if ((flags & 0x01) && nons) /* opt out */
                         *nons = 0;
   
                       return 1;
                     }
                 }
               else 
                 {
                   /* wrap around case, name falls between start and next domain name */
                   if (memcmp(workspace2, p, digest_len) >= 0 && memcmp(p, digest, digest_len) >= 0)
                     {
                       if ((flags & 0x01) && nons) /* opt out */
                         *nons = 0;
   
                       return 1;
                     }
                 }
             }
         }
   
     return 0;
   }
   
 static int prove_non_existence_nsec3(struct dns_header *header, size_t plen, unsigned char **nsecs, int nsec_count,  static int prove_non_existence_nsec3(struct dns_header *header, size_t plen, unsigned char **nsecs, int nsec_count,
                                     char *workspace1, char *workspace2, char *name, int type)                                     char *workspace1, char *workspace2, char *name, int type, char *wildname, int *nons)
 {  {
   unsigned char *salt, *p, *digest;    unsigned char *salt, *p, *digest;
  int digest_len, i, iterations, salt_len, hash_len, base32_len, algo = 0;  int digest_len, i, iterations, salt_len, base32_len, algo = 0;
   struct nettle_hash const *hash;    struct nettle_hash const *hash;
   char *closest_encloser, *next_closest, *wildcard;    char *closest_encloser, *next_closest, *wildcard;
   
   if (nons)
     *nons = 1;
   
   /* Look though the NSEC3 records to find the first one with     /* Look though the NSEC3 records to find the first one with 
     an algorithm we support (currently only algo == 1).     an algorithm we support.
   
      Take the algo, iterations, and salt of that record       Take the algo, iterations, and salt of that record
      as the ones we're going to use, and prune any        as the ones we're going to use, and prune any 
Line 1443  static int prove_non_existence_nsec3(struct dns_header Line 1683  static int prove_non_existence_nsec3(struct dns_header
   for (i = 0; i < nsec_count; i++)    for (i = 0; i < nsec_count; i++)
     {      {
       if (!(p = skip_name(nsecs[i], header, plen, 15)))        if (!(p = skip_name(nsecs[i], header, plen, 15)))
        return STAT_BOGUS; /* bad packet */        return 0; /* bad packet */
               
       p += 10; /* type, class, TTL, rdlen */        p += 10; /* type, class, TTL, rdlen */
       algo = *p++;        algo = *p++;
               
      if (algo == 1)      if ((hash = hash_find(nsec3_digest_name(algo))))
         break; /* known algo */          break; /* known algo */
     }      }
   
   /* No usable NSEC3s */    /* No usable NSEC3s */
   if (i == nsec_count)    if (i == nsec_count)
    return STAT_BOGUS;    return 0;
   
   p++; /* flags */    p++; /* flags */
   
   GETSHORT (iterations, p);    GETSHORT (iterations, p);
     /* Upper-bound iterations, to avoid DoS.
        Strictly, there are lower bounds for small keys, but
        since we don't have key size info here, at least limit
        to the largest bound, for 4096-bit keys. RFC 5155 10.3 */
     if (iterations > 2500)
       return 0;
     
   salt_len = *p++;    salt_len = *p++;
   salt = p;    salt = p;
   if (!CHECK_LEN(header, salt, plen, salt_len))    if (!CHECK_LEN(header, salt, plen, salt_len))
    return STAT_BOGUS; /* bad packet */    return 0; /* bad packet */
           
   /* Now prune so we only have NSEC3 records with same iterations, salt and algo */    /* Now prune so we only have NSEC3 records with same iterations, salt and algo */
   for (i = 0; i < nsec_count; i++)    for (i = 0; i < nsec_count; i++)
     {      {
       unsigned char *nsec3p = nsecs[i];        unsigned char *nsec3p = nsecs[i];
      int this_iter;      int this_iter, flags;
   
       nsecs[i] = NULL; /* Speculative, will be restored if OK. */        nsecs[i] = NULL; /* Speculative, will be restored if OK. */
               
       if (!(p = skip_name(nsec3p, header, plen, 15)))        if (!(p = skip_name(nsec3p, header, plen, 15)))
        return STAT_BOGUS; /* bad packet */        return 0; /* bad packet */
               
       p += 10; /* type, class, TTL, rdlen */        p += 10; /* type, class, TTL, rdlen */
               
       if (*p++ != algo)        if (*p++ != algo)
         continue;          continue;
     
      p++; /* flags */      flags = *p++; /* flags */
               
         /* 5155 8.2 */
         if (flags != 0 && flags != 1)
           continue;
   
       GETSHORT(this_iter, p);        GETSHORT(this_iter, p);
       if (this_iter != iterations)        if (this_iter != iterations)
         continue;          continue;
Line 1489  static int prove_non_existence_nsec3(struct dns_header Line 1741  static int prove_non_existence_nsec3(struct dns_header
         continue;          continue;
               
       if (!CHECK_LEN(header, p, plen, salt_len))        if (!CHECK_LEN(header, p, plen, salt_len))
        return STAT_BOGUS; /* bad packet */        return 0; /* bad packet */
   
       if (memcmp(p, salt, salt_len) != 0)        if (memcmp(p, salt, salt_len) != 0)
         continue;          continue;
Line 1498  static int prove_non_existence_nsec3(struct dns_header Line 1750  static int prove_non_existence_nsec3(struct dns_header
       nsecs[i] = nsec3p;        nsecs[i] = nsec3p;
     }      }
   
  /* Algo is checked as 1 above */  if ((digest_len = hash_name(name, &digest, hash, salt, salt_len, iterations)) == 0)
  if (!(hash = hash_find("sha1")))    return 0;
    return STAT_BOGUS;  
   if (check_nsec3_coverage(header, plen, digest_len, digest, type, workspace1, workspace2, nsecs, nsec_count, nons))
     return 1;
   
  /* Now, we need the "closest encloser NSEC3" */  /* Can't find an NSEC3 which covers the name directly, we need the "closest encloser NSEC3" 
      or an answer inferred from a wildcard record. */
   closest_encloser = name;    closest_encloser = name;
   next_closest = NULL;    next_closest = NULL;
   
Line 1511  static int prove_non_existence_nsec3(struct dns_header Line 1766  static int prove_non_existence_nsec3(struct dns_header
       if (*closest_encloser == '.')        if (*closest_encloser == '.')
         closest_encloser++;          closest_encloser++;
   
         if (wildname && hostname_isequal(closest_encloser, wildname))
           break;
   
       if ((digest_len = hash_name(closest_encloser, &digest, hash, salt, salt_len, iterations)) == 0)        if ((digest_len = hash_name(closest_encloser, &digest, hash, salt, salt_len, iterations)) == 0)
        return STAT_BOGUS;        return 0;
               
       for (i = 0; i < nsec_count; i++)        for (i = 0; i < nsec_count; i++)
         if ((p = nsecs[i]))          if ((p = nsecs[i]))
           {            {
             if (!extract_name(header, plen, &p, workspace1, 1, 0) ||              if (!extract_name(header, plen, &p, workspace1, 1, 0) ||
                 !(base32_len = base32_decode(workspace1, (unsigned char *)workspace2)))                  !(base32_len = base32_decode(workspace1, (unsigned char *)workspace2)))
              return STAT_BOGUS;              return 0;
                       
             if (digest_len == base32_len &&              if (digest_len == base32_len &&
                 memcmp(digest, workspace2, digest_len) == 0)                  memcmp(digest, workspace2, digest_len) == 0)
Line 1533  static int prove_non_existence_nsec3(struct dns_header Line 1791  static int prove_non_existence_nsec3(struct dns_header
     }      }
   while ((closest_encloser = strchr(closest_encloser, '.')));    while ((closest_encloser = strchr(closest_encloser, '.')));
       
  /* No usable NSEC3s */  if (!closest_encloser || !next_closest)
  if (i == nsec_count)    return 0;
    return STAT_BOGUS; 
       
  if (!next_closest)  /* Look for NSEC3 that proves the non-existence of the next-closest encloser */
   if ((digest_len = hash_name(next_closest, &digest, hash, salt, salt_len, iterations)) == 0)
     return 0;
 
   if (!check_nsec3_coverage(header, plen, digest_len, digest, type, workspace1, workspace2, nsecs, nsec_count, NULL))
     return 0;
   
   /* Finally, check that there's no seat of wildcard synthesis */
   if (!wildname)
     {      {
      /* We found an NSEC3 whose hashed name exactly matches the query, so      if (!(wildcard = strchr(next_closest, '.')) || wildcard == next_closest)
         Now we just need to check the type map. p points to the RR data for the record. */        return 0;
      int rdlen; 
      unsigned char *psave; 
      int offset = (type & 0xff) >> 3; 
      int mask = 0x80 >> (type & 0x07); 
               
      p += 8; /* class, type, TTL */      wildcard--;
      GETSHORT(rdlen, p);      *wildcard = '*';
      psave = p; 
      p += 5 + salt_len; /* algo, flags, iterations, salt_len, salt */ 
      hash_len = *p++; 
      if (!CHECK_LEN(header, p, plen, hash_len)) 
        return STAT_BOGUS; /* bad packet */ 
      p += hash_len; 
      rdlen -= p - psave; 
               
      while (rdlen >= 2)      if ((digest_len = hash_name(wildcard, &digest, hash, salt, salt_len, iterations)) == 0)
        {        return 0;
          if (!CHECK_LEN(header, p, plen, rdlen)) 
            return STAT_BOGUS; 
           
          if (p[0] == type >> 8) 
            { 
              /* Does the NSEC3 say our type exists? */ 
              if (offset < p[1] && (p[offset+2] & mask) != 0) 
                return STAT_BOGUS; 
               
              break; /* finshed checking */ 
            } 
           
          rdlen -= p[1]; 
          p +=  p[1]; 
        } 
               
      return STAT_SECURE;      if (!check_nsec3_coverage(header, plen, digest_len, digest, type, workspace1, workspace2, nsecs, nsec_count, NULL))
         return 0;
     }      }
     
     return 1;
   }
   
  /* Look for NSEC3 that proves the non-existence of the next-closest encloser */static int prove_non_existence(struct dns_header *header, size_t plen, char *keyname, char *name, int qtype, int qclass, char *wildname, int *nons)
  if ((digest_len = hash_name(next_closest, &digest, hash, salt, salt_len, iterations)) == 0){
    return STAT_BOGUS;  static unsigned char **nsecset = NULL;
  static int nsecset_sz = 0;
  for (i = 0; i < nsec_count; i++) 
    if ((p = nsecs[i])) 
      { 
        if (!extract_name(header, plen, &p, workspace1, 1, 0) || 
            !(base32_len = base32_decode(workspace1, (unsigned char *)workspace2))) 
          return STAT_BOGUS; 
            
        p += 15 + salt_len; /* class, type, TTL, rdlen, algo, flags, iterations, salt_len, salt */ 
        hash_len = *p++; /* p now points to next hashed name */ 
  
        if (!CHECK_LEN(header, p, plen, hash_len)) 
          return STAT_BOGUS; 
         
        if (digest_len == base32_len && hash_len == base32_len) 
          { 
            if (memcmp(workspace2, digest, digest_len) <= 0) 
              { 
                /* Normal case, hash falls between NSEC3 name-hash and next domain name-hash, 
                   wrap around case, name-hash falls between NSEC3 name-hash and end */ 
                if (memcmp(p, digest, digest_len) > 0 || memcmp(workspace2, p, digest_len) > 0) 
                  return STAT_SECURE; 
              } 
            else  
              { 
                /* wrap around case, name falls between start and next domain name */ 
                if (memcmp(workspace2, p, digest_len) > 0 && memcmp(p, digest, digest_len) > 0) 
                  return STAT_SECURE; 
              } 
          } 
      } 
       
  /* Finally, check that there's no seat of wildcard synthesis */  int type_found = 0;
  if (!(wildcard = strchr(next_closest, '.')) || wildcard == next_closest)  unsigned char *p = skip_questions(header, plen);
    return STAT_BOGUS;  int type, class, rdlen, i, nsecs_found;
       
  wildcard--;  /* Move to NS section */
  *wildcard = '*';  if (!p || !(p = skip_section(p, ntohs(header->ancount), header, plen)))
     return 0;
       
  if ((digest_len = hash_name(wildcard, &digest, hash, salt, salt_len, iterations)) == 0)  for (nsecs_found = 0, i = ntohs(header->nscount); i != 0; i--)
    return STAT_BOGUS;    {
       unsigned char *pstart = p;
       
       if (!(p = skip_name(p, header, plen, 10)))
         return 0;
       
       GETSHORT(type, p); 
       GETSHORT(class, p);
       p += 4; /* TTL */
       GETSHORT(rdlen, p);
 
       if (class == qclass && (type == T_NSEC || type == T_NSEC3))
         {
           /* No mixed NSECing 'round here, thankyouverymuch */
           if (type_found != 0 && type_found != type)
             return 0;
 
           type_found = type;
 
           if (!expand_workspace(&nsecset, &nsecset_sz, nsecs_found))
             return 0; 
           
           nsecset[nsecs_found++] = pstart;
         }
       
       if (!ADD_RDLEN(header, p, plen, rdlen))
         return 0;
     }
       
  for (i = 0; i < nsec_count; i++)  if (type_found == T_NSEC)
    if ((p = nsecs[i]))    return prove_non_existence_nsec(header, plen, nsecset, nsecs_found, daemon->workspacename, keyname, name, qtype, nons);
      {  else if (type_found == T_NSEC3)
        if (!extract_name(header, plen, &p, workspace1, 1, 0) ||    return prove_non_existence_nsec3(header, plen, nsecset, nsecs_found, daemon->workspacename, keyname, name, qtype, wildname, nons);
            !(base32_len = base32_decode(workspace1, (unsigned char *)workspace2)))  else
          return STAT_BOGUS;    return 0;
            
        p += 15 + salt_len; /* class, type, TTL, rdlen, algo, flags, iterations, salt_len, salt */ 
        hash_len = *p++; /* p now points to next hashed name */ 
  
        if (!CHECK_LEN(header, p, plen, hash_len)) 
          return STAT_BOGUS; 
         
        if (digest_len == base32_len && hash_len == base32_len) 
          { 
            if (memcmp(workspace2, digest, digest_len) <= 0) 
              { 
                /* Normal case, hash falls between NSEC3 name-hash and next domain name-hash, 
                   wrap around case, name-hash falls between NSEC3 name-hash and end */ 
                if (memcmp(p, digest, digest_len) > 0 || memcmp(workspace2, p, digest_len) > 0) 
                  return STAT_SECURE; 
              } 
            else  
              { 
                /* wrap around case, name falls between start and next domain name */ 
                if (memcmp(workspace2, p, digest_len) > 0 && memcmp(p, digest, digest_len) > 0) 
                  return STAT_SECURE; 
              } 
          } 
      } 
   
  return STAT_BOGUS; 
 }  }
    
/* Validate all the RRsets in the answer and authority sections of the reply (4035:3.2.3) *//* Check signing status of name.
/* Returns are the same as validate_rrset, plus the class if the missing key is in *class */   returns:
int dnssec_validate_reply(time_t now, struct dns_header *header, size_t plen, char *name, char *keyname, int *class, int *neganswer)   STAT_SECURE   zone is signed.
    STAT_INSECURE zone proved unsigned.
    STAT_NEED_DS  require DS record of name returned in keyname.
    STAT_NEED_KEY require DNSKEY record of name returned in keyname.
    name returned unaltered.
 */
 static int zone_status(char *name, int class, char *keyname, time_t now)
 {  {
  unsigned char *ans_start, *qname, *p1, *p2, **nsecs;  int name_start = strlen(name); /* for when TA is root */
  int type1, class1, rdlen1, type2, class2, rdlen2, qclass, qtype;  struct crec *crecp;
  int i, j, rc, nsec_count, cname_count = CNAME_CHAIN;  char *p;
  int nsec_type = 0, have_answer = 0; 
   
     /* First, work towards the root, looking for a trust anchor.
        This can either be one configured, or one previously cached.
        We can assume, if we don't find one first, that there is
        a trust anchor at the root. */
     for (p = name; p; p = strchr(p, '.'))
       {
         if (*p == '.')
           p++;
   
         if (cache_find_by_name(NULL, p, now, F_DS))
           {
             name_start = p - name;
             break;
           }
       }
   
     /* Now work away from the trust anchor */
     while (1)
       {
         strcpy(keyname, &name[name_start]);
         
         if (!(crecp = cache_find_by_name(NULL, keyname, now, F_DS)))
           return STAT_NEED_DS;
         
          /* F_DNSSECOK misused in DS cache records to non-existance of NS record.
             F_NEG && !F_DNSSECOK implies that we've proved there's no DS record here,
             but that's because there's no NS record either, ie this isn't the start
             of a zone. We only prove that the DNS tree below a node is unsigned when
             we prove that we're at a zone cut AND there's no DS record. */
         if (crecp->flags & F_NEG)
           {
             if (crecp->flags & F_DNSSECOK)
               return STAT_INSECURE; /* proved no DS here */
           }
         else
           {
             /* If all the DS records have digest and/or sig algos we don't support,
                then the zone is insecure. Note that if an algo
                appears in the DS, then RRSIGs for that algo MUST
                exist for each RRset: 4035 para 2.2  So if we find
                a DS here with digest and sig we can do, we're entitled
                to assume we can validate the zone and if we can't later,
                because an RRSIG is missing we return BOGUS.
             */
             do 
               {
                 if (crecp->uid == (unsigned int)class &&
                     hash_find(ds_digest_name(crecp->addr.ds.digest)) &&
                     verify_func(crecp->addr.ds.algo))
                   break;
               }
             while ((crecp = cache_find_by_name(crecp, keyname, now, F_DS)));
   
             if (!crecp)
               return STAT_INSECURE;
           }
   
         if (name_start == 0)
           break;
   
         for (p = &name[name_start-2]; (*p != '.') && (p != name); p--);
         
         if (p != name)
           p++;
         
         name_start = p - name;
       } 
   
     return STAT_SECURE;
   }
          
   /* Validate all the RRsets in the answer and authority sections of the reply (4035:3.2.3) 
      Return code:
      STAT_SECURE   if it validates.
      STAT_INSECURE at least one RRset not validated, because in unsigned zone.
      STAT_BOGUS    signature is wrong, bad packet, no validation where there should be.
      STAT_NEED_KEY need DNSKEY to complete validation (name is returned in keyname, class in *class)
      STAT_NEED_DS  need DS to complete validation (name is returned in keyname) 
   */
   int dnssec_validate_reply(time_t now, struct dns_header *header, size_t plen, char *name, char *keyname, 
                             int *class, int check_unsigned, int *neganswer, int *nons)
   {
     static unsigned char **targets = NULL;
     static int target_sz = 0;
   
     unsigned char *ans_start, *p1, *p2;
     int type1, class1, rdlen1, type2, class2, rdlen2, qclass, qtype, targetidx;
     int i, j, rc;
   
   if (neganswer)    if (neganswer)
     *neganswer = 0;      *neganswer = 0;
       
Line 1674  int dnssec_validate_reply(time_t now, struct dns_heade Line 1983  int dnssec_validate_reply(time_t now, struct dns_heade
   if (RCODE(header) != NXDOMAIN && RCODE(header) != NOERROR)    if (RCODE(header) != NXDOMAIN && RCODE(header) != NOERROR)
     return STAT_INSECURE;      return STAT_INSECURE;
   
  qname = p1 = (unsigned char *)(header+1);  p1 = (unsigned char *)(header+1);
       
      /* Find all the targets we're looking for answers to.
        The zeroth array element is for the query, subsequent ones
        for CNAME targets, unless the query is for a CNAME. */
   
     if (!expand_workspace(&targets, &target_sz, 0))
       return STAT_BOGUS;
     
     targets[0] = p1;
     targetidx = 1;
      
   if (!extract_name(header, plen, &p1, name, 1, 4))    if (!extract_name(header, plen, &p1, name, 1, 4))
     return STAT_BOGUS;      return STAT_BOGUS;
  
   GETSHORT(qtype, p1);    GETSHORT(qtype, p1);
   GETSHORT(qclass, p1);    GETSHORT(qclass, p1);
   ans_start = p1;    ans_start = p1;
   
   if (qtype == T_ANY)  
     have_answer = 1;  
     
  /* Can't validate an RRISG query */  /* Can't validate an RRSIG query */
   if (qtype == T_RRSIG)    if (qtype == T_RRSIG)
     return STAT_INSECURE;      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_BOGUS; /* 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_BOGUS;  
                  
               p1 = ans_start;  
               goto cname_loop;  
             }  
         }   
   
       if (!ADD_RDLEN(header, p1, plen, rdlen2))  
         return STAT_BOGUS;  
     }  
      
   if (neganswer && !have_answer)  
     *neganswer = 1;  
   
   /* No data, therefore no sigs */  
   if (ntohs(header->ancount) + ntohs(header->nscount) == 0)  
     return STAT_NO_SIG;  
       
     if (qtype != T_CNAME)
       for (j = ntohs(header->ancount); j != 0; j--) 
         {
           if (!(p1 = skip_name(p1, header, plen, 10)))
             return STAT_BOGUS; /* bad packet */
           
           GETSHORT(type2, p1); 
           p1 += 6; /* class, TTL */
           GETSHORT(rdlen2, p1);  
           
           if (type2 == T_CNAME)
             {
               if (!expand_workspace(&targets, &target_sz, targetidx))
                 return STAT_BOGUS;
               
               targets[targetidx++] = p1; /* pointer to target name */
             }
           
           if (!ADD_RDLEN(header, p1, plen, rdlen2))
             return STAT_BOGUS;
         }
     
   for (p1 = ans_start, i = 0; i < ntohs(header->ancount) + ntohs(header->nscount); i++)    for (p1 = ans_start, i = 0; i < ntohs(header->ancount) + ntohs(header->nscount); i++)
     {      {
       if (!extract_name(header, plen, &p1, name, 1, 10))        if (!extract_name(header, plen, &p1, name, 1, 10))
Line 1769  int dnssec_validate_reply(time_t now, struct dns_heade Line 2062  int dnssec_validate_reply(time_t now, struct dns_heade
           /* Not done, validate now */            /* Not done, validate now */
           if (j == i)            if (j == i)
             {              {
              int ttl, keytag, algo, digest, type_covered;              int sigcnt, rrcnt;
              unsigned char *psave;              char *wildname;
              struct all_addr a; 
              struct blockdata *key; 
              struct crec *crecp; 
                               
              rc = validate_rrset(now, header, plen, class1, type1, name, keyname, NULL, 0, 0, 0);              if (!explore_rrset(header, plen, class1, type1, name, keyname, &sigcnt, &rrcnt))
                              return STAT_BOGUS;
              if (rc == STAT_SECURE_WILDCARD)
               /* No signatures for RRset. We can be configured to assume this is OK and return a INSECURE result. */
               if (sigcnt == 0)
                 {                  {
                  /* An attacker replay a wildcard answer with a different                  if (check_unsigned)
                     answer and overlay a genuine RR. To prove this                    {
                     hasn't happened, the answer must prove that                      rc = zone_status(name, class1, keyname, now);
                     the gennuine record doesn't exist. Check that here. */                      if (rc == STAT_SECURE)
                  if (!nsec_type && !(nsec_type = find_nsec_records(header, plen, &nsecs, &nsec_count, class1)))                        rc = STAT_BOGUS;
                    return STAT_BOGUS; /* No NSECs or bad packet */                       if (class)
                          *class = class1; /* Class for NEED_DS or NEED_KEY */
                     }
                   else 
                     rc = STAT_INSECURE; 
                                       
                  if (nsec_type == T_NSEC)                  return rc;
                    rc = prove_non_existence_nsec(header, plen, nsecs, nsec_count, daemon->workspacename, keyname, name, type1);                }
                  else              
                    rc = prove_non_existence_nsec3(header, plen, nsecs, nsec_count, daemon->workspacename, keyname, name, type1);              /* explore_rrset() gives us key name from sigs in keyname.
                  Can't overwrite name here. */
               strcpy(daemon->workspacename, keyname);
               rc = zone_status(daemon->workspacename, class1, keyname, now);
   
                  if (rc != STAT_SECURE)              if (rc != STAT_SECURE)
                    return rc; 
                }  
              else if (rc != STAT_SECURE) 
                 {                  {
                     /* Zone is insecure, don't need to validate RRset */
                   if (class)                    if (class)
                    *class = class1; /* Class for DS or DNSKEY */                    *class = class1; /* Class for NEED_DS or NEED_KEY */
                   return rc;                    return rc;
                }                } 
                               
              /* Cache RRsigs in answer section, and if we just validated a DS RRset, cache it */              rc = validate_rrset(now, header, plen, class1, type1, sigcnt, rrcnt, name, keyname, &wildname, NULL, 0, 0, 0);
              cache_start_insert(); 
                               
              for (p2 = ans_start, j = 0; j < ntohs(header->ancount); j++)              if (rc == STAT_BOGUS || rc == STAT_NEED_KEY || rc == STAT_NEED_DS)
                 {                  {
                  if (!(rc = extract_name(header, plen, &p2, name, 0, 10)))                  if (class)
                    return STAT_BOGUS; /* bad packet */                    *class = class1; /* Class for DS or DNSKEY */
                                        return rc;
                  GETSHORT(type2, p2);                } 
                  GETSHORT(class2, p2);              else 
                  GETLONG(ttl, p2);                {
                  GETSHORT(rdlen2, p2);                  /* rc is now STAT_SECURE or STAT_SECURE_WILDCARD */
                                        
                  if (!CHECK_LEN(header, p2, plen, rdlen2))                  /* Note if we've validated either the answer to the question
                    return STAT_BOGUS; /* bad packet */                     or the target of a CNAME. Any not noted will need NSEC or
                                       to be in unsigned space. */
                  if (class2 == class1 && rc == 1) 
                    {  
                      psave = p2; 
   
                      if (type1 == T_DS && type2 == T_DS)                  for (j = 0; j <targetidx; j++)
                        {                    if ((p2 = targets[j]))
                          if (rdlen2 < 4)                      {
                            return STAT_BOGUS; /* bad packet */                        if (!(rc = extract_name(header, plen, &p2, name, 0, 10)))
                                                    return STAT_BOGUS; /* bad packet */
                          GETSHORT(keytag, p2);                        
                          algo = *p2++;                        if (class1 == qclass && rc == 1 && (type1 == T_CNAME || type1 == qtype || qtype == T_ANY ))
                          digest = *p2++;                          targets[j] = NULL;
                                                }
                          /* Cache needs to known class for DNSSEC stuff */                            
                          a.addr.dnssec.class = class2;                   /* An attacker replay a wildcard answer with a different
                                                answer and overlay a genuine RR. To prove this
                          if ((key = blockdata_alloc((char*)p2, rdlen2 - 4)))                      hasn't happened, the answer must prove that
                            {                      the gennuine record doesn't exist. Check that here. 
                              if (!(crecp = cache_insert(name, &a, now, ttl, F_FORWARD | F_DS | F_DNSSECOK)))                      Note that we may not yet have validated the NSEC/NSEC3 RRsets. 
                                blockdata_free(key);                      That's not a problem since if the RRsets later fail
                              else                      we'll return BOGUS then. */
                                {                  if (rc == STAT_SECURE_WILDCARD && !prove_non_existence(header, plen, keyname, name, type1, class1, wildname, NULL))
                                  a.addr.keytag = keytag;                    return STAT_BOGUS;
                                  log_query(F_KEYTAG | F_UPSTREAM, name, &a, "DS keytag %u"); 
                                  crecp->addr.ds.digest = digest; 
                                  crecp->addr.ds.keydata = key; 
                                  crecp->addr.ds.algo = algo; 
                                  crecp->addr.ds.keytag = keytag; 
                                  crecp->addr.ds.keylen = rdlen2 - 4;  
                                }  
                            } 
                        } 
                      else if (type2 == T_RRSIG) 
                        { 
                          if (rdlen2 < 18) 
                            return STAT_BOGUS; /* bad packet */ 
                           
                          GETSHORT(type_covered, p2); 
 
                          if (type_covered == type1 &&  
                              (type_covered == T_A || type_covered == T_AAAA || 
                               type_covered == T_CNAME || type_covered == T_DS ||  
                               type_covered == T_DNSKEY || type_covered == T_PTR))  
                            { 
                              a.addr.dnssec.type = type_covered; 
                              a.addr.dnssec.class = class1; 
                               
                              algo = *p2++; 
                              p2 += 13; /* labels, orig_ttl, expiration, inception */ 
                              GETSHORT(keytag, p2); 
                               
                              if ((key = blockdata_alloc((char*)psave, rdlen2))) 
                                { 
                                  if (!(crecp = cache_insert(name, &a, now, ttl,  F_FORWARD | F_DNSKEY | F_DS))) 
                                    blockdata_free(key); 
                                  else 
                                    { 
                                      crecp->addr.sig.keydata = key; 
                                      crecp->addr.sig.keylen = rdlen2; 
                                      crecp->addr.sig.keytag = keytag; 
                                      crecp->addr.sig.type_covered = type_covered; 
                                      crecp->addr.sig.algo = algo; 
                                    } 
                                } 
                            } 
                        } 
                       
                      p2 = psave; 
                    } 
                   
                  if (!ADD_RDLEN(header, p2, plen, rdlen2)) 
                    return STAT_BOGUS; /* bad packet */ 
                 }                  }
                     
               cache_end_insert();  
             }              }
         }          }
   
Line 1899  int dnssec_validate_reply(time_t now, struct dns_heade Line 2141  int dnssec_validate_reply(time_t now, struct dns_heade
         return STAT_BOGUS;          return STAT_BOGUS;
     }      }
   
  /* OK, all the RRsets validate, now see if we have a NODATA or NXDOMAIN reply */  /* OK, all the RRsets validate, now see if we have a missing answer or CNAME target. */
  if (have_answer)  for (j = 0; j <targetidx; j++)
    return STAT_SECURE;    if ((p2 = targets[j]))
           {
  /* NXDOMAIN or NODATA reply, prove that (name, class1, type1) can't exist */        if (neganswer)
  /* First marshall the NSEC records, if we've not done it previously */          *neganswer = 1;
  if (!nsec_type && !(nsec_type = find_nsec_records(header, plen, &nsecs, &nsec_count, qclass))) 
    return STAT_BOGUS; /* No NSECs */ 
    
  /* Get name of missing answer */ 
  if (!extract_name(header, plen, &qname, name, 1, 0)) 
    return STAT_BOGUS; 
   
  if (nsec_type == T_NSEC) 
    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, qtype); 
} 
   
/* Chase the CNAME chain in the packet until the first record which _doesn't validate.        if (!extract_name(header, plen, &p2, name, 1, 10))
   Needed for proving answer in unsigned space.          return STAT_BOGUS; /* bad packet */
   Return STAT_NEED_*             
          STAT_BOGUS - error        /* NXDOMAIN or NODATA reply, unanswered question is (name, qclass, qtype) */
          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, qclass, rdlen, j, rc; 
  int cname_count = CNAME_CHAIN; 
   
  /* Get question */        /* For anything other than a DS record, this situation is OK if either
  if (!extract_name(header, plen, &p, name, 1, 4))           the answer is in an unsigned zone, or there's a NSEC records. */
    return STAT_BOGUS;        if (!prove_non_existence(header, plen, keyname, name, qtype, qclass, NULL, nons))
           {
             /* Empty DS without NSECS */
             if (qtype == T_DS)
               return STAT_BOGUS;
             
             if ((rc = zone_status(name, qclass, keyname, now)) != STAT_SECURE)
               {
                 if (class)
                   *class = qclass; /* Class for NEED_DS or NEED_KEY */
                 return rc;
               } 
             
             return STAT_BOGUS; /* signed zone, no NSECs */
           }
       }
       
  p +=2; /* type */  return STAT_SECURE;
  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;      
    } 
 }  }
   
   
Line 2007  int dnskey_keytag(int alg, int flags, unsigned char *k Line 2198  int dnskey_keytag(int alg, int flags, unsigned char *k
     }      }
 }  }
   
size_t dnssec_generate_query(struct dns_header *header, char *end, char *name, int class, int type, union mysockaddr *addr)size_t dnssec_generate_query(struct dns_header *header, unsigned char *end, char *name, int class, 
                              int type, union mysockaddr *addr, int edns_pktsz)
 {  {
   unsigned char *p;    unsigned char *p;
   char *types = querystr("dnssec-query", type);    char *types = querystr("dnssec-query", type);
     size_t ret;
   
   if (addr->sa.sa_family == AF_INET)     if (addr->sa.sa_family == AF_INET) 
    log_query(F_DNSSEC | F_IPV4, name, (struct all_addr *)&addr->in.sin_addr, types);    log_query(F_NOEXTRA | F_DNSSEC | F_IPV4, name, (struct all_addr *)&addr->in.sin_addr, types);
 #ifdef HAVE_IPV6  #ifdef HAVE_IPV6
   else    else
    log_query(F_DNSSEC | F_IPV6, name, (struct all_addr *)&addr->in6.sin6_addr, types);    log_query(F_NOEXTRA | F_DNSSEC | F_IPV6, name, (struct all_addr *)&addr->in6.sin6_addr, types);
 #endif  #endif
       
   header->qdcount = htons(1);    header->qdcount = htons(1);
Line 2039  size_t dnssec_generate_query(struct dns_header *header Line 2232  size_t dnssec_generate_query(struct dns_header *header
   PUTSHORT(type, p);    PUTSHORT(type, p);
   PUTSHORT(class, p);    PUTSHORT(class, p);
   
  return add_do_bit(header, p - (unsigned char *)header, end);  ret = add_do_bit(header, p - (unsigned char *)header, end);
} 
   
/* Go through a domain name, find "pointers" and fix them up based on how many bytes  if (find_pseudoheader(header, ret, NULL, &p, NULL, NULL))
   we've chopped out of the packet, or check they don't point into an elided part.  */    PUTSHORT(edns_pktsz, p);
static int check_name(unsigned char **namep, struct dns_header *header, size_t plen, int fixup, unsigned char **rrs, int rr_count) 
{ 
  unsigned char *ansp = *namep; 
   
  while(1)  return ret;
    { 
      unsigned int label_type; 
       
      if (!CHECK_LEN(header, ansp, plen, 1)) 
        return 0; 
       
      label_type = (*ansp) & 0xc0; 
 
      if (label_type == 0xc0) 
        { 
          /* pointer for compression. */ 
          unsigned int offset; 
          int i; 
          unsigned char *p; 
           
          if (!CHECK_LEN(header, ansp, plen, 2)) 
            return 0; 
 
          offset = ((*ansp++) & 0x3f) << 8; 
          offset |= *ansp++; 
 
          p = offset + (unsigned char *)header; 
           
          for (i = 0; i < rr_count; i++) 
            if (p < rrs[i]) 
              break; 
            else 
              if (i & 1) 
                offset -= rrs[i] - rrs[i-1]; 
 
          /* does the pointer end up in an elided RR? */ 
          if (i & 1) 
            return 0; 
 
          /* No, scale the pointer */ 
          if (fixup) 
            { 
              ansp -= 2; 
              *ansp++ = (offset >> 8) | 0xc0; 
              *ansp++ = offset & 0xff; 
            } 
          break; 
        } 
      else if (label_type == 0x80) 
        return 0; /* reserved */ 
      else if (label_type == 0x40) 
        { 
          /* Extended label type */ 
          unsigned int count; 
           
          if (!CHECK_LEN(header, ansp, plen, 2)) 
            return 0; 
           
          if (((*ansp++) & 0x3f) != 1) 
            return 0; /* we only understand bitstrings */ 
           
          count = *(ansp++); /* Bits in bitstring */ 
           
          if (count == 0) /* count == 0 means 256 bits */ 
            ansp += 32; 
          else 
            ansp += ((count-1)>>3)+1; 
        } 
      else 
        { /* label type == 0 Bottom six bits is length */ 
          unsigned int len = (*ansp++) & 0x3f; 
           
          if (!ADD_RDLEN(header, ansp, plen, len)) 
            return 0; 
 
          if (len == 0) 
            break; /* zero length label marks the end. */ 
        } 
    } 
 
  *namep = ansp; 
 
  return 1; 
} 
 
/* Go through RRs and check or fixup the domain names contained within */ 
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) + ntohs(header->arcount); i++) 
    { 
      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; 
  
              for (pp = p, d = get_desc(type); *d != (u16)-1; d++) 
                { 
                  if (*d != 0) 
                    pp += *d; 
                  else if (!check_name(&pp, header, plen, fixup, rrs, rr_count)) 
                    return 0; 
                } 
            } 
        } 
       
      if (!ADD_RDLEN(header, p, plen, rdlen)) 
        return 0; 
    } 
   
  return 1; 
} 
         
 
size_t filter_rrsigs(struct dns_header *header, size_t plen) 
{ 
  static unsigned char **rrs; 
  static int rr_sz = 0; 
   
  unsigned char *p = (unsigned char *)(header+1); 
  int i, rdlen, qtype, qclass, rr_found, chop_an, chop_ns, chop_ar; 
 
  if (ntohs(header->qdcount) != 1 || 
      !(p = skip_name(p, header, plen, 4))) 
    return plen; 
   
  GETSHORT(qtype, p); 
  GETSHORT(qclass, p); 
 
  /* First pass, find pointers to start and end of all the records we wish to elide: 
     records added for DNSSEC, unless explicity queried for */ 
  for (rr_found = 0, chop_ns = 0, chop_an = 0, chop_ar = 0, i = 0;  
       i < ntohs(header->ancount) + ntohs(header->nscount) + ntohs(header->arcount); 
       i++) 
    { 
      unsigned char *pstart = p; 
      int type, class; 
 
      if (!(p = skip_name(p, header, plen, 10))) 
        return plen; 
       
      GETSHORT(type, p);  
      GETSHORT(class, p); 
      p += 4; /* TTL */ 
      GETSHORT(rdlen, p); 
       
      if ((type == T_NSEC || type == T_NSEC3 || type == T_RRSIG) &&  
          (type != qtype || class != qclass)) 
        { 
          if (!expand_workspace(&rrs, &rr_sz, rr_found + 1)) 
            return plen;  
           
          rrs[rr_found++] = pstart; 
 
          if (!ADD_RDLEN(header, p, plen, rdlen)) 
            return plen; 
           
          rrs[rr_found++] = p; 
           
          if (i < ntohs(header->ancount)) 
            chop_an++; 
          else if (i < (ntohs(header->nscount) + ntohs(header->ancount))) 
            chop_ns++; 
          else 
            chop_ar++; 
        } 
      else if (!ADD_RDLEN(header, p, plen, rdlen)) 
        return plen; 
    } 
   
  /* Nothing to do. */ 
  if (rr_found == 0) 
    return plen; 
 
  /* Second pass, look for pointers in names in the records we're keeping and make sure they don't 
     point to records we're going to elide. This is theoretically possible, but unlikely. If 
     it happens, we give up and leave the answer unchanged. */ 
  p = (unsigned char *)(header+1); 
   
  /* question first */ 
  if (!check_name(&p, header, plen, 0, rrs, rr_found)) 
    return plen; 
  p += 4; /* qclass, qtype */ 
   
  /* Now answers and NS */ 
  if (!check_rrs(p, header, plen, 0, rrs, rr_found)) 
    return plen; 
   
  /* Third pass, elide records */ 
  for (p = rrs[0], i = 1; i < rr_found; i += 2) 
    { 
      unsigned char *start = rrs[i]; 
      unsigned char *end = (i != rr_found - 1) ? rrs[i+1] : ((unsigned char *)(header+1)) + plen; 
       
      memmove(p, start, end-start); 
      p += end-start; 
    } 
      
  plen = p - (unsigned char *)header; 
  header->ancount = htons(ntohs(header->ancount) - chop_an); 
  header->nscount = htons(ntohs(header->nscount) - chop_ns); 
  header->arcount = htons(ntohs(header->arcount) - chop_ar); 
 
  /* Fourth pass, fix up pointers in the remaining records */ 
  p = (unsigned char *)(header+1); 
   
  check_name(&p, header, plen, 1, rrs, rr_found); 
  p += 4; /* qclass, qtype */ 
   
  check_rrs(p, header, plen, 1, rrs, rr_found); 
   
  return plen; 
 }  }
   
 unsigned char* hash_questions(struct dns_header *header, size_t plen, char *name)  unsigned char* hash_questions(struct dns_header *header, size_t plen, char *name)

Removed from v.1.1.1.1  
changed lines
  Added in v.1.1.1.2


FreeBSD-CVSweb <freebsd-cvsweb@FreeBSD.org>