Support service names for tls_on_connect_ports. Bug 72
[exim.git] / src / src / host.c
index 18821035c5e73a7cd268b3f1e307d3b5530a3c80..00524f41631db22ebf98bde88f75fe37dab4ab56 100644 (file)
@@ -1,10 +1,8 @@
-/* $Cambridge: exim/src/src/host.c,v 1.29 2007/10/18 12:01:00 nm4 Exp $ */
-
 /*************************************************
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
-/* Copyright (c) University of Cambridge 1995 - 2007 */
+/* Copyright (c) University of Cambridge 1995 - 2012 */
 /* See the file NOTICE for conditions of use and distribution. */
 
 /* Functions for finding hosts, either by gethostbyname(), gethostbyaddr(), or
@@ -70,6 +68,9 @@ sprintf(addr, "%d.%d.%d.%d",
 very good for the uses to which it is put. When running the regression tests,
 start with a fixed seed.
 
+If you need better, see vaguely_random_number() which is potentially stronger,
+if a crypto library is available, but might end up just calling this instead.
+
 Arguments:
   limit:    one more than the largest number required
 
@@ -79,6 +80,8 @@ Returns:    a pseudo-random number in the range 0 to limit-1
 int
 random_number(int limit)
 {
+if (limit < 1)
+  return 0;
 if (random_seed == 0)
   {
   if (running_in_test_harness) random_seed = 42; else
@@ -217,6 +220,8 @@ else
   int rc = dns_lookup(&dnsa, lname, type, NULL);
   int count = 0;
 
+  lookup_dnssec_authenticated = NULL;
+
   switch(rc)
     {
     case DNS_SUCCEED: break;
@@ -1174,19 +1179,15 @@ host_is_tls_on_connect_port(int port)
 {
 int sep = 0;
 uschar buffer[32];
-uschar *list = tls_on_connect_ports;
+uschar *list = tls_in.on_connect_ports;
 uschar *s;
+uschar *end;
 
-if (tls_on_connect) return TRUE;
+if (tls_in.on_connect) return TRUE;
 
-while ((s = string_nextinlist(&list, &sep, buffer, sizeof(buffer))) != NULL)
-  {
-  uschar *end;
-  int lport = Ustrtol(s, &end, 10);
-  if (*end != 0) log_write(0, LOG_MAIN|LOG_PANIC_DIE, "tls_on_connect_ports "
-    "contains \"%s\", which is not a port number: exim abandoned", s);
-  if (lport == port) return TRUE;
-  }
+while ((s = string_nextinlist(&list, &sep, buffer, sizeof(buffer))))
+  if (Ustrtol(s, &end, 10) == port)
+    return TRUE;
 
 return FALSE;
 }
@@ -1594,7 +1595,7 @@ dns_record *rr;
 dns_answer dnsa;
 dns_scan dnss;
 
-host_lookup_deferred = host_lookup_failed = FALSE;
+sender_host_dnssec = host_lookup_deferred = host_lookup_failed = FALSE;
 
 HDEBUG(D_host_lookup)
   debug_printf("looking up host name for %s\n", sender_host_address);
@@ -1619,7 +1620,7 @@ while ((ordername = string_nextinlist(&list, &sep, buffer, sizeof(buffer)))
   {
   if (strcmpic(ordername, US"bydns") == 0)
     {
-    dns_init(FALSE, FALSE);
+    dns_init(FALSE, FALSE, FALSE);    /* dnssec ctrl by dns_dnssec_ok glbl */
     dns_build_reverse(sender_host_address, buffer);
     rc = dns_lookup(&dnsa, buffer, T_PTR, NULL);
 
