]> git.netwichtig.de Git - user/henk/code/exim.git/blobdiff - src/src/dns.c
ARC: relax restriction on building without SPF
[user/henk/code/exim.git] / src / src / dns.c
index e3845978c36de35610681faa3ea8bc58a63f68f4..7736a2204af231d79aa6660580868ee55f4a9d53 100644 (file)
@@ -10,7 +10,6 @@
 #include "exim.h"
 
 
 #include "exim.h"
 
 
-
 /*************************************************
 *               Fake DNS resolver                *
 *************************************************/
 /*************************************************
 *               Fake DNS resolver                *
 *************************************************/
@@ -435,6 +434,7 @@ dnss->aptr += dnss->srr.size;                       /* Advance to next RR */
 /* Return a pointer to the dns_record structure within the dns_answer. This is
 for convenience so that the scans can use nice-looking for loops. */
 
 /* Return a pointer to the dns_record structure within the dns_answer. This is
 for convenience so that the scans can use nice-looking for loops. */
 
+TRACE debug_printf("%s: return %s\n", __FUNCTION__, dns_text_type(dnss->srr.type));
 return &dnss->srr;
 
 null_return:
 return &dnss->srr;
 
 null_return:
@@ -498,13 +498,13 @@ const HEADER * h = (const HEADER *) dnsa->answer;
 const uschar * auth_name;
 const uschar * trusted;
 
 const uschar * auth_name;
 const uschar * trusted;
 
+if (dnsa->answerlen < 0) return FALSE;
 if (h->ad) return TRUE;
 
 if (h->ad) return TRUE;
 
