Imported Upstream version 2.88
[platform/upstream/dnsmasq.git] / src / rfc1035.c
index 6fc4f26..5c0df56 100644 (file)
@@ -1,4 +1,4 @@
-/* dnsmasq is Copyright (c) 2000-2021 Simon Kelley
+/* dnsmasq is Copyright (c) 2000-2022 Simon Kelley
 
    This program is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
@@ -526,7 +526,7 @@ static int print_txt(struct dns_header *header, const size_t qlen, char *name,
        }
 
       *p3 = 0;
-      log_query(secflag | F_FORWARD | F_UPSTREAM, name, NULL, (char*)p1);
+      log_query(secflag | F_FORWARD | F_UPSTREAM, name, NULL, (char*)p1, 0);
       /* restore */
       memmove(p1 + 1, p1, i);
       *p1 = len;
@@ -538,10 +538,12 @@ static int print_txt(struct dns_header *header, const size_t qlen, char *name,
 /* Note that the following code can create CNAME chains that don't point to a real record,
    either because of lack of memory, or lack of SOA records.  These are treated by the cache code as 
    expired and cleaned out that way. 
-   Return 1 if we reject an address because it look like part of dns-rebinding attack. */
+   Return 1 if we reject an address because it look like part of dns-rebinding attack. 
+   Return 2 if the packet is malformed.
+*/
 int extract_addresses(struct dns_header *header, size_t qlen, char *name, time_t now, 
-                     char **ipsets, int is_sign, int check_rebind, int no_cache_dnssec,
-                     int secure, int *doctored)
+                     struct ipsets *ipsets, struct ipsets *nftsets, int is_sign, int check_rebind,
+                     int no_cache_dnssec, int secure, int *doctored)
 {
   unsigned char *p, *p1, *endrr, *namep;
   int j, qtype, qclass, aqtype, aqclass, ardlen, res, searched_soa = 0;
@@ -552,6 +554,11 @@ int extract_addresses(struct dns_header *header, size_t qlen, char *name, time_t
 #else
   (void)ipsets; /* unused */
 #endif
+#ifdef HAVE_NFTSET
+  char **nftsets_cur;
+#else
+  (void)nftsets; /* unused */
+#endif
   int found = 0, cname_count = CNAME_CHAIN;
   struct crec *cpp = NULL;
   int flags = RCODE(header) == NXDOMAIN ? F_NXDOMAIN : 0;
@@ -584,7 +591,7 @@ int extract_addresses(struct dns_header *header, size_t qlen, char *name, time_t
   namep = p = (unsigned char *)(header+1);
   
   if (ntohs(header->qdcount) != 1 || !extract_name(header, qlen, &p, name, 1, 4))
-    return 0; /* bad packet */
+    return 2; /* bad packet */
   
   GETSHORT(qtype, p); 
   GETSHORT(qclass, p);
@@ -602,13 +609,13 @@ int extract_addresses(struct dns_header *header, size_t qlen, char *name, time_t
        {
        cname_loop:
          if (!(p1 = skip_questions(header, qlen)))
-           return 0;
+           return 2;
          
          for (j = 0; j < ntohs(header->ancount); j++) 
            {
              int secflag = 0;
              if (!(res = extract_name(header, qlen, &p1, name, 0, 10)))
-               return 0; /* bad packet */
+               return 2; /* bad packet */
              
              GETSHORT(aqtype, p1); 
              GETSHORT(aqclass, p1);
@@ -643,10 +650,10 @@ int extract_addresses(struct dns_header *header, size_t qlen, char *name, time_t
 #endif
 
                  if (aqtype == T_CNAME)
-                   log_query(secflag | F_CNAME | F_FORWARD | F_UPSTREAM, name, NULL, NULL);
+                   log_query(secflag | F_CNAME | F_FORWARD | F_UPSTREAM, name, NULL, NULL, 0);
                  
                  if (!extract_name(header, qlen, &p1, name, 1, 0))
-                   return 0;
+                   return 2;
                  
                  if (aqtype == T_CNAME)
                    {
@@ -661,10 +668,10 @@ int extract_addresses(struct dns_header *header, size_t qlen, char *name, time_t
                  found = 1; 
                  
                  if (!name_encoding)
-                   log_query(secflag | F_FORWARD | F_UPSTREAM, name, NULL, querystr(NULL, aqtype));
+                   log_query(secflag | F_FORWARD | F_UPSTREAM, name, NULL, NULL, aqtype);
                  else
                    {
-                     log_query(name_encoding | secflag | F_REVERSE | F_UPSTREAM, name, &addr, NULL);
+                     log_query(name_encoding | secflag | F_REVERSE | F_UPSTREAM, name, &addr, NULL, 0);
                      if (insert)
                        cache_insert(name, &addr, C_IN, now, cttl, name_encoding | secflag | F_REVERSE);
                    }
@@ -672,7 +679,7 @@ int extract_addresses(struct dns_header *header, size_t qlen, char *name, time_t
 
              p1 = endrr;
              if (!CHECK_LEN(header, p1, qlen, 0))
-               return 0; /* bad packet */
+               return 2; /* bad packet */
            }
        }
       
@@ -691,7 +698,7 @@ int extract_addresses(struct dns_header *header, size_t qlen, char *name, time_t
              cache_insert(NULL, &addr, C_IN, now, ttl, flags);
            }
          
-         log_query(flags | F_UPSTREAM, name, &addr, NULL);
+         log_query(flags | F_UPSTREAM, name, &addr, NULL, 0);
        }
     }
   else
@@ -717,14 +724,14 @@ int extract_addresses(struct dns_header *header, size_t qlen, char *name, time_t
       
     cname_loop1:
       if (!(p1 = skip_questions(header, qlen)))
-       return 0;
+       return 2;
       
       for (j = 0; j < ntohs(header->ancount); j++) 
        {
          int secflag = 0;
          
          if (!(res = extract_name(header, qlen, &p1, name, 0, 10)))
-           return 0; /* bad packet */
+           return 2; /* bad packet */
          
          GETSHORT(aqtype, p1); 
          GETSHORT(aqclass, p1);
@@ -742,7 +749,7 @@ int extract_addresses(struct dns_header *header, size_t qlen, char *name, time_t
            {
              p1 = endrr;
              if (!CHECK_LEN(header, p1, qlen, 0))
-               return 0; /* bad packet */
+               return 2; /* bad packet */
              continue;
            }
          
@@ -762,7 +769,7 @@ int extract_addresses(struct dns_header *header, size_t qlen, char *name, time_t
              if (!cname_count--)
                return 0; /* looped CNAMES */
              
-             log_query(secflag | F_CNAME | F_FORWARD | F_UPSTREAM, name, NULL, NULL);
+             log_query(secflag | F_CNAME | F_FORWARD | F_UPSTREAM, name, NULL, NULL, 0);
              
              if (insert)
                {
@@ -785,16 +792,19 @@ int extract_addresses(struct dns_header *header, size_t qlen, char *name, time_t
              
              namep = p1;
              if (!extract_name(header, qlen, &p1, name, 1, 0))
-               return 0;
+               return 2;
              
-             goto cname_loop1;
+             if (qtype != T_CNAME)
+               goto cname_loop1;
+
+             found = 1;
            }
          else if (aqtype != qtype)
            {
 #ifdef HAVE_DNSSEC
              if (!option_bool(OPT_DNSSEC_VALID) || aqtype != T_RRSIG)
 #endif
-               log_query(secflag | F_FORWARD | F_UPSTREAM, name, NULL, querystr(NULL, aqtype));
+               log_query(secflag | F_FORWARD | F_UPSTREAM, name, NULL, NULL, aqtype);
            }
          else if (!(flags & F_NXDOMAIN))
            {
@@ -805,25 +815,25 @@ int extract_addresses(struct dns_header *header, size_t qlen, char *name, time_t
                  unsigned char *tmp = namep;
                  
                  if (!CHECK_LEN(header, p1, qlen, 6))
-                   return 0; /* bad packet */
+                   return 2; /* bad packet */
                  GETSHORT(addr.srv.priority, p1);
                  GETSHORT(addr.srv.weight, p1);
                  GETSHORT(addr.srv.srvport, p1);
                  if (!extract_name(header, qlen, &p1, name, 1, 0))
-                   return 0;
+                   return 2;
                  addr.srv.targetlen = strlen(name) + 1; /* include terminating zero */
                  if (!(addr.srv.target = blockdata_alloc(name, addr.srv.targetlen)))
                    return 0;
                  
                  /* we overwrote the original name, so get it back here. */
                  if (!extract_name(header, qlen, &tmp, name, 1, 0))
-                   return 0;
+                   return 2;
                }
              else if (flags & (F_IPV4 | F_IPV6))
                {
                  /* copy address into aligned storage */
                  if (!CHECK_LEN(header, p1, qlen, addrlen))
-                   return 0; /* bad packet */
+                   return 2; /* bad packet */
                  memcpy(&addr, p1, addrlen);
                  
                  /* check for returned address in private space */
@@ -840,14 +850,15 @@ int extract_addresses(struct dns_header *header, size_t qlen, char *name, time_t
                  
 #ifdef HAVE_IPSET
                  if (ipsets && (flags & (F_IPV4 | F_IPV6)))
-                   {
-                     ipsets_cur = ipsets;
-                     while (*ipsets_cur)
-                       {
-                         log_query((flags & (F_IPV4 | F_IPV6)) | F_IPSET, name, &addr, *ipsets_cur);
-                         add_to_ipset(*ipsets_cur++, &addr, flags, 0);
-                       }
-                   }
+                   for (ipsets_cur = ipsets->sets; *ipsets_cur; ipsets_cur++)
+                     if (add_to_ipset(*ipsets_cur, &addr, flags, 0) == 0)
+                       log_query((flags & (F_IPV4 | F_IPV6)) | F_IPSET, ipsets->domain, &addr, *ipsets_cur, 1);
+#endif
+#ifdef HAVE_NFTSET
+                 if (nftsets && (flags & (F_IPV4 | F_IPV6)))
+                   for (nftsets_cur = nftsets->sets; *nftsets_cur; nftsets_cur++)
+                     if (add_to_nftset(*nftsets_cur, &addr, flags, 0) == 0)
+                       log_query((flags & (F_IPV4 | F_IPV6)) | F_IPSET, nftsets->domain, &addr, *nftsets_cur, 0);
 #endif
                }
              
@@ -866,19 +877,30 @@ int extract_addresses(struct dns_header *header, size_t qlen, char *name, time_t
              if (aqtype == T_TXT)
                {
                  if (!print_txt(header, qlen, name, p1, ardlen, secflag))
-                   return 0;
+                   return 2;
                }
              else
-               log_query(flags | F_FORWARD | secflag | F_UPSTREAM, name, &addr, querystr(NULL, aqtype));
+               log_query(flags | F_FORWARD | secflag | F_UPSTREAM, name, &addr, NULL, aqtype);
            }
          
          p1 = endrr;
          if (!CHECK_LEN(header, p1, qlen, 0))
-           return 0; /* bad packet */
+           return 2; /* bad packet */
        }
       
-      if (!found && !option_bool(OPT_NO_NEG))
+      if (!found && (qtype != T_ANY || (flags & F_NXDOMAIN)))
        {
+         if (flags & F_NXDOMAIN)
+           {
+             flags &= ~(F_IPV4 | F_IPV6 | F_SRV);
+             
+             /* Can store NXDOMAIN reply to CNAME or ANY query. */
+             if (qtype == T_CNAME || qtype == T_ANY)
+               insert = 1;
+           }
+         
+         log_query(F_UPSTREAM | F_FORWARD | F_NEG | flags | (secure ? F_DNSSECOK : 0), name, NULL, NULL, 0);
+         
          if (!searched_soa)
            {
              searched_soa = 1;
@@ -887,22 +909,17 @@ int extract_addresses(struct dns_header *header, size_t qlen, char *name, time_t
          
          /* If there's no SOA to get the TTL from, but there is a CNAME 
             pointing at this, inherit its TTL */
-         if (ttl || cpp)
+         if (insert && !option_bool(OPT_NO_NEG) && (ttl || cpp))
            {
              if (ttl == 0)
                ttl = cttl;
              
-             log_query(F_UPSTREAM | F_FORWARD | F_NEG | flags | (secure ? F_DNSSECOK : 0), name, NULL, NULL);
-             
-             if (insert)
+             newc = cache_insert(name, NULL, C_IN, now, ttl, F_FORWARD | F_NEG | flags | (secure ? F_DNSSECOK : 0));   
+             if (newc && cpp)
                {
-                 newc = cache_insert(name, NULL, C_IN, now, ttl, F_FORWARD | F_NEG | flags | (secure ? F_DNSSECOK : 0));       
-                 if (newc && cpp)
-                   {
-                     next_uid(newc);
-                     cpp->addr.cname.target.cache = newc;
-                     cpp->addr.cname.uid = newc->uid;
-                   }
+                 next_uid(newc);
+                 cpp->addr.cname.target.cache = newc;
+                 cpp->addr.cname.uid = newc->uid;
                }
            }
        }
@@ -1080,7 +1097,7 @@ void setup_reply(struct dns_header *header, unsigned int flags, int ede)
       union all_addr a;
       a.log.rcode = REFUSED;
       a.log.ede = ede;
-      log_query(F_CONFIG | F_RCODE, "error", &a, NULL);
+      log_query(F_CONFIG | F_RCODE, "error", &a, NULL, 0);
       SET_RCODE(header, REFUSED);
     }
 }
@@ -1345,8 +1362,15 @@ int add_resource_record(struct dns_header *header, char *limit, int *truncp, int
 #undef CHECK_LIMIT
 }
 
+static int crec_isstale(struct crec *crecp, time_t now)
+{
+  return (!(crecp->flags & F_IMMORTAL)) && difftime(crecp->ttd, now) < 0; 
+}
+
 static unsigned long crec_ttl(struct crec *crecp, time_t now)
 {
+  signed long ttl = difftime(crecp->ttd, now);
+
   /* Return 0 ttl for DHCP entries, which might change
      before the lease expires, unless configured otherwise. */
 
@@ -1355,8 +1379,8 @@ static unsigned long crec_ttl(struct crec *crecp, time_t now)
       int conf_ttl = daemon->use_dhcp_ttl ? daemon->dhcp_ttl : daemon->local_ttl;
       
       /* Apply ceiling of actual lease length to configured TTL. */
-      if (!(crecp->flags & F_IMMORTAL) && (crecp->ttd - now) < conf_ttl)
-       return crecp->ttd - now;
+      if (!(crecp->flags & F_IMMORTAL) && ttl < conf_ttl)
+       return ttl;
       
       return conf_ttl;
     }    
@@ -1365,9 +1389,13 @@ static unsigned long crec_ttl(struct crec *crecp, time_t now)
   if (crecp->flags & F_IMMORTAL)
     return crecp->ttd;
 
+  /* Stale cache entries. */
+  if (ttl < 0)
+    return 0;
+  
   /* Return the Max TTL value if it is lower than the actual TTL */
-  if (daemon->max_ttl == 0 || ((unsigned)(crecp->ttd - now) < daemon->max_ttl))
-    return crecp->ttd - now;
+  if (daemon->max_ttl == 0 || ((unsigned)ttl < daemon->max_ttl))
+    return ttl;
   else
     return daemon->max_ttl;
 }
@@ -1380,7 +1408,8 @@ static int cache_validated(const struct crec *crecp)
 /* return zero if we can't answer from cache, or packet size if we can */
 size_t answer_request(struct dns_header *header, char *limit, size_t qlen,  
                      struct in_addr local_addr, struct in_addr local_netmask, 
-                     time_t now, int ad_reqd, int do_bit, int have_pseudoheader) 
+                     time_t now, int ad_reqd, int do_bit, int have_pseudoheader,
+                     int *stale) 
 {
   char *name = daemon->namebuff;
   unsigned char *p, *ansp;
@@ -1396,6 +1425,9 @@ size_t answer_request(struct dns_header *header, char *limit, size_t qlen,
   size_t len;
   int rd_bit = (header->hb3 & HB3_RD);
 
+  if (stale)
+    *stale = 0;
+  
   /* never answer queries with RD unset, to avoid cache snooping. */
   if (ntohs(header->ancount) != 0 ||
       ntohs(header->nscount) != 0 ||
@@ -1440,36 +1472,64 @@ size_t answer_request(struct dns_header *header, char *limit, size_t qlen,
 
       ans = 0; /* have we answered this question */
 
-      while (--count != 0 && (crecp = cache_find_by_name(NULL, name, now, F_CNAME)))
-       {
-         char *cname_target = cache_get_cname_target(crecp);
-
-         /* If the client asked for DNSSEC  don't use cached data. */
-         if ((crecp->flags & (F_HOSTS | F_DHCP | F_CONFIG)) ||
-             (rd_bit && (!do_bit || cache_validated(crecp))))
-           {
-             if (crecp->flags & F_CONFIG || qtype == T_CNAME)
-               ans = 1;
-
-             if (!(crecp->flags & F_DNSSECOK))
-               sec_data = 0;
-
-             if (!dryrun)
-               {
-                 log_query(crecp->flags, name, NULL, record_source(crecp->uid));
-                 if (add_resource_record(header, limit, &trunc, nameoffset, &ansp, 
-                                         crec_ttl(crecp, now), &nameoffset,
-                                         T_CNAME, C_IN, "d", cname_target))
-                   anscount++;
-               }
-
-           }
-         else
-           return 0; /* give up if any cached CNAME in chain can't be used for DNSSEC reasons. */
+      if (qclass == C_IN)
+       while (--count != 0 && (crecp = cache_find_by_name(NULL, name, now, F_CNAME | F_NXDOMAIN)))
+         {
+           char *cname_target;
+           int stale_flag = 0;
+           
+           if (crec_isstale(crecp, now))
+             {
+               if (stale)
+                 *stale = 1;
+               
+               stale_flag = F_STALE;
+             }
+           
+           if (crecp->flags & F_NXDOMAIN)
+             {
+               if (qtype == T_CNAME)
+                 {
+                  if (!dryrun)
+                    log_query(stale_flag | crecp->flags, name, NULL, record_source(crecp->uid), 0);
+                   auth = 0;
+                   nxdomain = 1;
+                   ans = 1;
+                 }
+               break;
+             }  
 
-         strcpy(name, cname_target);
-       }
-         
+           cname_target = cache_get_cname_target(crecp);
+           
+           /* If the client asked for DNSSEC  don't use cached data. */
+           if ((crecp->flags & (F_HOSTS | F_DHCP | F_CONFIG)) ||
+               (rd_bit && (!do_bit || cache_validated(crecp))))
+             {
+               if (crecp->flags & F_CONFIG || qtype == T_CNAME)
+                 ans = 1;
+               
+               if (!(crecp->flags & F_DNSSECOK))
+                 sec_data = 0;
+               
+               if (!dryrun)
+                 {
+                   log_query(stale_flag | crecp->flags, name, NULL, record_source(crecp->uid), 0);
+                   if (add_resource_record(header, limit, &trunc, nameoffset, &ansp, 
+                                           crec_ttl(crecp, now), &nameoffset,
+                                           T_CNAME, C_IN, "d", cname_target))
+                     anscount++;
+                 }
+               
+             }
+           else
+             return 0; /* give up if any cached CNAME in chain can't be used for DNSSEC reasons. */
+           
+           if (qtype == T_CNAME)
+             break;
+           
+           strcpy(name, cname_target);
+         }
+      
       if (qtype == T_TXT || qtype == T_ANY)
        {
          struct txt_record *t;
@@ -1493,7 +1553,7 @@ size_t answer_request(struct dns_header *header, char *limit, size_t qlen,
 #endif
                      if (ok)
                        {
-                         log_query(F_CONFIG | F_RRNAME, name, NULL, "<TXT>");
+                         log_query(F_CONFIG | F_RRNAME, name, NULL, "<TXT>", 0);
                          if (add_resource_record(header, limit, &trunc, nameoffset, &ansp, 
                                                  ttl, NULL,
                                                  T_TXT, t->class, "t", t->len, t->txt))
@@ -1515,7 +1575,7 @@ size_t answer_request(struct dns_header *header, char *limit, size_t qlen,
                  if (!dryrun)
                    {
                       addr.log.rcode = NOTIMP;
-                      log_query(F_CONFIG | F_RCODE, name, &addr, NULL);
+                      log_query(F_CONFIG | F_RCODE, name, &addr, NULL, 0);
                    }
                  ans = 1, sec_data = 0;
                }
@@ -1533,7 +1593,7 @@ size_t answer_request(struct dns_header *header, char *limit, size_t qlen,
                sec_data = 0;
                if (!dryrun)
                  {
-                   log_query(F_CONFIG | F_RRNAME, name, NULL, querystr(NULL, t->class));
+                   log_query(F_CONFIG | F_RRNAME, name, NULL, NULL, t->class);
                    if (add_resource_record(header, limit, &trunc, nameoffset, &ansp, 
                                            daemon->local_ttl, NULL,
                                            t->class, C_IN, "t", t->len, t->txt))
@@ -1563,7 +1623,7 @@ size_t answer_request(struct dns_header *header, char *limit, size_t qlen,
                    
                    if (addrlist)
                      break;
-                   else
+                   else if (!(intr->flags & INP4))
                      while (intr->next && strcmp(intr->intr, intr->next->intr) == 0)
                        intr = intr->next;
                  }
@@ -1578,7 +1638,7 @@ size_t answer_request(struct dns_header *header, char *limit, size_t qlen,
                    
                    if (addrlist)
                      break;
-                   else
+                   else if (!(intr->flags & INP6))
                      while (intr->next && strcmp(intr->intr, intr->next->intr) == 0)
                        intr = intr->next;
                  }
@@ -1589,7 +1649,7 @@ size_t answer_request(struct dns_header *header, char *limit, size_t qlen,
                  ans = 1;
                  if (!dryrun)
                    {
-                     log_query(is_arpa | F_REVERSE | F_CONFIG, intr->name, &addr, NULL);
+                     log_query(is_arpa | F_REVERSE | F_CONFIG, intr->name, &addr, NULL, 0);
                      if (add_resource_record(header, limit, &trunc, nameoffset, &ansp, 
                                              daemon->local_ttl, NULL,
                                              T_PTR, C_IN, "d", intr->name))
@@ -1602,7 +1662,7 @@ size_t answer_request(struct dns_header *header, char *limit, size_t qlen,
                  sec_data = 0;
                  if (!dryrun)
                    {
-                     log_query(F_CONFIG | F_RRNAME, name, NULL, "<PTR>");
+                     log_query(F_CONFIG | F_RRNAME, name, NULL, "<PTR>", 0);
                      for (ptr = daemon->ptr; ptr; ptr = ptr->next)
                        if (hostname_isequal(name, ptr->name) &&
                            add_resource_record(header, limit, &trunc, nameoffset, &ansp, 
@@ -1612,7 +1672,7 @@ size_t answer_request(struct dns_header *header, char *limit, size_t qlen,
                         
                    }
                }
-             else if ((crecp = cache_find_by_addr(NULL, &addr, now, is_arpa)))
+             else if (is_arpa && (crecp = cache_find_by_addr(NULL, &addr, now, is_arpa)))
                {
                  /* Don't use cache when DNSSEC data required, unless we know that
                     the zone is unsigned, which implies that we're doing
@@ -1622,22 +1682,33 @@ size_t answer_request(struct dns_header *header, char *limit, size_t qlen,
                    {
                      do 
                        { 
+                         int stale_flag = 0;
+                         
+                         if (crec_isstale(crecp, now))
+                           {
+                             if (stale)
+                               *stale = 1;
+                             
+                             stale_flag = F_STALE;
+                           }
+                         
                          /* don't answer wildcard queries with data not from /etc/hosts or dhcp leases */
                          if (qtype == T_ANY && !(crecp->flags & (F_HOSTS | F_DHCP)))
                            continue;
                          
+                         
                          if (!(crecp->flags & F_DNSSECOK))
                            sec_data = 0;
-                          
+                         
                          ans = 1;
-                          
+                         
                          if (crecp->flags & F_NEG)
                            {
                              auth = 0;
                              if (crecp->flags & F_NXDOMAIN)
                                nxdomain = 1;
                              if (!dryrun)
-                               log_query(crecp->flags & ~F_FORWARD, name, &addr, NULL);
+                               log_query(stale_flag | (crecp->flags & ~F_FORWARD), name, &addr, NULL, 0);
                            }
                          else
                            {
@@ -1645,8 +1716,8 @@ size_t answer_request(struct dns_header *header, char *limit, size_t qlen,
                                auth = 0;
                              if (!dryrun)
                                {
-                                 log_query(crecp->flags & ~F_FORWARD, cache_get_name(crecp), &addr, 
-                                           record_source(crecp->uid));
+                                 log_query(stale_flag | (crecp->flags & ~F_FORWARD), cache_get_name(crecp), &addr, 
+                                           record_source(crecp->uid), 0);
                                  
                                  if (add_resource_record(header, limit, &trunc, nameoffset, &ansp, 
                                                          crec_ttl(crecp, now), NULL,
@@ -1663,7 +1734,7 @@ size_t answer_request(struct dns_header *header, char *limit, size_t qlen,
                  sec_data = 0;
                  if (!dryrun)
                    {
-                     log_query(F_CONFIG | F_REVERSE | is_arpa, name, &addr, NULL); 
+                     log_query(F_CONFIG | F_REVERSE | is_arpa, name, &addr, NULL, 0);
                      
                      if (add_resource_record(header, limit, &trunc, nameoffset, &ansp, 
                                              daemon->local_ttl, NULL,
@@ -1681,7 +1752,7 @@ size_t answer_request(struct dns_header *header, char *limit, size_t qlen,
                  nxdomain = 1;
                  if (!dryrun)
                    log_query(F_CONFIG | F_REVERSE | is_arpa | F_NEG | F_NXDOMAIN,
-                             name, &addr, NULL);
+                             name, &addr, NULL, 0);
                }
            }
 
@@ -1736,7 +1807,7 @@ size_t answer_request(struct dns_header *header, char *limit, size_t qlen,
                              if (!dryrun)
                                {
                                  gotit = 1;
-                                 log_query(F_FORWARD | F_CONFIG | flag, name, &addrlist->addr, NULL);
+                                 log_query(F_FORWARD | F_CONFIG | flag, name, &addrlist->addr, NULL, 0);
                                  if (add_resource_record(header, limit, &trunc, nameoffset, &ansp, 
                                                          daemon->local_ttl, NULL, type, C_IN, 
                                                          type == T_A ? "4" : "6", &addrlist->addr))
@@ -1746,15 +1817,15 @@ size_t answer_request(struct dns_header *header, char *limit, size_t qlen,
                      }
                  
                  if (!dryrun && !gotit)
-                   log_query(F_FORWARD | F_CONFIG | flag | F_NEG, name, NULL, NULL);
+                   log_query(F_FORWARD | F_CONFIG | flag | F_NEG, name, NULL, NULL, 0);
                     
                  continue;
                }
 
-             if ((crecp = cache_find_by_name(NULL, name, now, flag | (dryrun ? F_NO_RR : 0))))
+             if ((crecp = cache_find_by_name(NULL, name, now, flag | F_NXDOMAIN | (dryrun ? F_NO_RR : 0))))
                {
                  int localise = 0;
-                 
+                                 
                  /* See if a putative address is on the network from which we received
                     the query, is so we'll filter other answers. */
                  if (local_addr.s_addr != 0 && option_bool(OPT_LOCALISE) && flag == F_IPV4)
@@ -1776,6 +1847,16 @@ size_t answer_request(struct dns_header *header, char *limit, size_t qlen,
                      (rd_bit && (!do_bit || cache_validated(crecp)) ))
                    do
                      { 
+                       int stale_flag = 0;
+                       
+                       if (crec_isstale(crecp, now))
+                         {
+                           if (stale)
+                             *stale = 1;
+                           
+                           stale_flag = F_STALE;
+                         }
+                       
                        /* don't answer wildcard queries with data not from /etc/hosts
                           or DHCP leases */
                        if (qtype == T_ANY && !(crecp->flags & (F_HOSTS | F_DHCP | F_CONFIG)))
@@ -1791,7 +1872,7 @@ size_t answer_request(struct dns_header *header, char *limit, size_t qlen,
                            if (crecp->flags & F_NXDOMAIN)
                              nxdomain = 1;
                            if (!dryrun)
-                             log_query(crecp->flags, name, NULL, NULL);
+                             log_query(stale_flag | crecp->flags, name, NULL, NULL, 0);
                          }
                        else 
                          {
@@ -1808,8 +1889,8 @@ size_t answer_request(struct dns_header *header, char *limit, size_t qlen,
                            ans = 1;
                            if (!dryrun)
                              {
-                               log_query(crecp->flags & ~F_REVERSE, name, &crecp->addr,
-                                         record_source(crecp->uid));
+                               log_query(stale_flag | (crecp->flags & ~F_REVERSE), name, &crecp->addr,
+                                         record_source(crecp->uid), 0);
                                
                                if (add_resource_record(header, limit, &trunc, nameoffset, &ansp, 
                                                        crec_ttl(crecp, now), NULL, type, C_IN, 
@@ -1824,7 +1905,7 @@ size_t answer_request(struct dns_header *header, char *limit, size_t qlen,
                  ans = 1, sec_data = 0;
                  if (!dryrun)
                    {
-                     log_query(F_FORWARD | F_CONFIG | flag, name, &addr, NULL);
+                     log_query(F_FORWARD | F_CONFIG | flag, name, &addr, NULL, 0);
                      if (add_resource_record(header, limit, &trunc, nameoffset, &ansp, 
                                              daemon->local_ttl, NULL, type, C_IN, type == T_A ? "4" : "6", &addr))
                        anscount++;
@@ -1843,7 +1924,7 @@ size_t answer_request(struct dns_header *header, char *limit, size_t qlen,
                    if (!dryrun)
                      {
                        int offset;
-                       log_query(F_CONFIG | F_RRNAME, name, NULL, "<MX>");
+                       log_query(F_CONFIG | F_RRNAME, name, NULL, "<MX>", 0);
                        if (add_resource_record(header, limit, &trunc, nameoffset, &ansp, daemon->local_ttl,
                                                &offset, T_MX, C_IN, "sd", rec->weight, rec->target))
                          {
@@ -1861,7 +1942,7 @@ size_t answer_request(struct dns_header *header, char *limit, size_t qlen,
                  sec_data = 0;
                  if (!dryrun)
                    {
-                     log_query(F_CONFIG | F_RRNAME, name, NULL, "<MX>");
+                     log_query(F_CONFIG | F_RRNAME, name, NULL, "<MX>", 0);
                      if (add_resource_record(header, limit, &trunc, nameoffset, &ansp, daemon->local_ttl, NULL, 
                                              T_MX, C_IN, "sd", 1, 
                                              option_bool(OPT_SELFMX) ? name : daemon->mxtarget))
@@ -1883,7 +1964,7 @@ size_t answer_request(struct dns_header *header, char *limit, size_t qlen,
                    if (!dryrun)
                      {
                        int offset;
-                       log_query(F_CONFIG | F_RRNAME, name, NULL, "<SRV>");
+                       log_query(F_CONFIG | F_RRNAME, name, NULL, "<SRV>", 0);
                        if (add_resource_record(header, limit, &trunc, nameoffset, &ansp, daemon->local_ttl, 
                                                &offset, T_SRV, C_IN, "sssd", 
                                                rec->priority, rec->weight, rec->srvport, rec->target))
@@ -1915,27 +1996,40 @@ size_t answer_request(struct dns_header *header, char *limit, size_t qlen,
 
              if (!found)
                {
-                 if ((crecp = cache_find_by_name(NULL, name, now, F_SRV | (dryrun ? F_NO_RR : 0))) &&
+                 if ((crecp = cache_find_by_name(NULL, name, now, F_SRV | F_NXDOMAIN | (dryrun ? F_NO_RR : 0))) &&
                      rd_bit && (!do_bit || (option_bool(OPT_DNSSEC_VALID) && !(crecp->flags & F_DNSSECOK))))
-                   {
-                     if (!(crecp->flags & F_DNSSECOK))
-                       sec_data = 0;
-                     
-                     auth = 0;
-                     found = ans = 1;
-                     
-                     do {
+                   do
+                     {
+                       int stale_flag = 0;
+                       
+                       if (crec_isstale(crecp, now))
+                         {
+                           if (stale)
+                             *stale = 1;
+                           
+                           stale_flag = F_STALE;
+                         }
+                       /* don't answer wildcard queries with data not from /etc/hosts or dhcp leases, except for NXDOMAIN */
+                       if (qtype == T_ANY && !(crecp->flags & (F_NXDOMAIN)))
+                         break;
+                       
+                       if (!(crecp->flags & F_DNSSECOK))
+                         sec_data = 0;
+                       
+                       auth = 0;
+                       found = ans = 1;
+                       
                        if (crecp->flags & F_NEG)
                          {
                            if (crecp->flags & F_NXDOMAIN)
                              nxdomain = 1;
                            if (!dryrun)
-                             log_query(crecp->flags, name, NULL, NULL);
+                             log_query(stale_flag | crecp->flags, name, NULL, NULL, 0);
                          }
                        else if (!dryrun)
                          {
                            char *target = blockdata_retrieve(crecp->addr.srv.target, crecp->addr.srv.targetlen, NULL);
-                           log_query(crecp->flags, name, NULL, 0);
+                           log_query(stale_flag | crecp->flags, name, NULL, NULL, 0);
                            
                            if (add_resource_record(header, limit, &trunc, nameoffset, &ansp, 
                                                    crec_ttl(crecp, now), NULL, T_SRV, C_IN, "sssd",
@@ -1945,14 +2039,13 @@ size_t answer_request(struct dns_header *header, char *limit, size_t qlen,
                          }
                      } while ((crecp = cache_find_by_name(crecp, name, now, F_SRV)));
                    }
-               }
-
+             
              if (!found && option_bool(OPT_FILTER) && (qtype == T_SRV || (qtype == T_ANY && strchr(name, '_'))))
                {
                  ans = 1;
                  sec_data = 0;
                  if (!dryrun)
-                   log_query(F_CONFIG | F_NEG, name, NULL, NULL);
+                   log_query(F_CONFIG | F_NEG, name, NULL, NULL, 0);
                }
            }
 
@@ -1966,7 +2059,7 @@ size_t answer_request(struct dns_header *header, char *limit, size_t qlen,
                    sec_data = 0;
                    if (!dryrun)
                      {
-                       log_query(F_CONFIG | F_RRNAME, name, NULL, "<NAPTR>");
+                       log_query(F_CONFIG | F_RRNAME, name, NULL, "<NAPTR>", 0);
                        if (add_resource_record(header, limit, &trunc, nameoffset, &ansp, daemon->local_ttl, 
                                                NULL, T_NAPTR, C_IN, "sszzzd", 
                                                na->order, na->pref, na->flags, na->services, na->regexp, na->replace))
@@ -1983,7 +2076,7 @@ size_t answer_request(struct dns_header *header, char *limit, size_t qlen,
              ans = 1;
              sec_data = 0;
              if (!dryrun)
-               log_query(F_CONFIG | F_NEG, name, &addr, NULL);
+               log_query(F_CONFIG | F_NEG, name, &addr, NULL, 0);
            }
        }