@@ -1636,6 +1637,13 @@ while ((ordername = string_nextinlist(&list, &sep, buffer, sizeof(buffer)))
       int count = 0;
       int old_pool = store_pool;
 
+      /* Ideally we'd check DNSSEC both forward and reverse, but we use the
+      gethost* routines for forward, so can't do that unless/until we rewrite. */
+      sender_host_dnssec = dns_is_secure(&dnsa);
+      DEBUG(D_dns)
+        debug_printf("Reverse DNS security status: %s\n",
+            sender_host_dnssec ? "DNSSEC verified (AD)" : "unverified");
+
       store_pool = POOL_PERM;        /* Save names in permanent storage */
 
       for (rr = dns_next_rr(&dnsa, &dnss, RESET_ANSWERS);
@@ -1909,7 +1917,8 @@ if (running_in_test_harness)
 some circumstances when the get..byname() function actually calls the DNS. */
 
 dns_init((flags & HOST_FIND_QUALIFY_SINGLE) != 0,
-         (flags & HOST_FIND_SEARCH_PARENTS) != 0);
+         (flags & HOST_FIND_SEARCH_PARENTS) != 0,
+        FALSE);        /*XXX dnssec? */
 
 /* In an IPv6 world, unless IPv6 has been disabled, we need to scan for both
 kinds of address, so go round the loop twice. Note that we have ensured that
@@ -2052,6 +2061,7 @@ for (i = 1; i <= times;
       host->port = PORT_NONE;
       host->status = hstatus_unknown;
       host->why = hwhy_unknown;
+      host->dnssec = DS_UNK;
       last = host;
       }
 
@@ -2067,6 +2077,7 @@ for (i = 1; i <= times;
       next->port = PORT_NONE;
       next->status = hstatus_unknown;
       next->why = hwhy_unknown;
+      next->dnssec = DS_UNK;
       next->last_try = 0;
       next->next = last->next;
       last->next = next;
@@ -2185,6 +2196,7 @@ Arguments:
   fully_qualified_name  if not NULL, return fully qualified name here if
                           the contents are different (i.e. it must be preset
                           to something)
+  dnnssec_require      if TRUE check the DNS result AD bit
 
 Returns:       HOST_FIND_FAILED     couldn't find A record
                HOST_FIND_AGAIN      try again later
@@ -2194,7 +2206,8 @@ Returns:       HOST_FIND_FAILED     couldn't find A record
 
 static int
 set_address_from_dns(host_item *host, host_item **lastptr,
-  uschar *ignore_target_hosts, BOOL allow_ip, uschar **fully_qualified_name)
+  uschar *ignore_target_hosts, BOOL allow_ip, uschar **fully_qualified_name,
+  BOOL dnssec_requested, BOOL dnssec_require)
 {
 dns_record *rr;
 host_item *thishostlast = NULL;    /* Indicates not yet filled in anything */
@@ -2255,6 +2268,8 @@ for (; i >= 0; i--)
   dns_scan dnss;
 
   int rc = dns_lookup(&dnsa, host->name, type, fully_qualified_name);
+  lookup_dnssec_authenticated = !dnssec_requested ? NULL
+    : dns_is_secure(&dnsa) ? US"yes" : US"no";
 
   /* We want to return HOST_FIND_AGAIN if one of the A, A6, or AAAA lookups
   fails or times out, but not if another one succeeds. (In the early
@@ -2277,6 +2292,12 @@ for (; i >= 0; i--)
     if (rc != DNS_NOMATCH && rc != DNS_NODATA) v6_find_again = TRUE;
     continue;
     }
+  if (dnssec_require && !dns_is_secure(&dnsa))
+    {
+    log_write(L_host_lookup_failed, LOG_MAIN, "dnssec fail on %s for %.256s",
+               i>1 ? "A6" : i>0 ? "AAAA" : "A", host->name);
+    continue;
+    }
 
   /* Lookup succeeded: fill in the given host item with the first non-ignored
   address found; create additional items for any others. A single A6 record
@@ -2423,6 +2444,8 @@ Arguments:
   srv_service           when SRV used, the service name
   srv_fail_domains      DNS errors for these domains => assume nonexist
   mx_fail_domains       DNS errors for these domains => assume nonexist
+  dnssec_request_domains => make dnssec request
+  dnssec_require_domains => ditto and nonexist failures
   fully_qualified_name  if not NULL, return fully-qualified name
   removed               set TRUE if local host was removed from the list
 
@@ -2440,6 +2463,7 @@ Returns:                HOST_FIND_FAILED  Failed to find the host or domain;
 int
 host_find_bydns(host_item *host, uschar *ignore_target_hosts, int whichrrs,
   uschar *srv_service, uschar *srv_fail_domains, uschar *mx_fail_domains,
+  uschar *dnssec_request_domains, uschar *dnssec_require_domains,
   uschar **fully_qualified_name, BOOL *removed)
 {
 host_item *h, *last;
@@ -2449,6 +2473,12 @@ int ind_type = 0;
 int yield;
 dns_answer dnsa;
 dns_scan dnss;
+BOOL dnssec_require = match_isinlist(host->name, &dnssec_require_domains,
+                                   0, NULL, NULL, MCL_DOMAIN, TRUE, NULL) == OK;
+BOOL dnssec_request = dnssec_require
+                   || match_isinlist(host->name, &dnssec_request_domains,
+                                   0, NULL, NULL, MCL_DOMAIN, TRUE, NULL) == OK;
+dnssec_status_t dnssec;
 
 /* Set the default fully qualified name to the incoming name, initialize the
 resolver if necessary, set up the relevant options, and initialize the flag
@@ -2456,7 +2486,9 @@ that gets set for DNS syntax check errors. */
 
 if (fully_qualified_name != NULL) *fully_qualified_name = host->name;
 dns_init((whichrrs & HOST_FIND_QUALIFY_SINGLE) != 0,
-         (whichrrs & HOST_FIND_SEARCH_PARENTS) != 0);
+         (whichrrs & HOST_FIND_SEARCH_PARENTS) != 0,
+        dnssec_request
+        );
 host_find_failed_syntax = FALSE;
 
 /* First, if requested, look for SRV records. The service name is given; we
@@ -2477,20 +2509,37 @@ if ((whichrrs & HOST_FIND_BY_SRV) != 0)
   the input name, pass back the new original domain, without the prepended
   magic. */
 
