Diff for /embedaddon/dnsmasq/src/dnssec.c between versions 1.1 and 1.1.1.3

version 1.1, 2014/06/15 16:31:38 version 1.1.1.3, 2021/03/17 00:56:46
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-2020 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 19 Line 19
   
 #ifdef HAVE_DNSSEC  #ifdef HAVE_DNSSEC
   
 #include <nettle/rsa.h>  
 #include <nettle/dsa.h>  
 #ifndef NO_NETTLE_ECC  
 #  include <nettle/ecdsa.h>  
 #  include <nettle/ecc-curve.h>  
 #endif  
 #include <nettle/nettle-meta.h>  
 #include <gmp.h>  
   
 #define SERIAL_UNDEF  -100  #define SERIAL_UNDEF  -100
 #define SERIAL_EQ        0  #define SERIAL_EQ        0
 #define SERIAL_LT       -1  #define SERIAL_LT       -1
 #define SERIAL_GT        1  #define SERIAL_GT        1
   
 /* http://www.iana.org/assignments/ds-rr-types/ds-rr-types.xhtml */  
 static char *ds_digest_name(int digest)  
 {  
   switch (digest)  
     {  
     case 1: return "sha1";  
     case 2: return "sha256";  
     case 3: return "gosthash94";  
     case 4: return "sha384";  
     default: return NULL;  
     }  
 }  
    
 /* http://www.iana.org/assignments/dns-sec-alg-numbers/dns-sec-alg-numbers.xhtml */  
 static char *algo_digest_name(int algo)  
 {  
   switch (algo)  
     {  
     case 1: return "md5";  
     case 3: return "sha1";  
     case 5: return "sha1";  
     case 6: return "sha1";  
     case 7: return "sha1";  
     case 8: return "sha256";  
     case 10: return "sha512";  
     case 12: return "gosthash94";  
     case 13: return "sha256";  
     case 14: return "sha384";  
     default: return NULL;  
     }  
 }  
         
 /* Find pointer to correct hash function in nettle library */  
 static const struct nettle_hash *hash_find(char *name)  
 {  
   int i;  
     
   if (!name)  
     return NULL;  
     
   for (i = 0; nettle_hashes[i]; i++)  
     {  
       if (strcmp(nettle_hashes[i]->name, name) == 0)  
         return nettle_hashes[i];  
     }  
   
   return NULL;  
 }  
   
 /* expand ctx and digest memory allocations if necessary and init hash function */  
 static int hash_init(const struct nettle_hash *hash, void **ctxp, unsigned char **digestp)  
 {  
   static void *ctx = NULL;  
   static unsigned char *digest = NULL;  
   static unsigned int ctx_sz = 0;  
   static unsigned int digest_sz = 0;  
   
   void *new;  
   
   if (ctx_sz < hash->context_size)  
     {  
       if (!(new = whine_malloc(hash->context_size)))  
         return 0;  
       if (ctx)  
         free(ctx);  
       ctx = new;  
       ctx_sz = hash->context_size;  
     }  
     
   if (digest_sz < hash->digest_size)  
     {  
       if (!(new = whine_malloc(hash->digest_size)))  
         return 0;  
       if (digest)  
         free(digest);  
       digest = new;  
       digest_sz = hash->digest_size;  
     }  
   
   *ctxp = ctx;  
   *digestp = digest;  
   
   hash->init(ctx);  
   
   return 1;  
 }  
     
 static int rsa_verify(struct blockdata *key_data, unsigned int key_len, unsigned char *sig, size_t sig_len,  
                       unsigned char *digest, int algo)  
 {  
   unsigned char *p;  
   size_t exp_len;  
     
   static struct rsa_public_key *key = NULL;  
   static mpz_t sig_mpz;  
     
   if (key == NULL)  
     {  
       if (!(key = whine_malloc(sizeof(struct rsa_public_key))))  
         return 0;  
         
       nettle_rsa_public_key_init(key);  
       mpz_init(sig_mpz);  
     }  
     
   if ((key_len < 3) || !(p = blockdata_retrieve(key_data, key_len, NULL)))  
     return 0;  
     
   key_len--;  
   if ((exp_len = *p++) == 0)  
     {  
       GETSHORT(exp_len, p);  
       key_len -= 2;  
     }  
     
   if (exp_len >= key_len)  
     return 0;  
     
   key->size =  key_len - exp_len;  
   mpz_import(key->e, exp_len, 1, 1, 0, 0, p);  
   mpz_import(key->n, key->size, 1, 1, 0, 0, p + exp_len);  
   
   mpz_import(sig_mpz, sig_len, 1, 1, 0, 0, sig);  
     
   switch (algo)  
     {  
     case 1:  
       return nettle_rsa_md5_verify_digest(key, digest, sig_mpz);  
     case 5: case 7:  
       return nettle_rsa_sha1_verify_digest(key, digest, sig_mpz);  
     case 8:  
       return nettle_rsa_sha256_verify_digest(key, digest, sig_mpz);  
     case 10:  
       return nettle_rsa_sha512_verify_digest(key, digest, sig_mpz);  
     }  
   
   return 0;  
 }    
   
 static int dsa_verify(struct blockdata *key_data, unsigned int key_len, unsigned char *sig, size_t sig_len,  
                       unsigned char *digest, int algo)  
 {  
   unsigned char *p;  
   unsigned int t;  
     
   static struct dsa_public_key *key = NULL;  
   static struct dsa_signature *sig_struct;  
     
   if (key == NULL)  
     {  
       if (!(sig_struct = whine_malloc(sizeof(struct dsa_signature))) ||   
           !(key = whine_malloc(sizeof(struct dsa_public_key))))   
         return 0;  
         
       nettle_dsa_public_key_init(key);  
       nettle_dsa_signature_init(sig_struct);  
     }  
     
   if ((sig_len < 41) || !(p = blockdata_retrieve(key_data, key_len, NULL)))  
     return 0;  
     
   t = *p++;  
     
   if (key_len < (213 + (t * 24)))  
     return 0;  
     
   mpz_import(key->q, 20, 1, 1, 0, 0, p); p += 20;  
   mpz_import(key->p, 64 + (t*8), 1, 1, 0, 0, p); p += 64 + (t*8);  
   mpz_import(key->g, 64 + (t*8), 1, 1, 0, 0, p); p += 64 + (t*8);  
   mpz_import(key->y, 64 + (t*8), 1, 1, 0, 0, p); p += 64 + (t*8);  
     
   mpz_import(sig_struct->r, 20, 1, 1, 0, 0, sig+1);  
   mpz_import(sig_struct->s, 20, 1, 1, 0, 0, sig+21);  
     
   (void)algo;  
     
   return nettle_dsa_sha1_verify_digest(key, digest, sig_struct);  
 }   
    
 #ifndef NO_NETTLE_ECC  
 static int dnsmasq_ecdsa_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)  
 {  
   unsigned char *p;  
   unsigned int t;  
   struct ecc_point *key;  
   
   static struct ecc_point *key_256 = NULL, *key_384 = NULL;  
   static mpz_t x, y;  
   static struct dsa_signature *sig_struct;  
     
   if (!sig_struct)  
     {  
       if (!(sig_struct = whine_malloc(sizeof(struct dsa_signature))))  
         return 0;  
         
       nettle_dsa_signature_init(sig_struct);  
       mpz_init(x);  
       mpz_init(y);  
     }  
     
   switch (algo)  
     {  
     case 13:  
       if (!key_256)  
         {  
           if (!(key_256 = whine_malloc(sizeof(struct ecc_point))))  
             return 0;  
             
           nettle_ecc_point_init(key_256, &nettle_secp_256r1);  
         }  
         
       key = key_256;  
       t = 32;  
       break;  
         
     case 14:  
       if (!key_384)  
         {  
           if (!(key_384 = whine_malloc(sizeof(struct ecc_point))))  
             return 0;  
             
           nettle_ecc_point_init(key_384, &nettle_secp_384r1);  
         }  
         
       key = key_384;  
       t = 48;  
       break;  
           
     default:  
       return 0;  
     }  
     
   if (sig_len != 2*t || key_len != 2*t ||  
       (p = blockdata_retrieve(key_data, key_len, NULL)))  
     return 0;  
     
   mpz_import(x, t , 1, 1, 0, 0, p);  
   mpz_import(y, t , 1, 1, 0, 0, p + t);  
   
   if (!ecc_point_set(key, x, y))  
     return 0;  
     
   mpz_import(sig_struct->r, t, 1, 1, 0, 0, sig);  
   mpz_import(sig_struct->s, t, 1, 1, 0, 0, sig + t);  
     
   return nettle_ecdsa_verify(key, digest_len, digest, sig_struct);  
 }   
 #endif   
   
 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)  
 {  
   (void)digest_len;  
   
   switch (algo)  
     {  
     case 1: case 5: case 7: case 8: case 10:  
       return rsa_verify(key_data, key_len, sig, sig_len, digest, algo);  
         
     case 3: case 6:   
       return dsa_verify(key_data, key_len, sig, sig_len, digest, algo);  
    
 #ifndef NO_NETTLE_ECC     
     case 13: case 14:  
       return dnsmasq_ecdsa_verify(key_data, key_len, sig, sig_len, digest, digest_len, algo);  
 #endif  
     }  
     
   return 0;  
 }  
   
 /* 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 31  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 declared 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 50  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 74  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 361  static void from_wire(char *name) Line 103  static void from_wire(char *name)
 static int count_labels(char *name)  static int count_labels(char *name)
 {  {
   int i;    int i;
  char *p;
   
   if (*name == 0)    if (*name == 0)
     return 0;      return 0;
   
  for (i = 0; *name; name++)  for (p = name, i = 0; *p; p++)
    if (*name == '.')    if (*p == '.')
       i++;        i++;
   
  return i+1;  /* Don't count empty first label. */
   return *name == '.' ? i : i+1;
 }  }
   
 /* 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 131  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.
  unsigned long curtime;           0 -> not using timestamp, or timestamp exists and is in past.
            1 -> timestamp exists and is in future.
 */
   
  /* Checking timestamps may be temporarily disabled */static time_t timestamp_time;
  if (option_bool(OPT_DNSSEC_TIME)) 
    return 1; 
   
  curtime = time(0); 
   
  /* We must explicitly check against wanted values, because of SERIAL_UNDEF */ 
  return serial_compare_32(curtime, date_start) == SERIAL_GT 
    && serial_compare_32(curtime, date_end) == SERIAL_LT; 
} 
   
