constification
[exim.git] / src / src / dns.c
index bfef69927649313d01bf45c234f8a7918aa61205..79eb9360e0b1a11120dbffdf2ac43e48be8ae487 100644 (file)
@@ -1,10 +1,8 @@
-/* $Cambridge: exim/src/src/dns.c,v 1.21 2009/11/16 19:50:36 nm4 Exp $ */
-
 /*************************************************
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
-/* Copyright (c) University of Cambridge 1995 - 2009 */
+/* Copyright (c) University of Cambridge 1995 - 2014 */
 /* See the file NOTICE for conditions of use and distribution. */
 
 /* Functions for interfacing with the DNS. */
@@ -48,7 +46,7 @@ Returns:      length of returned data, or -1 on error (h_errno set)
 */
 
 static int
-fakens_search(uschar *domain, int type, uschar *answerptr, int size)
+fakens_search(const uschar *domain, int type, uschar *answerptr, int size)
 {
 int len = Ustrlen(domain);
 int asize = size;                  /* Locally modified */
@@ -161,33 +159,38 @@ the first time we have been here, and set the resolver options.
 Arguments:
   qualify_single    TRUE to set the RES_DEFNAMES option
   search_parents    TRUE to set the RES_DNSRCH option
+  use_dnssec        TRUE to set the RES_USE_DNSSEC option
 
 Returns:            nothing
 */
 
 void
-dns_init(BOOL qualify_single, BOOL search_parents)
+dns_init(BOOL qualify_single, BOOL search_parents, BOOL use_dnssec)
 {
-if ((_res.options & RES_INIT) == 0)
+res_state resp = os_get_dns_resolver_res();
+
+if ((resp->options & RES_INIT) == 0)
   {
-  DEBUG(D_resolver) _res.options |= RES_DEBUG;     /* For Cygwin */
+  DEBUG(D_resolver) resp->options |= RES_DEBUG;     /* For Cygwin */
+  os_put_dns_resolver_res(resp);
   res_init();
-  DEBUG(D_resolver) _res.options |= RES_DEBUG;
+  DEBUG(D_resolver) resp->options |= RES_DEBUG;
+  os_put_dns_resolver_res(resp);
   }
 
-_res.options &= ~(RES_DNSRCH | RES_DEFNAMES);
-_res.options |= (qualify_single? RES_DEFNAMES : 0) |
+resp->options &= ~(RES_DNSRCH | RES_DEFNAMES);
+resp->options |= (qualify_single? RES_DEFNAMES : 0) |
                 (search_parents? RES_DNSRCH : 0);
-if (dns_retrans > 0) _res.retrans = dns_retrans;
-if (dns_retry > 0) _res.retry = dns_retry;
+if (dns_retrans > 0) resp->retrans = dns_retrans;
+if (dns_retry > 0) resp->retry = dns_retry;
 
 #ifdef RES_USE_EDNS0
 if (dns_use_edns0 >= 0)
   {
   if (dns_use_edns0)
-    _res.options |= RES_USE_EDNS0;
+    resp->options |= RES_USE_EDNS0;
   else
-    _res.options &= ~RES_USE_EDNS0;
+    resp->options &= ~RES_USE_EDNS0;
   DEBUG(D_resolver)
     debug_printf("Coerced resolver EDNS0 support %s.\n",
         dns_use_edns0 ? "on" : "off");
@@ -198,6 +201,43 @@ if (dns_use_edns0 >= 0)
     debug_printf("Unable to %sset EDNS0 without resolver support.\n",
         dns_use_edns0 ? "" : "un");
 #endif
+
+#ifndef DISABLE_DNSSEC
+# ifdef RES_USE_DNSSEC
+#  ifndef RES_USE_EDNS0
+#   error Have RES_USE_DNSSEC but not RES_USE_EDNS0?  Something hinky ...
+#  endif
+if (use_dnssec)
+  resp->options |= RES_USE_DNSSEC;
+if (dns_dnssec_ok >= 0)
+  {
+  if (dns_use_edns0 == 0 && dns_dnssec_ok != 0)
+    {
+    DEBUG(D_resolver)
+      debug_printf("CONFLICT: dns_use_edns0 forced false, dns_dnssec_ok forced true, ignoring latter!\n");
+    }
+  else
+    {
+    if (dns_dnssec_ok)
+      resp->options |= RES_USE_DNSSEC;
+    else
+      resp->options &= ~RES_USE_DNSSEC;
+    DEBUG(D_resolver) debug_printf("Coerced resolver DNSSEC support %s.\n",
+        dns_dnssec_ok ? "on" : "off");
+    }
+  }
+# else
+if (dns_dnssec_ok >= 0)
+  DEBUG(D_resolver)
+    debug_printf("Unable to %sset DNSSEC without resolver support.\n",
+        dns_dnssec_ok ? "" : "un");
+if (use_dnssec)
+  DEBUG(D_resolver)
+    debug_printf("Unable to set DNSSEC without resolver support.\n");
+# endif
+#endif /* DISABLE_DNSSEC */
+
+os_put_dns_resolver_res(resp);
 }
 
 
@@ -217,9 +257,9 @@ Returns:     nothing
 */
 
 void
-dns_build_reverse(uschar *string, uschar *buffer)
+dns_build_reverse(const uschar *string, uschar *buffer)
 {
-uschar *p = string + Ustrlen(string);
+const uschar *p = string + Ustrlen(string);
 uschar *pp = buffer;
 
 /* Handle IPv4 address */
@@ -231,7 +271,7 @@ if (Ustrchr(string, ':') == NULL)
   int i;
   for (i = 0; i < 4; i++)
     {
-    uschar *ppp = p;
+    const uschar *ppp = p;
     while (ppp > string && ppp[-1] != '.') ppp--;
     Ustrncpy(pp, ppp, p - ppp);
     pp += p - ppp;
@@ -390,6 +430,41 @@ return &(dnss->srr);
 
 
 
+/*************************************************
+*    Return whether AD bit set in DNS result     *
+*************************************************/
+
+/* We do not perform DNSSEC work ourselves; if the administrator has installed
+a verifying resolver which sets AD as appropriate, though, we'll use that.
+(AD = Authentic Data)
+
+Argument:   pointer to dns answer block
+Returns:    bool indicating presence of AD bit
+*/
+
+BOOL
+dns_is_secure(const dns_answer * dnsa)
+{
+#ifdef DISABLE_DNSSEC
+DEBUG(D_dns)
+  debug_printf("DNSSEC support disabled at build-time; dns_is_secure() false\n");
+return FALSE;
+#else
+HEADER *h = (HEADER *)dnsa->answer;
+return h->ad ? TRUE : FALSE;
+#endif
+}
+
+static void
+dns_set_insecure(dns_answer * dnsa)
+{
+HEADER * h = (HEADER *)dnsa->answer;
+h->ad = 0;
+}
+
+
+
+
 /*************************************************
 *            Turn DNS type into text             *
 *************************************************/
@@ -411,11 +486,13 @@ switch(t)
   case T_AAAA:  return US"AAAA";
   case T_A6:    return US"A6";
   case T_TXT:   return US"TXT";
+  case T_SPF:   return US"SPF";
   case T_PTR:   return US"PTR";
   case T_SOA:   return US"SOA";
   case T_SRV:   return US"SRV";
   case T_NS:    return US"NS";
   case T_CNAME: return US"CNAME";
+  case T_TLSA:  return US"TLSA";
   default:      return US"?";
   }
 }
@@ -440,11 +517,12 @@ Returns:     the return code
 */
 
 static int
-dns_return(uschar *name, int type, int rc)
+dns_return(const uschar * name, int type, int rc)
 {
+res_state resp = os_get_dns_resolver_res();
 tree_node *node = store_get_perm(sizeof(tree_node) + 290);
 sprintf(CS node->name, "%.255s-%s-%lx", name, dns_text_type(type),
-  _res.options);
+  resp->options);
 node->data.val = rc;
 (void)tree_insertnode(&tree_dns_fails, node);
 return rc;
@@ -478,12 +556,13 @@ Returns:    DNS_SUCCEED   successful lookup
 */
 
 int
-dns_basic_lookup(dns_answer *dnsa, uschar *name, int type)
+dns_basic_lookup(dns_answer *dnsa, const uschar *name, int type)
 {
 #ifndef STAND_ALONE
 int rc = -1;
-uschar *save;
+const uschar *save_domain;
 #endif
+res_state resp = os_get_dns_resolver_res();
 
 tree_node *previous;
 uschar node_name[290];
@@ -494,7 +573,7 @@ have many addresses in the same domain. We rely on the resolver and name server
 caching for successful lookups. */
 
 sprintf(CS node_name, "%.255s-%s-%lx", name, dns_text_type(type),
-  _res.options);
+  resp->options);
 previous = tree_search(tree_dns_fails, node_name);
 if (previous != NULL)
   {
@@ -525,23 +604,21 @@ For SRV records, we omit the initial _smtp._tcp. components at the start. */
 
 if (check_dns_names_pattern[0] != 0 && type != T_PTR && type != T_TXT)
   {
-  uschar *checkname = name;
+  const uschar *checkname = name;
   int ovector[3*(EXPAND_MAXN+1)];
 
-  if (regex_check_dns_names == NULL)
-    regex_check_dns_names =
-      regex_must_compile(check_dns_names_pattern, FALSE, TRUE);
+  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)
+  if (type == T_SRV || type == T_TLSA)
     {
     while (*checkname++ != '.');
     while (*checkname++ != '.');
     }
 
-  if (pcre_exec(regex_check_dns_names, NULL, CS checkname, Ustrlen(checkname),
+  if (pcre_exec(regex_check_dns_names, NULL, CCS checkname, Ustrlen(checkname),
       0, PCRE_EOPT, ovector, sizeof(ovector)/sizeof(int)) < 0)
     {
     DEBUG(D_dns)
@@ -578,7 +655,7 @@ domains, and interfaces to a fake nameserver for certain special zones. */
 if (running_in_test_harness)
   dnsa->answerlen = fakens_search(name, type, dnsa->answer, MAXPACKET);
 else
-  dnsa->answerlen = res_search(CS name, C_IN, type, dnsa->answer, MAXPACKET);
+  dnsa->answerlen = res_search(CCS name, C_IN, type, dnsa->answer, MAXPACKET);
 
 if (dnsa->answerlen > MAXPACKET)
   {
@@ -599,12 +676,12 @@ if (dnsa->answerlen < 0) switch (h_errno)
     name, dns_text_type(type));
 
   /* Cut this out for various test programs */
-  #ifndef STAND_ALONE
-  save = deliver_domain;
-  deliver_domain = name;  /* set $domain */
-  rc = match_isinlist(name, &dns_again_means_nonexist, 0, NULL, NULL,
+#ifndef STAND_ALONE
+  save_domain = deliver_domain;
+  deliver_domain = string_copy(name);  /* set $domain */
+  rc = match_isinlist(name, (const uschar **)&dns_again_means_nonexist, 0, NULL, NULL,
     MCL_DOMAIN, TRUE, NULL);
-  deliver_domain = save;
+  deliver_domain = save_domain;
   if (rc != OK)
     {
     DEBUG(D_dns) debug_printf("returning DNS_AGAIN\n");
@@ -614,9 +691,9 @@ if (dnsa->answerlen < 0) switch (h_errno)
     "DNS_NOMATCH\n", name);
   return dns_return(name, type, DNS_NOMATCH);
 
-  #else   /* For stand-alone tests */
+#else   /* For stand-alone tests */
   return dns_return(name, type, DNS_AGAIN);
-  #endif
+#endif
 
   case NO_RECOVERY:
   DEBUG(D_dns) debug_printf("DNS lookup of %s (%s) gave NO_RECOVERY\n"
@@ -677,10 +754,12 @@ Returns:                DNS_SUCCEED   successful lookup
 */
 
 int
-dns_lookup(dns_answer *dnsa, uschar *name, int type, uschar **fully_qualified_name)
+dns_lookup(dns_answer *dnsa, const uschar *name, int type,
+  const uschar **fully_qualified_name)
 {
 int i;
-uschar *orig_name = name;
+const uschar *orig_name = name;
+BOOL secure_so_far = TRUE;
 
 /* Loop to follow CNAME chains so far, but no further... */
 
@@ -735,7 +814,12 @@ for (i = 0; i < 10; i++)
 
   /* If any data records of the correct type were found, we are done. */
 
-  if (type_rr.data != NULL) return DNS_SUCCEED;
+  if (type_rr.data != NULL)
+    {
+    if (!secure_so_far)        /* mark insecure if any element of CNAME chain was */
+      dns_set_insecure(dnsa);
+    return DNS_SUCCEED;
+    }
 
   /* If there are no data records, we need to re-scan the DNS using the
   domain given in the CNAME record, which should exist (otherwise we should
@@ -744,10 +828,13 @@ for (i = 0; i < 10; i++)
 
   if (cname_rr.data == NULL) return DNS_FAIL;
   datalen = dn_expand(dnsa->answer, dnsa->answer + dnsa->answerlen,
-    cname_rr.data, (DN_EXPAND_ARG4_TYPE)data, 256);
+    cname_rr.data, (DN_EXPAND_ARG4_TYPE)data, sizeof(data));
   if (datalen < 0) return DNS_FAIL;
   name = data;
 
+  if (!dns_is_secure(dnsa))
+    secure_so_far = FALSE;
+
   DEBUG(D_dns) debug_printf("CNAME found: change to %s\n", name);
   }       /* Loop back to do another lookup */
 
@@ -786,8 +873,8 @@ Returns:                DNS_SUCCEED   successful lookup
 */
 
 int
-dns_special_lookup(dns_answer *dnsa, uschar *name, int type,
-  uschar **fully_qualified_name)
+dns_special_lookup(dns_answer *dnsa, const uschar *name, int type,
+  const uschar **fully_qualified_name)
 {
 if (type >= 0) return dns_lookup(dnsa, name, type, fully_qualified_name);
 
@@ -800,7 +887,7 @@ root servers. */
 
 if (type == T_ZNS)
   {
-  uschar *d = name;
+  const uschar *d = name;
   while (d != 0)
     {
     int rc = dns_lookup(dnsa, d, T_NS, fully_qualified_name);
@@ -833,7 +920,7 @@ if (type == T_CSA)
   rc = dns_lookup(dnsa, srvname, T_SRV, NULL);
   if (rc == DNS_SUCCEED || rc == DNS_AGAIN)
     {
-    if (rc == DNS_SUCCEED) *fully_qualified_name = name;
+    if (rc == DNS_SUCCEED) *fully_qualified_name = string_copy(name);
     return rc;
     }
 
@@ -941,7 +1028,7 @@ if (type == T_CSA)
       /* Extract the numerical SRV fields (p is incremented) */
       p = rr->data;
       GETSHORT(priority, p);
-      GETSHORT(weight, p);
+      GETSHORT(weight, p);     weight = weight; /* compiler quietening */
       GETSHORT(port, p);
 
       /* Check the CSA version number */
@@ -1183,4 +1270,16 @@ else
 return yield;
 }
 
+
+
+void
+dns_pattern_init(void)
+{
+if (check_dns_names_pattern[0] != 0 && !regex_check_dns_names)
+  regex_check_dns_names =
+    regex_must_compile(check_dns_names_pattern, FALSE, TRUE);
+}
+
+/* vi: aw ai sw=2
+*/
 /* End of dns.c */