+  dnssec = DS_UNK;
+  lookup_dnssec_authenticated = NULL;
   rc = dns_lookup(&dnsa, buffer, ind_type, &temp_fully_qualified_name);
+
+  if (dnssec_request)
+    {
+    if (dns_is_secure(&dnsa))
+      { dnssec = DS_YES; lookup_dnssec_authenticated = US"yes"; }
+    else
+      { dnssec = DS_NO; lookup_dnssec_authenticated = US"no"; }
+    }
+
   if (temp_fully_qualified_name != buffer && fully_qualified_name != NULL)
     *fully_qualified_name = temp_fully_qualified_name + prefix_length;
 
   /* On DNS failures, we give the "try again" error unless the domain is
   listed as one for which we continue. */
 
+  if (rc == DNS_SUCCEED && dnssec_require && !dns_is_secure(&dnsa))
+    {
+    log_write(L_host_lookup_failed, LOG_MAIN,
+               "dnssec fail on SRV for %.256s", host->name);
+    rc = DNS_FAIL;
+    }
   if (rc == DNS_FAIL || rc == DNS_AGAIN)
     {
     #ifndef STAND_ALONE
     if (match_isinlist(host->name, &srv_fail_domains, 0, NULL, NULL, MCL_DOMAIN,
         TRUE, NULL) != OK)
     #endif
-      return HOST_FIND_AGAIN;
+      { yield = HOST_FIND_AGAIN; goto out; }
     DEBUG(D_host_lookup) debug_printf("DNS_%s treated as DNS_NODATA "
       "(domain in srv_fail_domains)\n", (rc == DNS_FAIL)? "FAIL":"AGAIN");
     }
@@ -2506,17 +2555,41 @@ listed as one for which we continue. */
 if (rc != DNS_SUCCEED && (whichrrs & HOST_FIND_BY_MX) != 0)
   {
   ind_type = T_MX;
+  dnssec = DS_UNK;
+  lookup_dnssec_authenticated = NULL;
   rc = dns_lookup(&dnsa, host->name, ind_type, fully_qualified_name);
-  if (rc == DNS_NOMATCH) return HOST_FIND_FAILED;
-  if (rc == DNS_FAIL || rc == DNS_AGAIN)
+
+  if (dnssec_request)
     {
-    #ifndef STAND_ALONE
-    if (match_isinlist(host->name, &mx_fail_domains, 0, NULL, NULL, MCL_DOMAIN,
-        TRUE, NULL) != OK)
-    #endif
-      return HOST_FIND_AGAIN;
-    DEBUG(D_host_lookup) debug_printf("DNS_%s treated as DNS_NODATA "
-      "(domain in mx_fail_domains)\n", (rc == DNS_FAIL)? "FAIL":"AGAIN");
+    if (dns_is_secure(&dnsa))
+      { dnssec = DS_YES; lookup_dnssec_authenticated = US"yes"; }
+    else
+      { dnssec = DS_NO; lookup_dnssec_authenticated = US"no"; }
+    }
+
+  switch (rc)
+    {
+    case DNS_NOMATCH:
+      yield = HOST_FIND_FAILED; goto out;
+
+    case DNS_SUCCEED:
+      if (!dnssec_require || dns_is_secure(&dnsa))
+       break;
+      log_write(L_host_lookup_failed, LOG_MAIN,
+                 "dnssec fail on MX for %.256s", host->name);
+      rc = DNS_FAIL;
+      /*FALLTRHOUGH*/
+
+    case DNS_FAIL:
+    case DNS_AGAIN:
+      #ifndef STAND_ALONE
+      if (match_isinlist(host->name, &mx_fail_domains, 0, NULL, NULL, MCL_DOMAIN,
+         TRUE, NULL) != OK)
+      #endif
+       { yield = HOST_FIND_AGAIN; goto out; }
+      DEBUG(D_host_lookup) debug_printf("DNS_%s treated as DNS_NODATA "
+       "(domain in mx_fail_domains)\n", (rc == DNS_FAIL)? "FAIL":"AGAIN");
+      break;
     }
   }
 