static u16 *get_desc(int type)int setup_timestamp(void)
 {  {
  /* List of RRtypes which include domains in the data.  struct stat statbuf;
     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[] =   daemon->back_to_the_future = 0;
    {  
      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;  if (!daemon->timestamp_file)
     return 0;
       
  while (*p != type && *p != 0)  if (stat(daemon->timestamp_file, &statbuf) != -1)
    while (*p++ != (u16)-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 (utimes(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 timeval tv[2];
   
  return p+1;          close(fd);
           
           timestamp_time = 1420070400; /* 1-1-2015 */
           tv[0].tv_sec = tv[1].tv_sec = timestamp_time;
           tv[0].tv_usec = tv[1].tv_usec = 0;
           if (utimes(daemon->timestamp_file, tv) == 0)
             goto check_and_exit;
         }
     }
 
   return -1;
 }  }
   
/* Return bytes of canonicalised rdata, when the return value is zero, the remaining /* Check whether today/now is between date_start and date_end */
   data, pointed to by *p, should be used raw. */static int is_check_date(unsigned long curtime)
static int get_rdata(struct dns_header *header, size_t plen, unsigned char *end, char *buff,  
                     unsigned char **p, u16 **desc) 
 {  {
  int d = **desc;  /* Checking timestamps may be temporarily disabled */
      
  (*desc)++;  /* If the current time if _before_ the timestamp
       on our persistent timestamp file, then assume the
  /* No more data needs mangling */     time if not yet correct, and don't check the
  if (d == (u16)-1)     key timestamps. As soon as the current time is
    return 0;     later then the timestamp, update the timestamp
       and start checking keys */
  if (d == 0 && extract_name(header, plen, p, buff, 1, 0))  if (daemon->timestamp_file)
    /* domain-name, canonicalise */    {
    return to_wire(buff);      if (daemon->back_to_the_future == 0 && difftime(timestamp_time, curtime) <= 0)
  else 
    {  
      /* plain data preceding a domain-name, don't run off the end of the data */ 
      if ((end - *p) < d) 
        d = end - *p; 
       
      if (d != 0) 
         {          {
          memcpy(buff, *p, d);          if (utimes(daemon->timestamp_file, NULL) != 0)
          *p += d;            my_syslog(LOG_ERR, _("failed to update mtime on %s: %s"), daemon->timestamp_file, strerror(errno));
        }          
                my_syslog(LOG_INFO, _("system time considered valid, now checking DNSSEC signature timestamps."));
      return d;          daemon->back_to_the_future = 1;
           daemon->dnssec_no_time_check = 0;
           queue_event(EVENT_RELOAD); /* purge cache */
         } 
 
       return daemon->back_to_the_future;
     }      }
     else
       return !daemon->dnssec_no_time_check;
 }  }
   