-/* If the resolver we ask is authoritative for the domain in question, it
-* may not set the AD but the AA bit. If we explicitly trust
-* the resolver for that domain (via a domainlist in dns_trust_aa),
-* we return TRUE to indicate a secure answer.
-*/
+/* If the resolver we ask is authoritative for the domain in question, it may
+not set the AD but the AA bit. If we explicitly trust the resolver for that
+domain (via a domainlist in dns_trust_aa), we return TRUE to indicate a secure
+answer.  */
 
 if (  !h->aa
    || !dns_trust_aa
 
 if (  !h->aa
    || !dns_trust_aa
@@ -540,12 +540,12 @@ h->aa = h->ad = 0;
  ************************************************/
 
 BOOL
  ************************************************/
 
 BOOL
-dns_is_aa(const dns_answer *dnsa)
+dns_is_aa(const dns_answer * dnsa)
 {
 #ifdef DISABLE_DNSSEC
 return FALSE;
 #else
 {
 #ifdef DISABLE_DNSSEC
 return FALSE;
 #else
-return ((const HEADER*)dnsa->answer)->aa;
+return dnsa->answerlen >= 0 && ((const HEADER *)dnsa->answer)->aa;
 #endif
 }
 
 #endif
 }
 
@@ -593,6 +593,10 @@ static void
 dns_fail_tag(uschar * buf, const uschar * name, int dns_type)
 {
 res_state resp = os_get_dns_resolver_res();
 dns_fail_tag(uschar * buf, const uschar * name, int dns_type)
 {
 res_state resp = os_get_dns_resolver_res();
+
+/*XX buf needs to be 255 +1 + (max(typetext) == 5) +1 + max(chars_for_long-max) +1
+We truncate the name here for safety... could use a dynamic string. */
+
 sprintf(CS buf, "%.255s-%s-%lx", name, dns_text_type(dns_type),
   (unsigned long) resp->options);
 }
 sprintf(CS buf, "%.255s-%s-%lx", name, dns_text_type(dns_type),
   (unsigned long) resp->options);
 }
@@ -606,21 +610,155 @@ caching.
 Arguments:
   name       the domain name
   type       the lookup type
 Arguments:
   name       the domain name
   type       the lookup type
+  expiry     time TTL expires, or zero for unlimited
   rc         the return code
 
 Returns:     the return code
 */
 
   rc         the return code
 
 Returns:     the return code
 */
 
+/* we need:  255 +1 + (max(typetext) == 5) +1 + max(chars_for_long-max) +1 */
+#define DNS_FAILTAG_MAX 290
+#define DNS_FAILNODE_SIZE \
+  (sizeof(expiring_data) + sizeof(tree_node) + DNS_FAILTAG_MAX)
+
+static int
+dns_fail_return(const uschar * name, int type, time_t expiry, int rc)
+{
+uschar node_name[DNS_FAILTAG_MAX];
+tree_node * previous, * new;
+expiring_data * e;
+
+dns_fail_tag(node_name, name, type);
+if ((previous = tree_search(tree_dns_fails, node_name)))
+  e = previous->data.ptr;
+else
+  {
+  e = store_get_perm(DNS_FAILNODE_SIZE, is_tainted(name));
+  new = (void *)(e+1);
+  dns_fail_tag(new->name, name, type);
+  new->data.ptr = e;
+  (void)tree_insertnode(&tree_dns_fails, new);
+  }
+
+DEBUG(D_dns) debug_printf(" %s neg-cache entry for %s, ttl %d\n",
+  previous ? "update" : "writing",
+  node_name, expiry ? (int)(expiry - time(NULL)) : -1);
+e->expiry = expiry;
+e->data.val = rc;
+return rc;
+}
+
+
+/* Return the cached result of a known-bad lookup, or -1.
+*/
 static int
 static int
-dns_return(const uschar * name, int type, int rc)
+dns_fail_cache_hit(const uschar * name, int type)
 {
 {
-tree_node *node = store_get_perm(sizeof(tree_node) + 290, TRUE);
-dns_fail_tag(node->name, name, type);
-node->data.val = rc;
-(void)tree_insertnode(&tree_dns_fails, node);
+uschar node_name[DNS_FAILTAG_MAX];
+tree_node * previous;
+expiring_data * e;
+int val, rc;
+
+dns_fail_tag(node_name, name, type);
+if (!(previous = tree_search(tree_dns_fails, node_name)))
+  return -1;
+
+e = previous->data.ptr;
+val = e->data.val;
+rc = e->expiry && e->expiry <= time(NULL) ? -1 : val;
+
+DEBUG(D_dns) debug_printf("DNS lookup of %.255s-%s: %scached value %s%s\n",
+  name, dns_text_type(type),
+  rc == -1 ? "" : "using ",
+    val == DNS_NOMATCH ? "DNS_NOMATCH" :
+    val == DNS_NODATA ? "DNS_NODATA" :
+    val == DNS_AGAIN ? "DNS_AGAIN" :
+    val == DNS_FAIL ? "DNS_FAIL" : "??",
+  rc == -1 ? " past valid time" : "");
+
 return rc;
 }
 
 return rc;
 }
 
+
+
+/* This is really gross. The successful return value from res_search() is
+the packet length, which is stored in dnsa->answerlen. If we get a
+negative DNS reply then res_search() returns -1, which causes the bounds
+checks for name decompression to fail when it is treated as a packet
+length, which in turn causes the authority search to fail. The correct
+packet length has been lost inside libresolv, so we have to guess a
+replacement value. (The only way to fix this properly would be to
+re-implement res_search() and res_query() so that they don't muddle their
+success and packet length return values.) For added safety we only reset
+the packet length if the packet header looks plausible. */
+
+static void
+fake_dnsa_len_for_fail(dns_answer * dnsa, int type)
+{
+const HEADER * h = (const HEADER *)dnsa->answer;
+
+if (  h->qr == 1                               /* a response */
+   && h->opcode == QUERY
+   && h->tc == 0                               /* nmessage not truncated */
+   && (h->rcode == NOERROR || h->rcode == NXDOMAIN)
+   && (  ntohs(h->qdcount) == 1                        /* one question record */
+      || f.running_in_test_harness)
+   && ntohs(h->ancount) == 0                   /* no answer records */
+   && ntohs(h->nscount) >= 1)                  /* authority records */
+  {
+  DEBUG(D_dns) debug_printf("faking res_search(%s) response length as %d\n",
+    dns_text_type(type), (int)sizeof(dnsa->answer));
+  dnsa->answerlen = sizeof(dnsa->answer);
+  }
+}
+
+
+/* Return the TTL suitable for an NXDOMAIN result, which is given
+in the SOA.  We hope that one was returned in the lookup, and do not
+bother doing a separate lookup; if not found return a forever TTL.
+*/
+
+time_t
+dns_expire_from_soa(dns_answer * dnsa, int type)
+{
+dns_scan dnss;
+
+fake_dnsa_len_for_fail(dnsa, type);
+
+for (dns_record * rr = dns_next_rr(dnsa, &dnss, RESET_AUTHORITY);
+     rr; rr = dns_next_rr(dnsa, &dnss, RESET_NEXT)
+    ) if (rr->type == T_SOA)
+  {
+  const uschar * p = rr->data;
+  uschar discard_buf[256];
+  int len;
+  unsigned long ttl;
+
+  /* Skip the mname & rname strings */
+
+  if ((len = dn_expand(dnsa->answer, dnsa->answer + dnsa->answerlen,
+      p, (DN_EXPAND_ARG4_TYPE)discard_buf, 256)) < 0)
+    break;
+  p += len;
+  if ((len = dn_expand(dnsa->answer, dnsa->answer + dnsa->answerlen,
+      p, (DN_EXPAND_ARG4_TYPE)discard_buf, 256)) < 0)
+    break;
+  p += len;
+
+  /* Skip the SOA serial, refresh, retry & expire.  Grab the TTL */
+
+  if (p > dnsa->answer + dnsa->answerlen - 5 * INT32SZ)
+    break;
+  p += 4 * INT32SZ;
+  GETLONG(ttl, p);
+
+  return time(NULL) + ttl;
+  }
+DEBUG(D_dns) debug_printf("DNS: no SOA record found for neg-TTL\n");
+return 0;
+}
+
+
 /*************************************************
 *              Do basic DNS lookup               *
 *************************************************/
 /*************************************************
 *              Do basic DNS lookup               *
 *************************************************/
@@ -651,31 +789,23 @@ Returns:    DNS_SUCCEED   successful lookup
 */
 
 int
 */
 
 int
-dns_basic_lookup(dns_answer *dnsa, const uschar *name, int type)
+dns_basic_lookup(dns_answer * dnsa, const uschar * name, int type)
 {
 {
+int rc;
 #ifndef STAND_ALONE
 #ifndef STAND_ALONE
-int rc = -1;
-const uschar *save_domain;
+const uschar * save_domain;
 #endif
 
 #endif
 
-tree_node *previous;
-uschar node_name[290];
-
 /* DNS lookup failures of any kind are cached in a tree. This is mainly so that
 a timeout on one domain doesn't happen time and time again for messages that
 have many addresses in the same domain. We rely on the resolver and name server
 /* DNS lookup failures of any kind are cached in a tree. This is mainly so that
 a timeout on one domain doesn't happen time and time again for messages that
 have many addresses in the same domain. We rely on the resolver and name server
-caching for successful lookups. */
+caching for successful lookups.
+*/
 
 
-dns_fail_tag(node_name, name, type);
-if ((previous = tree_search(tree_dns_fails, node_name)))
+if ((rc = dns_fail_cache_hit(name, type)) > 0)
   {
   {
-  DEBUG(D_dns) debug_printf("DNS lookup of %.255s-%s: using cached value %s\n",
-    name, dns_text_type(type),
-      previous->data.val == DNS_NOMATCH ? "DNS_NOMATCH" :
-      previous->data.val == DNS_NODATA ? "DNS_NODATA" :
-      previous->data.val == DNS_AGAIN ? "DNS_AGAIN" :
-      previous->data.val == DNS_FAIL ? "DNS_FAIL" : "??");
-  return previous->data.val;
+  dnsa->answerlen = -1;
+  return rc;
   }
 
 #ifdef SUPPORT_I18N
   }
 
 #ifdef SUPPORT_I18N
@@ -706,34 +836,17 @@ regex has substrings that are used - the default uses a conditional.
 
 This test is omitted for PTR records. These occur only in calls from the dnsdb
 lookup, which constructs the names itself, so they should be OK. Besides,
 
 This test is omitted for PTR records. These occur only in calls from the dnsdb
 lookup, which constructs the names itself, so they should be OK. Besides,
-bitstring labels don't conform to normal name syntax. (But the aren't used any
-more.)
-
-For SRV records, we omit the initial _smtp._tcp. components at the start.
-The check has been seen to bite on the destination of a SRV lookup that
-initiall hit a CNAME, for which the next name had only two components.
-RFC2782 makes no mention of the possibiility of CNAMES, but the Wikipedia
-article on SRV says they are not a valid configuration. */
+bitstring labels don't conform to normal name syntax. (But they aren't used any
+more.) */
 
 #ifndef STAND_ALONE   /* Omit this for stand-alone tests */
 
 if (check_dns_names_pattern[0] != 0 && type != T_PTR && type != T_TXT)
   {
 
 #ifndef STAND_ALONE   /* Omit this for stand-alone tests */
 
 if (check_dns_names_pattern[0] != 0 && type != T_PTR && type != T_TXT)
   {
-  const uschar *checkname = name;
   int ovector[3*(EXPAND_MAXN+1)];
 
   dns_pattern_init();
   int ovector[3*(EXPAND_MAXN+1)];
 
   dns_pattern_init();
-
-  /* For an SRV lookup, skip over the first two components (the service and
-  protocol names, which both start with an underscore). */
-
-  if (type == T_SRV || type == T_TLSA)
-    {
-    while (*checkname && *checkname++ != '.') ;
-    while (*checkname && *checkname++ != '.') ;
-    }
-
-  if (pcre_exec(regex_check_dns_names, NULL, CCS checkname, Ustrlen(checkname),
+  if (pcre_exec(regex_check_dns_names, NULL, CCS name, Ustrlen(name),
       0, PCRE_EOPT, ovector, nelem(ovector)) < 0)
     {
     DEBUG(D_dns)
       0, PCRE_EOPT, ovector, nelem(ovector)) < 0)
     {
     DEBUG(D_dns)
@@ -762,6 +875,7 @@ if ((type == T_A || type == T_AAAA) && string_is_ip_address(name, NULL) != 0)
 (res_search), we call fakens_search(), which recognizes certain special
 domains, and interfaces to a fake nameserver for certain special zones. */
 
 (res_search), we call fakens_search(), which recognizes certain special
 domains, and interfaces to a fake nameserver for certain special zones. */
 
+h_errno = 0;
 dnsa->answerlen = f.running_in_test_harness
   ? fakens_search(name, type, dnsa->answer, sizeof(dnsa->answer))
   : res_search(CCS name, C_IN, type, dnsa->answer, sizeof(dnsa->answer));
 dnsa->answerlen = f.running_in_test_harness
   ? fakens_search(name, type, dnsa->answer, sizeof(dnsa->answer))
   : res_search(CCS name, C_IN, type, dnsa->answer, sizeof(dnsa->answer));
@@ -779,7 +893,7 @@ if (dnsa->answerlen < 0) switch (h_errno)
   case HOST_NOT_FOUND:
     DEBUG(D_dns) debug_printf("DNS lookup of %s (%s) gave HOST_NOT_FOUND\n"
       "returning DNS_NOMATCH\n", name, dns_text_type(type));
   case HOST_NOT_FOUND:
     DEBUG(D_dns) debug_printf("DNS lookup of %s (%s) gave HOST_NOT_FOUND\n"
       "returning DNS_NOMATCH\n", name, dns_text_type(type));
-    return dns_return(name, type, DNS_NOMATCH);
+    return dns_fail_return(name, type, dns_expire_from_soa(dnsa, type), DNS_NOMATCH);
 
   case TRY_AGAIN:
     DEBUG(D_dns) debug_printf("DNS lookup of %s (%s) gave TRY_AGAIN\n",
 
   case TRY_AGAIN:
     DEBUG(D_dns) debug_printf("DNS lookup of %s (%s) gave TRY_AGAIN\n",
@@ -795,30 +909,30 @@ if (dnsa->answerlen < 0) switch (h_errno)
     if (rc != OK)
       {
       DEBUG(D_dns) debug_printf("returning DNS_AGAIN\n");
     if (rc != OK)
       {
       DEBUG(D_dns) debug_printf("returning DNS_AGAIN\n");
-      return dns_return(name, type, DNS_AGAIN);
+      return dns_fail_return(name, type, 0, DNS_AGAIN);
       }
     DEBUG(D_dns) debug_printf("%s is in dns_again_means_nonexist: returning "
       "DNS_NOMATCH\n", name);
       }
     DEBUG(D_dns) debug_printf("%s is in dns_again_means_nonexist: returning "
       "DNS_NOMATCH\n", name);
-    return dns_return(name, type, DNS_NOMATCH);
+    return dns_fail_return(name, type, dns_expire_from_soa(dnsa, type), DNS_NOMATCH);
 
 #else   /* For stand-alone tests */
 
 #else   /* For stand-alone tests */
-    return dns_return(name, type, DNS_AGAIN);
+    return dns_fail_return(name, type, 0, DNS_AGAIN);
 #endif
 
   case NO_RECOVERY:
     DEBUG(D_dns) debug_printf("DNS lookup of %s (%s) gave NO_RECOVERY\n"
       "returning DNS_FAIL\n", name, dns_text_type(type));
 #endif
 
   case NO_RECOVERY:
     DEBUG(D_dns) debug_printf("DNS lookup of %s (%s) gave NO_RECOVERY\n"
       "returning DNS_FAIL\n", name, dns_text_type(type));
-    return dns_return(name, type, DNS_FAIL);
+    return dns_fail_return(name, type, 0, DNS_FAIL);
 
   case NO_DATA:
     DEBUG(D_dns) debug_printf("DNS lookup of %s (%s) gave NO_DATA\n"
       "returning DNS_NODATA\n", name, dns_text_type(type));
 
   case NO_DATA:
     DEBUG(D_dns) debug_printf("DNS lookup of %s (%s) gave NO_DATA\n"
       "returning DNS_NODATA\n", name, dns_text_type(type));
-    return dns_return(name, type, DNS_NODATA);
+    return dns_fail_return(name, type, dns_expire_from_soa(dnsa, type), DNS_NODATA);
 
   default:
     DEBUG(D_dns) debug_printf("DNS lookup of %s (%s) gave unknown DNS error %d\n"
       "returning DNS_FAIL\n", name, dns_text_type(type), h_errno);
 
   default:
     DEBUG(D_dns) debug_printf("DNS lookup of %s (%s) gave unknown DNS error %d\n"
       "returning DNS_FAIL\n", name, dns_text_type(type), h_errno);
-    return dns_return(name, type, DNS_FAIL);
+    return dns_fail_return(name, type, 0, DNS_FAIL);
   }
 
 DEBUG(D_dns) debug_printf("DNS lookup of %s (%s) succeeded\n",
   }
 
 DEBUG(D_dns) debug_printf("DNS lookup of %s (%s) succeeded\n",
@@ -1086,23 +1200,7 @@ switch (type)
 
     if (rc == DNS_NOMATCH)
       {
 
     if (rc == DNS_NOMATCH)
       {
-      /* This is really gross. The successful return value from res_search() is
-      the packet length, which is stored in dnsa->answerlen. If we get a
-      negative DNS reply then res_search() returns -1, which causes the bounds
-      checks for name decompression to fail when it is treated as a packet
-      length, which in turn causes the authority search to fail. The correct
-      packet length has been lost inside libresolv, so we have to guess a
-      replacement value. (The only way to fix this properly would be to
-      re-implement res_search() and res_query() so that they don't muddle their
-      success and packet length return values.) For added safety we only reset
-      the packet length if the packet header looks plausible. */
-
-      const HEADER * h = (const HEADER *)dnsa->answer;
-      if (h->qr == 1 && h->opcode == QUERY && h->tc == 0
-         && (h->rcode == NOERROR || h->rcode == NXDOMAIN)
-         && ntohs(h->qdcount) == 1 && ntohs(h->ancount) == 0
-         && ntohs(h->nscount) >= 1)
-           dnsa->answerlen = sizeof(dnsa->answer);
+      fake_dnsa_len_for_fail(dnsa, T_CSA);
 
       for (rr = dns_next_rr(dnsa, &dnss, RESET_AUTHORITY);
           rr; rr = dns_next_rr(dnsa, &dnss, RESET_NEXT)
 
       for (rr = dns_next_rr(dnsa, &dnss, RESET_AUTHORITY);
           rr; rr = dns_next_rr(dnsa, &dnss, RESET_NEXT)