@@ -2529,14 +2602,25 @@ if (rc != DNS_SUCCEED)
   if ((whichrrs & HOST_FIND_BY_A) == 0)
     {
     DEBUG(D_host_lookup) debug_printf("Address records are not being sought\n");
-    return HOST_FIND_FAILED;
+    yield = HOST_FIND_FAILED;
+    goto out;
     }
 
   last = host;        /* End of local chainlet */
   host->mx = MX_NONE;
   host->port = PORT_NONE;
+  dnssec = DS_UNK;
+  lookup_dnssec_authenticated = NULL;
   rc = set_address_from_dns(host, &last, ignore_target_hosts, FALSE,
-    fully_qualified_name);
+    fully_qualified_name, dnssec_request, dnssec_require);
+
+  if (dnssec_request)
+    {
+    if (dns_is_secure(&dnsa))
+      { dnssec = DS_YES; lookup_dnssec_authenticated = US"yes"; }
+    else
+      { dnssec = DS_NO; lookup_dnssec_authenticated = US"no"; }
+    }
 
   /* If one or more address records have been found, check that none of them
   are local. Since we know the host items all have their IP addresses
@@ -2563,7 +2647,8 @@ if (rc != DNS_SUCCEED)
       }
     }
 
-  return rc;
+  yield = rc;
+  goto out;
   }
 
 /* We have found one or more MX or SRV records. Sort them according to
@@ -2606,9 +2691,7 @@ for (rr = dns_next_rr(&dnsa, &dnss, RESET_ANSWERS);
   the same precedence to sort randomly. */
 
   if (ind_type == T_MX)
-    {
     weight = random_number(500);
-    }
 
   /* SRV records are specified with a port and a weight. The weight is used
   in a special algorithm. However, to start with, we just use it to order the
@@ -2672,6 +2755,7 @@ for (rr = dns_next_rr(&dnsa, &dnss, RESET_ANSWERS);
     host->sort_key = precedence * 1000 + weight;
     host->status = hstatus_unknown;
     host->why = hwhy_unknown;
+    host->dnssec = dnssec;
     last = host;
     }
 
@@ -2688,6 +2772,7 @@ for (rr = dns_next_rr(&dnsa, &dnss, RESET_ANSWERS);
     next->sort_key = sort_key;
     next->status = hstatus_unknown;
     next->why = hwhy_unknown;
+    next->dnssec = dnssec;
     next->last_try = 0;
 
     /* Handle the case when we have to insert before the first item. */
@@ -2747,7 +2832,8 @@ if (ind_type == T_SRV)
   if (host == last && host->name[0] == 0)
     {
     DEBUG(D_host_lookup) debug_printf("the single SRV record is \".\"\n");
-    return HOST_FIND_FAILED;
+    yield = HOST_FIND_FAILED;
+    goto out;
     }
 
   DEBUG(D_host_lookup)
@@ -2857,12 +2943,14 @@ otherwise invalid host names obtained from MX or SRV records can cause trouble
 if they happen to match something local. */
 
 yield = HOST_FIND_FAILED;    /* Default yield */