static int expand_workspace(unsigned char ***wkspc, int *sz, int new)/* Check whether today/now is between date_start and date_end */
 static int check_date_range(unsigned long curtime, u32 date_start, u32 date_end)
 {  {
  unsigned char **p;  /* We must explicitly check against wanted values, because of SERIAL_UNDEF */
  int new_sz = *sz;  return serial_compare_32(curtime, date_start) == SERIAL_GT
      && serial_compare_32(curtime, date_end) == SERIAL_LT;
  if (new_sz > new)}
    return 1; 
   
  if (new >= 100)/* Return bytes of canonicalised rrdata one by one.
    return 0;   Init state->ip with the RR, and state->end with the end of same.
    Init state->op to NULL.
    Init state->desc to RR descriptor.
    Init state->buff with a MAXDNAME * 2 buffer.
    
    After each call which returns 1, state->op points to the next byte of data.
    On returning 0, the end has been reached.
 */
 struct rdata_state {
   u16 *desc;
   size_t c;
   unsigned char *end, *ip, *op;
   char *buff;
 };
   
  new_sz += 5;static int get_rdata(struct dns_header *header, size_t plen, struct rdata_state *state)
 {
   int d;
       
  if (!(p = whine_malloc((new_sz) * sizeof(unsigned char **))))  if (state->op && state->c != 1)
    return 0;   
   
  if (*wkspc) 
     {      {
      memcpy(p, *wkspc, *sz * sizeof(unsigned char **));      state->op++;
      free(*wkspc);      state->c--;
       return 1;
     }      }
     
   *wkspc = p;  
   *sz = new_sz;  
   
  return 1;  while (1)
     {
       d = *(state->desc);
       
       if (d == (u16)-1)
         {
           /* all the bytes to the end. */
           if ((state->c = state->end - state->ip) != 0)
             {
               state->op = state->ip;
               state->ip = state->end;;
             }
           else
             return 0;
         }
       else
         {
           state->desc++;
           
           if (d == (u16)0)
             {
               /* domain-name, canonicalise */
               int len;
               
               if (!extract_name(header, plen, &state->ip, state->buff, 1, 0) ||
                   (len = to_wire(state->buff)) == 0)
                 continue;
               
               state->c = len;
               state->op = (unsigned char *)state->buff;
             }
           else
             {
               /* plain data preceding a domain-name, don't run off the end of the data */
               if ((state->end - state->ip) < d)
                 d = state->end - state->ip;
               
               if (d == 0)
                 continue;
                   
               state->op = state->ip;
               state->c = d;
               state->ip += d;
             }
         }
       
       return 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  
   RRs which have two domain-names at the start and then other data. 
   The domain-names may have different lengths in each RR, but sort equal 
   
   ------------static int sort_rrset(struct dns_header *header, size_t plen, u16 *rr_desc, int rrsetidx, 
   |abcde|fghi|                      unsigned char **rrset, char *buff1, char *buff2)
   ------------ 
   |abcd|efghi| 
   ------------ 
 
   leaving the following bytes as deciding the order. Hence the nasty left1 and left2 variables. 
*/ 
 
static void sort_rrset(struct dns_header *header, size_t plen, u16 *rr_desc, int rrsetidx,  
                       unsigned char **rrset, char *buff1, char *buff2) 
 {  {
  int swap, quit, i;  int swap, i, j;
       
   do    do
     {      {
       for (swap = 0, i = 0; i < rrsetidx-1; i++)        for (swap = 0, i = 0; i < rrsetidx-1; i++)
         {          {
          int rdlen1, rdlen2, left1, left2, len1, len2, len, rc;          int rdlen1, rdlen2;
          u16 *dp1, *dp2;          struct rdata_state state1, state2;
          unsigned char *end1, *end2;          
           /* Note that these have been determined to be OK previously,            /* Note that these have been determined to be OK previously,
              so we don't need to check for NULL return here. */               so we don't need to check for NULL return here. */
          unsigned char *p1 = skip_name(rrset[i], header, plen, 10);          state1.ip = skip_name(rrset[i], header, plen, 10);
          unsigned char *p2 = skip_name(rrset[i+1], header, plen, 10);          state2.ip = skip_name(rrset[i+1], header, plen, 10);
           state1.op = state2.op = NULL;
           state1.buff = buff1;
           state2.buff = buff2;
           state1.desc = state2.desc = rr_desc;
                       
          p1 += 8; /* skip class, type, ttl */          state1.ip += 8; /* skip class, type, ttl */
          GETSHORT(rdlen1, p1);          GETSHORT(rdlen1, state1.ip);
          end1 = p1 + rdlen1;          if (!CHECK_LEN(header, state1.ip, plen, rdlen1))
             return rrsetidx; /* short packet */
           state1.end = state1.ip + rdlen1;
                       
          p2 += 8; /* skip class, type, ttl */          state2.ip += 8; /* skip class, type, ttl */
          GETSHORT(rdlen2, p2);          GETSHORT(rdlen2, state2.ip);
          end2 = p2 + rdlen2;           if (!CHECK_LEN(header, state2.ip, plen, rdlen2))
                      return rrsetidx; /* short packet */
          dp1 = dp2 = rr_desc;          state2.end = state2.ip + rdlen2; 
          
          for (quit = 0, left1 = 0, left2 = 0, len1 = 0, len2 = 0; !quit;)          /* If the RR has no names in it then canonicalisation
              is the identity function and we can compare
              the RRs directly. If not we compare the 
              canonicalised RRs one byte at a time. */
           if (*rr_desc == (u16)-1)       
             {              {
              if (left1 != 0)              int rdmin = rdlen1 > rdlen2 ? rdlen2 : rdlen1;
                memmove(buff1, buff1 + len1 - left1, left1);              int cmp = memcmp(state1.ip, state2.ip, rdmin);
                               
              if ((len1 = get_rdata(header, plen, end1, buff1 + left1, &p1, &dp1)) == 0)              if (cmp > 0 || (cmp == 0 && rdlen1 > rdmin))
                 {                  {
                   quit = 1;  
                   len1 = end1 - p1;  
                   memcpy(buff1 + left1, p1, len1);  
                 }  
               len1 += left1;  
                 
               if (left2 != 0)  
                 memmove(buff2, buff2 + len2 - left2, left2);  
                 
               if ((len2 = get_rdata(header, plen, end2, buff2 + left2, &p2, &dp2)) == 0)  
                 {  
                   quit = 1;  
                   len2 = end2 - p2;  
                   memcpy(buff2 + left2, p2, len2);  
                 }  
               len2 += left2;  
                  
               if (len1 > len2)  
                 left1 = len1 - len2, left2 = 0, len = len2;  
               else  
                 left2 = len2 - len1, left1 = 0, len = len1;  
                 
               rc = (len == 0) ? 0 : memcmp(buff1, buff2, len);  
                 
               if (rc > 0 || (rc == 0 && quit && len1 > len2))  
                 {  
                   unsigned char *tmp = rrset[i+1];                    unsigned char *tmp = rrset[i+1];
                   rrset[i+1] = rrset[i];                    rrset[i+1] = rrset[i];
                   rrset[i] = tmp;                    rrset[i] = tmp;
                  swap = quit = 1;                  swap = 1;
                 }                  }
              else if (rc < 0)              else if (cmp == 0 && (rdlen1 == rdlen2))
                quit = 1;                {
                   /* Two RRs are equal, remove one copy. RFC 4034, para 6.3 */
                   for (j = i+1; j < rrsetidx-1; j++)
                     rrset[j] = rrset[j+1];
                   rrsetidx--;
                   i--;
                 }
             }              }
             else
               /* Comparing canonicalised RRs, byte-at-a-time. */
               while (1)
                 {
                   int ok1, ok2;
                   
                   ok1 = get_rdata(header, plen, &state1);
                   ok2 = get_rdata(header, plen, &state2);
                   
                   if (!ok1 && !ok2)
                     {
                       /* Two RRs are equal, remove one copy. RFC 4034, para 6.3 */
                       for (j = i+1; j < rrsetidx-1; j++)
                         rrset[j] = rrset[j+1];
                       rrsetidx--;
                       i--;
                       break;
                     }
                   else if (ok1 && (!ok2 || *state1.op > *state2.op)) 
                     {
                       unsigned char *tmp = rrset[i+1];
                       rrset[i+1] = rrset[i];
                       rrset[i] = tmp;
                       swap = 1;
                       break;
                     }
                   else if (ok2 && (!ok1 || *state2.op > *state1.op))
                     break;
                   
                   /* arrive here when bytes are equal, go round the loop again
                      and compare the next ones. */
                 }
         }          }
     } while (swap);      } while (swap);
   
     return rrsetidx;
 }  }
   
/* 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 members 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 0;
   
  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;
               
       if (!(res = extract_name(header, plen, &p, name, 0, 10)))        if (!(res = extract_name(header, plen, &p, name, 0, 10)))
        return STAT_BOGUS; /* bad packet */        return 0; /* bad packet */
               
       GETSHORT(stype, p);        GETSHORT(stype, p);
       GETSHORT(sclass, p);        GETSHORT(sclass, p);
      p += 4; /* TTL */           
       
       pdata = p;        pdata = p;
   
         p += 4; /* TTL */
       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 449  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;
                 }                   } 
                               
              p = pdata + 2; /* restore for ADD_RDLEN */              p = pdata + 6; /* restore for ADD_RDLEN */
             }              }
         }          }
               
       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.
 
    ttl_out is the floor on TTL, based on TTL and orig_ttl and expiration of sig used to validate.
 */
 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 long *ttl_out)
 {
   unsigned char *p;
   int rdlen, j, name_labels, algo, labels, key_tag;
   struct crec *crecp = NULL;
   u16 *rr_desc = rrfilter_desc(type);
   u32 sig_expiration, sig_inception;
 
   unsigned long curtime = time(0);
   int time_check = is_check_date(curtime);
       
     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. */
  sort_rrset(header, plen, rr_desc, rrsetidx, rrset, daemon->workspacename, keyname);  rrsetidx = sort_rrset(header, plen, rr_desc, rrsetidx, rrset, daemon->workspacename, keyname);
                     
   /* Now try all the sigs to try and find one which validates */    /* Now try all the sigs to try and find one which validates */
   for (j = 0; j <sigidx; j++)    for (j = 0; j <sigidx; j++)
Line 701  static int validate_rrset(time_t now, struct dns_heade Line 556  static int validate_rrset(time_t now, struct dns_heade
       const struct nettle_hash *hash;        const struct nettle_hash *hash;
       void *ctx;        void *ctx;
       char *name_start;        char *name_start;
      u32 nsigttl;      u32 nsigttl, ttl, orig_ttl;
               
       p = sigs[j];        p = sigs[j];
         GETLONG(ttl, p);
       GETSHORT(rdlen, p); /* rdlen >= 18 checked previously */        GETSHORT(rdlen, p); /* rdlen >= 18 checked previously */
       psav = p;        psav = p;
               
Line 718  static int validate_rrset(time_t now, struct dns_heade Line 574  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      if ((time_check && !check_date_range(curtime, sig_inception, sig_expiration)) ||
         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) || 
           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;
      
        if (ttl_out)
          {
            /* 4035 5.3.3 rules on TTLs */
            if (orig_ttl < ttl)
              ttl = orig_ttl;
            
            if (time_check && difftime(sig_expiration, curtime) < ttl)
              ttl = difftime(sig_expiration, curtime);
 
            *ttl_out = ttl;
          }
        
       sig = p;        sig = p;
       sig_len = rdlen - (p - psav);        sig_len = rdlen - (p - psav);
                               
Line 762  static int validate_rrset(time_t now, struct dns_heade Line 605  static int validate_rrset(time_t now, struct dns_heade
       wire_len = to_wire(keyname);        wire_len = to_wire(keyname);
       hash->update(ctx, (unsigned int)wire_len, (unsigned char*)keyname);        hash->update(ctx, (unsigned int)wire_len, (unsigned char*)keyname);
       from_wire(keyname);        from_wire(keyname);
   
   #define RRBUFLEN 128 /* Most RRs are smaller than this. */
               
       for (i = 0; i < rrsetidx; ++i)        for (i = 0; i < rrsetidx; ++i)
         {          {
          int seg;          int j;
          unsigned char *end, *cp;          struct rdata_state state;
          u16 len, *dp;          u16 len;
           unsigned char rrbuf[RRBUFLEN];
                       
           p = rrset[i];            p = rrset[i];
             
           if (!extract_name(header, plen, &p, name, 1, 10))             if (!extract_name(header, plen, &p, name, 1, 10)) 
             return STAT_BOGUS;              return STAT_BOGUS;
   
Line 778  static int validate_rrset(time_t now, struct dns_heade Line 625  static int validate_rrset(time_t now, struct dns_heade
           /* if more labels than in RRsig name, hash *.<no labels in rrsig labels field>  4035 5.3.2 */            /* if more labels than in RRsig name, hash *.<no labels in rrsig labels field>  4035 5.3.2 */
           if (labels < name_labels)            if (labels < name_labels)
             {              {
              int k;              for (j = name_labels - labels; j != 0; j--)
              for (k = name_labels - labels; k != 0; k--)                {
                while (*name_start != '.' && *name_start != 0)                  while (*name_start != '.' && *name_start != 0)
                  name_start++;                    name_start++;
                   if (j != 1 && *name_start == '.')
                     name_start++;
                 }
               
               if (wildcard_out)
                 *wildcard_out = name_start+1;
 
               name_start--;                name_start--;
               *name_start = '*';                *name_start = '*';
             }              }
Line 790  static int validate_rrset(time_t now, struct dns_heade Line 644  static int validate_rrset(time_t now, struct dns_heade
           hash->update(ctx, (unsigned int)wire_len, (unsigned char *)name_start);            hash->update(ctx, (unsigned int)wire_len, (unsigned char *)name_start);
           hash->update(ctx, 4, p); /* class and type */            hash->update(ctx, 4, p); /* class and type */
           hash->update(ctx, 4, (unsigned char *)&nsigttl);            hash->update(ctx, 4, (unsigned char *)&nsigttl);
          
          p += 8; /* skip class, type, ttl */          p += 8; /* skip type, class, ttl */
           GETSHORT(rdlen, p);            GETSHORT(rdlen, p);
           if (!CHECK_LEN(header, p, plen, rdlen))            if (!CHECK_LEN(header, p, plen, rdlen))
             return STAT_BOGUS;               return STAT_BOGUS; 
          
          end = p + rdlen;          /* Optimisation for RR types which need no cannonicalisation.
                       This includes DNSKEY DS NSEC and NSEC3, which are also long, so
          /* canonicalise rdata and calculate length of same, use name buffer as workspace */             it saves lots of calls to get_rdata, and avoids the pessimal
          cp = p;             segmented insertion, even with a small rrbuf[].
          dp = rr_desc;             
          for (len = 0; (seg = get_rdata(header, plen, end, name, &cp, &dp)) != 0; len += seg);             If canonicalisation is not needed, a simple insertion into the hash works.
          len += end - cp;          */
          len = htons(len);          if (*rr_desc == (u16)-1)
          hash->update(ctx, 2, (unsigned char *)&len);             {
                        len = htons(rdlen);
          /* Now canonicalise again and digest. */              hash->update(ctx, 2, (unsigned char *)&len);
          cp = p;              hash->update(ctx, rdlen, p);
          dp = rr_desc;            }
          while ((seg = get_rdata(header, plen, end, name, &cp, &dp)))          else
            hash->update(ctx, seg, (unsigned char *)name);            {
          if (cp != end)              /* canonicalise rdata and calculate length of same, use 
            hash->update(ctx, end - cp, cp);                 name buffer as workspace for get_rdata. */
               state.ip = p;
               state.op = NULL;
               state.desc = rr_desc;
               state.buff = name;
               state.end = p + rdlen;
               
               for (j = 0; get_rdata(header, plen, &state); j++)
                 if (j < RRBUFLEN)
                   rrbuf[j] = *state.op;
               
               len = htons((u16)j);
               hash->update(ctx, 2, (unsigned char *)&len); 
               
               /* If the RR is shorter than RRBUFLEN (most of them, in practice)
                  then we can just digest it now. If it exceeds RRBUFLEN we have to
                  go back to the start and do it in chunks. */
               if (j >= RRBUFLEN)
                 {
                   state.ip = p;
                   state.op = NULL;
                   state.desc = rr_desc;
                   
                   for (j = 0; get_rdata(header, plen, &state); j++)
                     {
                       rrbuf[j] = *state.op;
                       
                       if (j == RRBUFLEN - 1)
                         {
                           hash->update(ctx, RRBUFLEN, rrbuf);
                           j = -1;
                         }
                     }
                 }
               
               if (j != 0)
                 hash->update(ctx, j, rrbuf);
             }
         }          }
             
       hash->digest(ctx, hash->digest_size, digest);        hash->digest(ctx, hash->digest_size, digest);
Line 842  static int validate_rrset(time_t now, struct dns_heade Line 733  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, rdlen, flags, algo, valid, keytag;
   unsigned long ttl, sig_ttl;
   struct blockdata *key;    struct blockdata *key;
  struct all_addr a;  union all_addr a;
   
   if (ntohs(header->qdcount) != 1 ||    if (ntohs(header->qdcount) != 1 ||
         RCODE(header) == SERVFAIL || RCODE(header) == REFUSED ||
       !extract_name(header, plen, &p, name, 1, 4))        !extract_name(header, plen, &p, name, 1, 4))
     return STAT_BOGUS;      return STAT_BOGUS;
   
   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 770  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 822  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 841  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) &&
                  (ds_digest = blockdata_retrieve(recp1->addr.key.keydata, recp1->addr.ds.keylen, NULL)) &&                  recp1->addr.ds.keylen == (int)hash->digest_size &&
                   (ds_digest = blockdata_retrieve(recp1->addr.ds.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, &sig_ttl) == STAT_SECURE)
                 {                  {
                   valid = 1;                    valid = 1;
                   break;                    break;
Line 968  int dnssec_validate_by_ds(time_t now, struct dns_heade Line 860  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 869  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);
           GETLONG(ttl, p);            GETLONG(ttl, p);
           GETSHORT(rdlen, p);            GETSHORT(rdlen, p);
   
             /* TTL may be limited by sig. */
             if (sig_ttl < ttl)
               ttl = sig_ttl;
                           
           if (!CHECK_LEN(header, p, plen, rdlen))            if (!CHECK_LEN(header, p, plen, rdlen))
             return STAT_BOGUS; /* bad packet */              return STAT_BOGUS; /* bad packet */
Line 1002  int dnssec_validate_by_ds(time_t now, struct dns_heade Line 898  int dnssec_validate_by_ds(time_t now, struct dns_heade
                   algo = *p++;                    algo = *p++;
                   keytag = dnskey_keytag(algo, flags, p, rdlen - 4);                    keytag = dnskey_keytag(algo, flags, p, rdlen - 4);
                                       
                   /* Cache needs to known class for DNSSEC stuff */  
                   a.addr.dnssec.class = class;  
                     
                   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)))                      a.key.keylen = rdlen - 4;
                        blockdata_free(key);                      a.key.keydata = key;
                      else                      a.key.algo = algo;
                       a.key.keytag = keytag;
                       a.key.flags = flags;
                       
                       if (!cache_insert(name, &a, class, now, ttl, F_FORWARD | F_DNSKEY | F_DNSSECOK))
                         {                          {
                          a.addr.keytag = keytag;                          blockdata_free(key);
                          log_query(F_KEYTAG | F_UPSTREAM, name, &a, "DNSKEY keytag %u");                          return STAT_BOGUS;
                           
                          recp1->addr.key.keylen = rdlen - 4; 
                          recp1->addr.key.keydata = key; 
                          recp1->addr.key.algo = algo; 
                          recp1->addr.key.keytag = keytag; 
                          recp1->addr.key.flags = flags; 
                         }                          }
                    }                      else
                } 
              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)))                          a.log.keytag = keytag;
                            blockdata_free(key);                          a.log.algo = algo;
                           if (algo_digest_name(algo))
                             log_query(F_NOEXTRA | F_KEYTAG | F_UPSTREAM, name, &a, "DNSKEY keytag %hu, algo %hu");
                           else                            else
                            {                            log_query(F_NOEXTRA | F_KEYTAG | F_UPSTREAM, name, &a, "DNSKEY keytag %hu, algo %hu (not supported)");
                              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 932  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, neg_ttl = 0;
   int aclass, atype, rdlen;
   unsigned long ttl;
   union 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 967  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, &neg_ttl);
 
  if (val == STAT_NO_SIG) 
    val = STAT_INSECURE; 
       
     if (rc == STAT_INSECURE)
       {
         my_syslog(LOG_WARNING, _("Insecure DS reply received for %s, check domain configuration and upstream DNS server DNSSEC support"), name);
         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; 
 
      if (RCODE(header) == NXDOMAIN) 
        flags |= F_NXDOMAIN; 
               
      if (val == STAT_SECURE)      for (i = 0; i < ntohs(header->ancount); i++)
        flags |= F_DNSSECOK; 
       
      for (i = ntohs(header->nscount); i != 0; i--) 
         {          {
          if (!(p = skip_name(p, header, plen, 0)))          if (!(rc = extract_name(header, plen, &p, name, 0, 10)))
            return STAT_BOGUS;            return STAT_BOGUS; /* bad packet */
                       
          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)  
             {  
               p += rdlen;  
               continue;  
             }  
              
           if (ttl < minttl)  
             minttl = ttl;  
                       
          /* MNAME */          if (aclass == class && atype == T_DS && rc == 1)
          if (!(p = skip_name(p, header, plen, 0)))            { 
            return STAT_BOGUS;              int algo, digest, keytag;
          /* RNAME */              unsigned char *psave = p;
          if (!(p = skip_name(p, header, plen, 20)))              struct blockdata *key;
            return STAT_BOGUS;           
          p += 16; /* SERIAL REFRESH RETRY EXPIRE */              if (rdlen < 4)
                          return STAT_BOGUS; /* bad packet */
          GETLONG(ttl, p); /* minTTL */              
          if (ttl < minttl)              GETSHORT(keytag, p);
            minttl = ttl;              algo = *p++;
                        digest = *p++;
          break;              
               if ((key = blockdata_alloc((char*)p, rdlen - 4)))
                 {
                   a.ds.digest = digest;
                   a.ds.keydata = key;
                   a.ds.algo = algo;
                   a.ds.keytag = keytag;
                   a.ds.keylen = rdlen - 4;
 
                   if (!cache_insert(name, &a, class, now, ttl, F_FORWARD | F_DS | F_DNSSECOK))
                     {
                       blockdata_free(key);
                       return STAT_BOGUS;
                     }
                   else
                     {
                       a.log.keytag = keytag;
                       a.log.algo = algo;
                       a.log.digest = digest;
                       if (ds_digest_name(digest) && algo_digest_name(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)");
                     } 
                 }
               
               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;
               
         if (RCODE(header) == NXDOMAIN)
           flags |= F_NXDOMAIN;
               
      if (i != 0)      /* We only cache validated DS records, DNSSECOK flag hijacked 
        {         to store presence/absence of NS. */
          cache_start_insert();      if (nons)
         flags &= ~F_DNSSECOK;
       
       cache_start_insert();
                       
          a.addr.dnssec.class = class;      /* Use TTL from NSEC for negative cache entries */
          cache_insert(name, &a, now, ttl, flags);      if (!cache_insert(name, NULL, class, now, neg_ttl, flags))
                  return STAT_BOGUS;
          cache_end_insert();       
        }      cache_end_insert();  
      
      return (val == STAT_SECURE) ? STAT_NO_DS : STAT_INSECURE;       log_query(F_NOEXTRA | F_UPSTREAM, name, NULL, nons ? "no DS/cut" : "no DS");
     }      }
      
  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 1143  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.static int prove_non_existence_nsec(struct dns_header *header, size_t plen, unsigned char **nsecs, unsigned char **labels, int nsec_count,
   return an array of pointers to them. */                                    char *workspace1_in, char *workspace2, char *name, int type, int *nons)
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,  
                                     char *workspace1, char *workspace2, char *name, int type)  
 {  
   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++)
     {      {
         char *workspace1 = workspace1_in;
         int sig_labels, name_labels;
   
       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;
      
       /* If NSEC comes from wildcard expansion, use original wildcard
          as name for computation. */
       sig_labels = *labels[i];
       name_labels = count_labels(workspace1);
 
       if (sig_labels < name_labels)
         {
           int k;
           for (k = name_labels - sig_labels; k != 0; k--)
             {
               while (*workspace1 != '.' && *workspace1 != 0)
                 workspace1++;
               if (k != 1 && *workspace1 == '.')
                 workspace1++;
             }
           
           workspace1--;
           *workspace1 = '*';
         }
           
       rc = hostname_cmp(workspace1, name);        rc = hostname_cmp(workspace1, name);
               
       if (rc == 0)        if (rc == 0)
         {          {
           /* 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. For the root DS, 
                    this is expected. */
                 if (name_labels != 0 && 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; /* finished checking */
                 }                  }
                               
               rdlen -= p[1];                rdlen -= p[1];
               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 1325  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 name_labels)
   {
     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. For the root DS, 
                          this is expected.  */
                       if (name_labels != 0 && 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; /* finished 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 1454  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 1512  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 1521  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, count_labels(name)))
     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 1537  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 1562  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, 1))
     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_nsec3_coverage(header, plen, digest_len, digest, type, workspace1, workspace2, nsecs, nsec_count, NULL, 1))
         return 0;
     }
   
   return 1;
 }
 
 static int prove_non_existence(struct dns_header *header, size_t plen, char *keyname, char *name, int qtype, int qclass, char *wildname, int *nons, int *nsec_ttl)
 {
   static unsigned char **nsecset = NULL, **rrsig_labels = NULL;
   static int nsecset_sz = 0, rrsig_labels_sz = 0;
   
   int type_found = 0;
   unsigned char *auth_start, *p = skip_questions(header, plen);
   int type, class, rdlen, i, nsecs_found;
   unsigned long ttl;
   
   /* Move to NS section */
   if (!p || !(p = skip_section(p, ntohs(header->ancount), header, plen)))
     return 0;
 
   auth_start = p;
   
   for (nsecs_found = 0, i = 0; i < ntohs(header->nscount); i++)
     {
       unsigned char *pstart = p;
       
       if (!extract_name(header, plen, &p, daemon->workspacename, 1, 10))
         return 0;
           
       GETSHORT(type, p); 
       GETSHORT(class, p);
       GETLONG(ttl, p);
       GETSHORT(rdlen, p);
 
       if (class == qclass && (type == T_NSEC || type == T_NSEC3))
         {          {
          if (!CHECK_LEN(header, p, plen, rdlen))          if (nsec_ttl)
            return STAT_BOGUS;            {
               /* Limit TTL with sig TTL */
               if (daemon->rr_status[ntohs(header->ancount) + i] < ttl)
                 ttl = daemon->rr_status[ntohs(header->ancount) + i];
               *nsec_ttl = ttl;
             }
                       
          if (p[0] == type >> 8)          /* 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; 
           
           if (type == T_NSEC)
             {              {
              /* Does the NSEC3 say our type exists? */              /* If we're looking for NSECs, find the corresponding SIGs, to 
              if (offset < p[1] && (p[offset+2] & mask) != 0)                 extract the labels value, which we need in case the NSECs
                return STAT_BOGUS;                 are the result of wildcard expansion.
                  Note that the NSEC may not have been validated yet
                  so if there are multiple SIGs, make sure the label value
                  is the same in all, to avoid be duped by a rogue one.
                  If there are no SIGs, that's an error */
               unsigned char *p1 = auth_start;
               int res, j, rdlen1, type1, class1;
                               
              break; /* finshed checking */              if (!expand_workspace(&rrsig_labels, &rrsig_labels_sz, nsecs_found))
                 return 0;
               
               rrsig_labels[nsecs_found] = NULL;
               
               for (j = ntohs(header->nscount); j != 0; j--)
                 {
                   if (!(res = extract_name(header, plen, &p1, daemon->workspacename, 0, 10)))
                     return 0;
 
                    GETSHORT(type1, p1); 
                    GETSHORT(class1, p1);
                    p1 += 4; /* TTL */
                    GETSHORT(rdlen1, p1);
 
                    if (!CHECK_LEN(header, p1, plen, rdlen1))
                      return 0;
                    
                    if (res == 1 && class1 == qclass && type1 == T_RRSIG)
                      {
                        int type_covered;
                        unsigned char *psav = p1;
                        
                        if (rdlen1 < 18)
                          return 0; /* bad packet */
 
                        GETSHORT(type_covered, p1);
 
                        if (type_covered == T_NSEC)
                          {
                            p1++; /* algo */
                            
                            /* labels field must be the same in every SIG we find. */
                            if (!rrsig_labels[nsecs_found])
                              rrsig_labels[nsecs_found] = p1;
                            else if (*rrsig_labels[nsecs_found] != *p1) /* algo */
                              return 0;
                            }
                        p1 = psav;
                      }
                    
                    if (!ADD_RDLEN(header, p1, plen, rdlen1))
                      return 0;
                 }
 
               /* Must have found at least one sig. */
               if (!rrsig_labels[nsecs_found])
                 return 0;
             }              }
          
          rdlen -= p[1];          nsecset[nsecs_found++] = pstart;   
          p +=  p[1]; 
         }          }
               
      return STAT_SECURE;      if (!ADD_RDLEN(header, p, plen, rdlen))
         return 0;
     }      }
     
     if (type_found == T_NSEC)
       return prove_non_existence_nsec(header, plen, nsecset, rrsig_labels, nsecs_found, daemon->workspacename, keyname, name, qtype, nons);
     else if (type_found == T_NSEC3)
       return prove_non_existence_nsec3(header, plen, nsecset, nsecs_found, daemon->workspacename, keyname, name, qtype, wildname, nons);
     else
       return 0;
   }
   
  /* Look for NSEC3 that proves the non-existence of the next-closest encloser *//* Check signing status of name.
  if ((digest_len = hash_name(next_closest, &digest, hash, salt, salt_len, iterations)) == 0)   returns:
    return STAT_BOGUS;   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)
 {
   int name_start = strlen(name); /* for when TA is root */
   struct crec *crecp;
   char *p;
   
  for (i = 0; i < nsec_count; i++)  /* First, work towards the root, looking for a trust anchor.
    if ((p = nsecs[i]))     This can either be one configured, or one previously cached.
      {     We can assume, if we don't find one first, that there is
        if (!extract_name(header, plen, &p, workspace1, 1, 0) ||     a trust anchor at the root. */
            !(base32_len = base32_decode(workspace1, (unsigned char *)workspace2)))  for (p = name; p; p = strchr(p, '.'))
          return STAT_BOGUS;    {
                 if (*p == '.')
        p += 15 + salt_len; /* class, type, TTL, rdlen, algo, flags, iterations, salt_len, salt */        p++;
        hash_len = *p++; /* p now points to next hashed name */
       if (cache_find_by_name(NULL, p, now, F_DS))
        if (!CHECK_LEN(header, p, plen, hash_len))        {
          return STAT_BOGUS;          name_start = p - name;
                  break;
        if (digest_len == base32_len && hash_len == base32_len)        }
          {    }
            if (memcmp(workspace2, digest, digest_len) <= 0)
              {  /* Now work away from the trust anchor */
                /* Normal case, hash falls between NSEC3 name-hash and next domain name-hash,  while (1)
                   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)      strcpy(keyname, &name[name_start]);
                  return STAT_SECURE;      
              }      if (!(crecp = cache_find_by_name(NULL, keyname, now, F_DS)))
            else         return STAT_NEED_DS;
              {      
                /* wrap around case, name falls between start and next domain name */       /* F_DNSSECOK misused in DS cache records to non-existence of NS record.
                if (memcmp(workspace2, p, digest_len) > 0 && memcmp(p, digest, digest_len) > 0)          F_NEG && !F_DNSSECOK implies that we've proved there's no DS record here,
                  return STAT_SECURE;          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)
          {
  /* Finally, check that there's no seat of wildcard synthesis */          if (crecp->flags & F_DNSSECOK)
  if (!(wildcard = strchr(next_closest, '.')) || wildcard == next_closest)            return STAT_INSECURE; /* proved no DS here */
    return STAT_BOGUS;        }
        else
  wildcard--;        {
  *wildcard = '*';          /* 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
  if ((digest_len = hash_name(wildcard, &digest, hash, salt, salt_len, iterations)) == 0)             appears in the DS, then RRSIGs for that algo MUST
    return STAT_BOGUS;             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
  for (i = 0; i < nsec_count; i++)             to assume we can validate the zone and if we can't later,
    if ((p = nsecs[i]))             because an RRSIG is missing we return BOGUS.
      {          */
        if (!extract_name(header, plen, &p, workspace1, 1, 0) ||          do 
            !(base32_len = base32_decode(workspace1, (unsigned char *)workspace2)))            {
          return STAT_BOGUS;              if (crecp->uid == (unsigned int)class &&
                             ds_digest_name(crecp->addr.ds.digest) &&
        p += 15 + salt_len; /* class, type, TTL, rdlen, algo, flags, iterations, salt_len, salt */                  algo_digest_name(crecp->addr.ds.algo))
        hash_len = *p++; /* p now points to next hashed name */                break;
             }
        if (!CHECK_LEN(header, p, plen, hash_len))          while ((crecp = cache_find_by_name(crecp, keyname, now, F_DS)));
          return STAT_BOGUS;
                  if (!crecp)
        if (digest_len == base32_len && hash_len == base32_len)            return STAT_INSECURE;
          {        }
            if (memcmp(workspace2, digest, digest_len) <= 0)
              {      if (name_start == 0)
                /* Normal case, hash falls between NSEC3 name-hash and next domain name-hash,        break;
                   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)      for (p = &name[name_start-2]; (*p != '.') && (p != name); p--);
                  return STAT_SECURE;      
              }      if (p != name)
            else         p++;
              {      
                /* wrap around case, name falls between start and next domain name */      name_start = p - name;
                if (memcmp(workspace2, p, digest_len) > 0 && memcmp(p, digest, digest_len) > 0)    } 
                  return STAT_SECURE;
              }  return STAT_SECURE;
          } 
      } 
   
  return STAT_BOGUS; 
 }  }
           
/* Validate all the RRsets in the answer and authority sections of the reply (4035:3.2.3) *//* Validate all the RRsets in the answer and authority sections of the reply (4035:3.2.3) 
/* Returns are the same as validate_rrset, plus the class if the missing key is in *class */   Return code:
int dnssec_validate_reply(time_t now, struct dns_header *header, size_t plen, char *name, char *keyname, int *class, int *neganswer)   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)
 
    daemon->rr_status points to a char array which corressponds to the RRs in the 
    answer and auth sections. This is set to 1 for each RR which is validated, and 0 for any which aren't.
 
    When validating replies to DS records, we're only interested in the NSEC{3} RRs in the auth section.
    Other RRs in that section missing sigs will not cause am INSECURE reply. We determine this mode
    is the nons argument is non-NULL.
 */
 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, int *nsec_ttl)
 {  {
  unsigned char *ans_start, *qname, *p1, *p2, **nsecs;  static unsigned char **targets = NULL;
  int type1, class1, rdlen1, type2, class2, rdlen2, qclass, qtype;  static int target_sz = 0;
  int i, j, rc, nsec_count, cname_count = CNAME_CHAIN; 
  int nsec_type = 0, have_answer = 0; 
   
     unsigned char *ans_start, *p1, *p2;
     int type1, class1, rdlen1 = 0, type2, class2, rdlen2, qclass, qtype, targetidx;
     int i, j, rc = STAT_INSECURE;
     int secure = STAT_SECURE;
   
     /* extend rr_status if necessary */
     if (daemon->rr_status_sz < ntohs(header->ancount) + ntohs(header->nscount))
       {
         unsigned long *new = whine_malloc(sizeof(*daemon->rr_status) * (ntohs(header->ancount) + ntohs(header->nscount) + 64));
   
         if (!new)
           return STAT_BOGUS;
   
         free(daemon->rr_status);
         daemon->rr_status = new;
         daemon->rr_status_sz = ntohs(header->ancount) + ntohs(header->nscount) + 64;
       }
     
     memset(daemon->rr_status, 0, sizeof(*daemon->rr_status) * daemon->rr_status_sz);
     
   if (neganswer)    if (neganswer)
     *neganswer = 0;      *neganswer = 0;
       
Line 1674  int dnssec_validate_reply(time_t now, struct dns_heade Line 1850  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 (i != 0 && !ADD_RDLEN(header, p1, plen, rdlen1))
           return STAT_BOGUS;
         
       if (!extract_name(header, plen, &p1, name, 1, 10))        if (!extract_name(header, plen, &p1, name, 1, 10))
         return STAT_BOGUS; /* bad packet */          return STAT_BOGUS; /* bad packet */
               
Line 1746  int dnssec_validate_reply(time_t now, struct dns_heade Line 1909  int dnssec_validate_reply(time_t now, struct dns_heade
       GETSHORT(rdlen1, p1);        GETSHORT(rdlen1, p1);
               
       /* Don't try and validate RRSIGs! */        /* Don't try and validate RRSIGs! */
      if (type1 != T_RRSIG)      if (type1 == T_RRSIG)
         continue;
       
       /* Check if we've done this RRset already */
       for (p2 = ans_start, j = 0; j < i; j++)
         {          {
          /* Check if we've done this RRset already */          if (!(rc = extract_name(header, plen, &p2, name, 0, 10)))
          for (p2 = ans_start, j = 0; j < i; j++)            return STAT_BOGUS; /* bad packet */
            { 
              if (!(rc = extract_name(header, plen, &p2, name, 0, 10))) 
                return STAT_BOGUS; /* bad packet */ 
               
              GETSHORT(type2, p2); 
              GETSHORT(class2, p2); 
              p2 += 4; /* TTL */ 
              GETSHORT(rdlen2, p2); 
               
              if (type2 == type1 && class2 == class1 && rc == 1) 
                break; /* Done it before: name, type, class all match. */ 
               
              if (!ADD_RDLEN(header, p2, plen, rdlen2)) 
                return STAT_BOGUS; 
            } 
                       
             GETSHORT(type2, p2);
             GETSHORT(class2, p2);
             p2 += 4; /* TTL */
             GETSHORT(rdlen2, p2);
             
             if (type2 == type1 && class2 == class1 && rc == 1)
               break; /* Done it before: name, type, class all match. */
             
             if (!ADD_RDLEN(header, p2, plen, rdlen2))
               return STAT_BOGUS;
           }
         
         /* Done already: copy the validation status */
         if (j != i)
           daemon->rr_status[i] = daemon->rr_status[j];
         else
           {
           /* Not done, validate now */            /* Not done, validate now */
          if (j == i)          int sigcnt, rrcnt;
           char *wildname;
           
           if (!explore_rrset(header, plen, class1, type1, name, keyname, &sigcnt, &rrcnt))
             return STAT_BOGUS;
           
           /* No signatures for RRset. We can be configured to assume this is OK and return an INSECURE result. */
           if (sigcnt == 0)
             {              {
              int ttl, keytag, algo, digest, type_covered;              /* NSEC and NSEC3 records must be signed. We make this assumption elsewhere. */
              unsigned char *psave;              if (type1 == T_NSEC || type1 == T_NSEC3)
              struct all_addr a;                rc = STAT_INSECURE;
              struct blockdata *key;              else if (nons && i >= ntohs(header->ancount))
              struct crec *crecp;                /* If we're validating a DS reply, rather than looking for the value of AD bit,
                                 we only care that NSEC and NSEC3 RRs in the auth section are signed. 
              rc = validate_rrset(now, header, plen, class1, type1, name, keyname, NULL, 0, 0, 0);                   Return SECURE even if others (SOA....) are not. */
                              rc = STAT_SECURE;
              if (rc == STAT_SECURE_WILDCARD)              else
                 {                  {
                  /* An attacker replay a wildcard answer with a different                  /* unsigned RRsets in auth section are not BOGUS, but do make reply insecure. */
                     answer and overlay a genuine RR. To prove this                  if (check_unsigned && i < ntohs(header->ancount))
                     hasn't happened, the answer must prove that                    {
                     the gennuine record doesn't exist. Check that here. */                      rc = zone_status(name, class1, keyname, now);
                  if (!nsec_type && !(nsec_type = find_nsec_records(header, plen, &nsecs, &nsec_count, class1)))                      if (rc == STAT_SECURE)
                    return STAT_BOGUS; /* No NSECs or bad packet */                        rc = STAT_BOGUS;
                       if (class)
                         *class = class1; /* Class for NEED_DS or NEED_KEY */
                     }
                   else 
                     rc = STAT_INSECURE; 
                                       
                  if (nsec_type == T_NSEC)                  if (rc != STAT_INSECURE)
                    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); 
 
                  if (rc != STAT_SECURE) 
                     return rc;                      return rc;
                }                 }
              else if (rc != STAT_SECURE)            }
           else
             {
               /* 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_BOGUS || rc == STAT_NEED_KEY || rc == STAT_NEED_DS)
                 {                  {
                   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 */              /* Zone is insecure, don't need to validate RRset */
              cache_start_insert();              if (rc == STAT_SECURE)
               
              for (p2 = ans_start, j = 0; j < ntohs(header->ancount); j++) 
                 {                  {
                  if (!(rc = extract_name(header, plen, &p2, name, 0, 10)))                  unsigned long sig_ttl;
                    return STAT_BOGUS; /* bad packet */                  rc = validate_rrset(now, header, plen, class1, type1, sigcnt,
                                                            rrcnt, name, keyname, &wildname, NULL, 0, 0, 0, &sig_ttl);
                  GETSHORT(type2, p2); 
                  GETSHORT(class2, p2); 
                  GETLONG(ttl, p2); 
                  GETSHORT(rdlen2, p2); 
                        
                  if (!CHECK_LEN(header, p2, plen, rdlen2)) 
                    return STAT_BOGUS; /* bad packet */ 
                                       
                  if (class2 == class1 && rc == 1)                  if (rc == STAT_BOGUS || rc == STAT_NEED_KEY || rc == STAT_NEED_DS)
                    {                     {
                      psave = p2;                      if (class)
                        *class = class1; /* Class for DS or DNSKEY */
                      if (type1 == T_DS && type2 == T_DS)                      return rc;
                        {                    } 
                          if (rdlen2 < 4) 
                            return STAT_BOGUS; /* bad packet */ 
                           
                          GETSHORT(keytag, p2); 
                          algo = *p2++; 
                          digest = *p2++; 
                           
                          /* Cache needs to known class for DNSSEC stuff */ 
                          a.addr.dnssec.class = class2; 
                           
                          if ((key = blockdata_alloc((char*)p2, rdlen2 - 4))) 
                            { 
                              if (!(crecp = cache_insert(name, &a, now, ttl, F_FORWARD | F_DS | F_DNSSECOK))) 
                                blockdata_free(key); 
                              else 
                                { 
                                  a.addr.keytag = keytag; 
                                  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))                  /* rc is now STAT_SECURE or STAT_SECURE_WILDCARD */
                    return STAT_BOGUS; /* bad packet */ 
                } 
                                       
              cache_end_insert();                  /* Note that RR is validated */
                   daemon->rr_status[i] = sig_ttl;
                    
                   /* Note if we've validated either the answer to the question
                      or the target of a CNAME. Any not noted will need NSEC or
                      to be in unsigned space. */
                   for (j = 0; j <targetidx; j++)
                     if ((p2 = targets[j]))
                       {
                         int rc1;
                         if (!(rc1 = extract_name(header, plen, &p2, name, 0, 10)))
                           return STAT_BOGUS; /* bad packet */
                         
                         if (class1 == qclass && rc1 == 1 && (type1 == T_CNAME || type1 == qtype || qtype == T_ANY ))
                           targets[j] = NULL;
                       }
                   
                   /* An attacker replay a wildcard answer with a different
                      answer and overlay a genuine RR. To prove this
                      hasn't happened, the answer must prove that
                      the genuine record doesn't exist. Check that here. 
                      Note that we may not yet have validated the NSEC/NSEC3 RRsets. 
                      That's not a problem since if the RRsets later fail
                      we'll return BOGUS then. */
                   if (rc == STAT_SECURE_WILDCARD &&
                       !prove_non_existence(header, plen, keyname, name, type1, class1, wildname, NULL, NULL))
                     return STAT_BOGUS;
 
                   rc = STAT_SECURE;
                 }
             }              }
         }          }
   
      if (!ADD_RDLEN(header, p1, plen, rdlen1))      if (rc == STAT_INSECURE)
        return STAT_BOGUS;        secure = STAT_INSECURE;
     }      }
   
  /* 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)  if (secure == STAT_SECURE)
    return STAT_SECURE;    for (j = 0; j <targetidx; j++)
           if ((p2 = targets[j]))
  /* NXDOMAIN or NODATA reply, prove that (name, class1, type1) can't exist */ 
  /* First marshall the NSEC records, if we've not done it previously */ 
  if (!nsec_type && !(nsec_type = find_nsec_records(header, plen, &nsecs, &nsec_count, 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. 
   Needed for proving answer in unsigned space. 
   Return STAT_NEED_*  
          STAT_BOGUS - error 
          STAT_INSECURE - name of first non-secure record in name  
*/ 
int dnssec_chase_cname(time_t now, struct dns_header *header, size_t plen, char *name, char *keyname) 
{ 
  unsigned char *p = (unsigned char *)(header+1); 
  int type, class, qclass, rdlen, j, rc; 
  int cname_count = CNAME_CHAIN; 
 
  /* Get question */ 
  if (!extract_name(header, plen, &p, name, 1, 4)) 
    return STAT_BOGUS; 
   
  p +=2; /* type */ 
  GETSHORT(qclass, p); 
 
  while (1) 
    { 
      for (j = ntohs(header->ancount); j != 0; j--)  
         {          {
          if (!(rc = extract_name(header, plen, &p, name, 0, 10)))          if (neganswer)
             *neganswer = 1;
           
           if (!extract_name(header, plen, &p2, name, 1, 10))
             return STAT_BOGUS; /* bad packet */              return STAT_BOGUS; /* bad packet */
                       
          GETSHORT(type, p);           /* NXDOMAIN or NODATA reply, unanswered question is (name, qclass, qtype) */
          GETSHORT(class, p);          
          p += 4; /* TTL */          /* For anything other than a DS record, this situation is OK if either
          GETSHORT(rdlen, p);             the answer is in an unsigned zone, or there's a NSEC records. */
          if (!prove_non_existence(header, plen, keyname, name, qtype, qclass, NULL, nons, nsec_ttl))
          /* Not target, loop */ 
          if (rc == 2 || qclass != class) 
             {              {
              if (!ADD_RDLEN(header, p, plen, rdlen))              /* Empty DS without NSECS */
               if (qtype == T_DS)
                 return STAT_BOGUS;                  return STAT_BOGUS;
              continue;              
               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 */
             }              }
             
           /* 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 secure;
      return STAT_INSECURE;   
    } 
 }  }
   
   
Line 2007  int dnskey_keytag(int alg, int flags, unsigned char *k Line 2096  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, int edns_pktsz)
 {  {
   unsigned char *p;    unsigned char *p;
  char *types = querystr("dnssec-query", type);  size_t ret;
   
   if (addr->sa.sa_family == AF_INET)   
     log_query(F_DNSSEC | F_IPV4, name, (struct all_addr *)&addr->in.sin_addr, types);  
 #ifdef HAVE_IPV6  
   else  
     log_query(F_DNSSEC | F_IPV6, name, (struct all_addr *)&addr->in6.sin6_addr, types);  
 #endif  
     
   header->qdcount = htons(1);    header->qdcount = htons(1);
   header->ancount = htons(0);    header->ancount = htons(0);
   header->nscount = htons(0);    header->nscount = htons(0);
Line 2034  size_t dnssec_generate_query(struct dns_header *header Line 2117  size_t dnssec_generate_query(struct dns_header *header
   
   p = (unsigned char *)(header+1);    p = (unsigned char *)(header+1);
                   
  p = do_rfc1035_name(p, name);  p = do_rfc1035_name(p, name, NULL);
   *p++ = 0;    *p++ = 0;
   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) 
{ 
  int q; 
  unsigned int len; 
  unsigned char *p = (unsigned char *)(header+1); 
  const struct nettle_hash *hash; 
  void *ctx; 
  unsigned char *digest; 
   
  if (!(hash = hash_find("sha1")) || !hash_init(hash, &ctx, &digest)) 
    return NULL; 
   
  for (q = ntohs(header->qdcount); q != 0; q--)  
    { 
      if (!extract_name(header, plen, &p, name, 1, 4)) 
        break; /* bad packet */ 
       
      len = to_wire(name); 
      hash->update(ctx, len, (unsigned char *)name); 
      /* CRC the class and type as well */ 
      hash->update(ctx, 4, p); 
 
      p += 4; 
      if (!CHECK_LEN(header, p, plen, 0)) 
        break; /* bad packet */ 
    } 
   
  hash->digest(ctx, hash->digest_size, digest); 
  return digest; 
 }  }
   
 #endif /* HAVE_DNSSEC */  #endif /* HAVE_DNSSEC */

Removed from v.1.1  
changed lines
  Added in v.1.1.1.3


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