-dns_init(FALSE, FALSE);      /* Disable qualify_single and search_parents */
+dns_init(FALSE, FALSE,       /* Disable qualify_single and search_parents */
+        dnssec_request || dnssec_require);
 
 for (h = host; h != last->next; h = h->next)
   {
   if (h->address != NULL) continue;  /* Inserted by a multihomed host */
-  rc = set_address_from_dns(h, &last, ignore_target_hosts, allow_mx_to_ip, NULL);
+  rc = set_address_from_dns(h, &last, ignore_target_hosts, allow_mx_to_ip,
+    NULL, dnssec_request, dnssec_require);
   if (rc != HOST_FOUND)
     {
     h->status = hstatus_unusable;
@@ -2971,6 +3059,9 @@ DEBUG(D_host_lookup)
     }
   }
 
+out:
+
+dns_init(FALSE, FALSE, FALSE); /* clear the dnssec bit for getaddrbyname */
 return yield;
 }
 
@@ -2992,6 +3083,8 @@ int whichrrs = HOST_FIND_BY_MX | HOST_FIND_BY_A;
 BOOL byname = FALSE;
 BOOL qualify_single = TRUE;
 BOOL search_parents = FALSE;
+BOOL request_dnssec = FALSE;
+BOOL require_dnssec = FALSE;
 uschar **argv = USS cargv;
 uschar buffer[256];
 
@@ -3011,7 +3104,7 @@ if (argc > 1) primary_hostname = argv[1];
 
 /* So that debug level changes can be done first */
 
-dns_init(qualify_single, search_parents);
+dns_init(qualify_single, search_parents, FALSE);
 
 printf("Testing host lookup\n");
 printf("> ");
@@ -3037,10 +3130,14 @@ while (Ufgets(buffer, 256, stdin) != NULL)
     whichrrs = HOST_FIND_BY_SRV | HOST_FIND_BY_MX;
   else if (Ustrcmp(buffer, "srv+mx+a") == 0)
     whichrrs = HOST_FIND_BY_SRV | HOST_FIND_BY_MX | HOST_FIND_BY_A;
-  else if (Ustrcmp(buffer, "qualify_single") == 0) qualify_single = TRUE;
+  else if (Ustrcmp(buffer, "qualify_single")    == 0) qualify_single = TRUE;
   else if (Ustrcmp(buffer, "no_qualify_single") == 0) qualify_single = FALSE;
-  else if (Ustrcmp(buffer, "search_parents") == 0) search_parents = TRUE;
+  else if (Ustrcmp(buffer, "search_parents")    == 0) search_parents = TRUE;
   else if (Ustrcmp(buffer, "no_search_parents") == 0) search_parents = FALSE;
+  else if (Ustrcmp(buffer, "request_dnssec")    == 0) request_dnssec = TRUE;
+  else if (Ustrcmp(buffer, "no_request_dnssec") == 0) request_dnssec = FALSE;
+  else if (Ustrcmp(buffer, "require_dnssec")    == 0) require_dnssec = TRUE;
+  else if (Ustrcmp(buffer, "no_reqiret_dnssec") == 0) require_dnssec = FALSE;
   else if (Ustrcmp(buffer, "test_harness") == 0)
     running_in_test_harness = !running_in_test_harness;
   else if (Ustrcmp(buffer, "ipv6") == 0) disable_ipv6 = !disable_ipv6;
@@ -3073,11 +3170,12 @@ while (Ufgets(buffer, 256, stdin) != NULL)
     if (qualify_single) flags |= HOST_FIND_QUALIFY_SINGLE;
     if (search_parents) flags |= HOST_FIND_SEARCH_PARENTS;
 
-    rc = byname?
-      host_find_byname(&h, NULL, flags, &fully_qualified_name, TRUE)
-      :
-      host_find_bydns(&h, NULL, flags, US"smtp", NULL, NULL,
-        &fully_qualified_name, NULL);
+    rc = byname
+      ? host_find_byname(&h, NULL, flags, &fully_qualified_name, TRUE)
+      : host_find_bydns(&h, NULL, flags, US"smtp", NULL, NULL,
+                       request_dnssec ? &h.name : NULL,
+                       require_dnssec ? &h.name : NULL,
+                       &fully_qualified_name, NULL);
 
     if (rc == HOST_FIND_FAILED) printf("Failed\n");
       else if (rc == HOST_FIND_AGAIN) printf("Again\n");
@@ -3136,4 +3234,6 @@ return 0;
 }
 #endif  /* STAND_ALONE */
 
+/* vi: aw ai sw=2
+*/
 /* End of host.c */