Fix bug in previous patch: following data is permitted after '.' so it
[exim.git] / src / src / host.c
index 07d28395e58707b9ccc4d4f04d77030258008e88..b353fe2db4fd10f32daedda95145cd6ec5d4ef6a 100644 (file)
@@ -1,10 +1,10 @@
-/* $Cambridge: exim/src/src/host.c,v 1.22 2006/02/16 10:05:33 ph10 Exp $ */
+/* $Cambridge: exim/src/src/host.c,v 1.28 2007/01/08 10:50:18 ph10 Exp $ */
 
 /*************************************************
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
-/* Copyright (c) University of Cambridge 1995 - 2006 */
+/* Copyright (c) University of Cambridge 1995 - 2007 */
 /* See the file NOTICE for conditions of use and distribution. */
 
 /* Functions for finding hosts, either by gethostbyname(), gethostbyaddr(), or
@@ -1763,11 +1763,11 @@ for (hname = sender_host_name; hname != NULL; hname = *aliases++)
   h.mx = MX_NONE;
   h.address = NULL;
 
-  /* When called with the 5th argument FALSE, host_find_byname() won't return
+  /* When called with the last argument FALSE, host_find_byname() won't return
   HOST_FOUND_LOCAL. If the incoming address is an IPv4 address expressed in
   IPv6 format, we must compare the IPv4 part to any IPv4 addresses. */
 
-  if ((rc = host_find_byname(&h, NULL, NULL, FALSE)) == HOST_FOUND)
+  if ((rc = host_find_byname(&h, NULL, 0, NULL, FALSE)) == HOST_FOUND)
     {
     host_item *hh;
     HDEBUG(D_host_lookup) debug_printf("checking addresses for %s\n", hname);
@@ -1792,6 +1792,7 @@ for (hname = sender_host_name; hname != NULL; hname = *aliases++)
     {
     HDEBUG(D_host_lookup) debug_printf("temporary error for host name lookup\n");
     host_lookup_deferred = TRUE;
+    sender_host_name = NULL;
     return DEFER;
     }
   else
@@ -1848,9 +1849,12 @@ return FAIL;
 *************************************************/
 
 /* The input is a host_item structure with the name filled in and the address
-field set to NULL. We use gethostbyname(). Of course, gethostbyname() may use
-the DNS, but it doesn't do MX processing. If more than one address is given,
-chain on additional host items, with other relevant fields copied.
+field set to NULL. We use gethostbyname() or getipnodebyname() or
+gethostbyname2(), as appropriate. Of course, these functions may use the DNS,
+but they do not do MX processing. It appears, however, that in some systems the
+current setting of resolver options is used when one of these functions calls
+the resolver. For this reason, we call dns_init() at the start, with arguments
+influenced by bits in "flags", just as we do for host_find_bydns().
 
 The second argument provides a host list (usually an IP list) of hosts to
 ignore. This makes it possible to ignore IPv6 link-local addresses or loopback
@@ -1867,6 +1871,8 @@ Arguments:
                            multiple IP addresses cause other host items to be
                              chained on.
   ignore_target_hosts    a list of hosts to ignore
+  flags                  HOST_FIND_QUALIFY_SINGLE   ) passed to
+                         HOST_FIND_SEARCH_PARENTS   )   dns_init()
   fully_qualified_name   if not NULL, set to point to host name for
                          compatibility with host_find_bydns
   local_host_check       TRUE if a check for the local host is wanted
@@ -1878,7 +1884,7 @@ Returns:                 HOST_FIND_FAILED  Failed to find the host or domain
 */
 
 int
-host_find_byname(host_item *host, uschar *ignore_target_hosts,
+host_find_byname(host_item *host, uschar *ignore_target_hosts, int flags,
   uschar **fully_qualified_name, BOOL local_host_check)
 {
 int i, yield, times;
@@ -1890,15 +1896,21 @@ int af;
 #endif
 
 /* If we are in the test harness, a name ending in .test.again.dns always
-forces a temporary error response. */
+forces a temporary error response, unless the name is in
+dns_again_means_nonexist. */
 
 if (running_in_test_harness)
   {
   uschar *endname = host->name + Ustrlen(host->name);
-  if (Ustrcmp(endname - 14, "test.again.dns") == 0)
-    return HOST_FIND_AGAIN;
+  if (Ustrcmp(endname - 14, "test.again.dns") == 0) goto RETURN_AGAIN;
   }
 
+/* Make sure DNS options are set as required. This appears to be necessary in
+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);
+
 /* 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
 AF_INET6 is defined even in an IPv4 world, which makes for slightly tidier
@@ -1906,14 +1918,17 @@ code. However, if dns_ipv4_lookup matches the domain, we also just do IPv4
 lookups here (except when testing standalone). */
 
 #if HAVE_IPV6
-  #ifndef STAND_ALONE
-  if (disable_ipv6 || (dns_ipv4_lookup != NULL &&
+  #ifdef STAND_ALONE
+  if (disable_ipv6)
+  #else
+  if (disable_ipv6 ||
+    (dns_ipv4_lookup != NULL &&
         match_isinlist(host->name, &dns_ipv4_lookup, 0, NULL, NULL, MCL_DOMAIN,
           TRUE, NULL) == OK))
+  #endif
+
     { af = AF_INET; times = 1; }
   else
-  #endif  /* STAND_ALONE */
-
     { af = AF_INET6; times = 2; }
 
 /* No IPv6 support */
@@ -1936,9 +1951,13 @@ for (i = 1; i <= times;
      i++)
   {
   BOOL ipv4_addr;
-  int error_num;
+  int error_num = 0;
   struct hostent *hostdata;
 
+  #ifdef STAND_ALONE
+  printf("Looking up: %s\n", host->name);
+  #endif
+
   #if HAVE_IPV6
   if (running_in_test_harness)
     hostdata = host_fake_gethostbyname(host->name, af, &error_num);
@@ -2071,7 +2090,7 @@ if (host->address == NULL)
     string_sprintf("no IP address found for host %s", host->name);
 
   HDEBUG(D_host_lookup) debug_printf("%s\n", msg);
-  if (temp_error) return HOST_FIND_AGAIN;
+  if (temp_error) goto RETURN_AGAIN;
   if (host_checking || !log_testing_mode)
     log_write(L_host_lookup_failed, LOG_MAIN, "%s", msg);
   return HOST_FIND_FAILED;
@@ -2108,6 +2127,28 @@ HDEBUG(D_host_lookup)
 /* Return the found status. */
 
 return yield;
+
+/* Handle the case when there is a temporary error. If the name matches
+dns_again_means_nonexist, return permanent rather than temporary failure. */
+
+RETURN_AGAIN:
+  {
+  #ifndef STAND_ALONE
+  int rc;
+  uschar *save = deliver_domain;
+  deliver_domain = host->name;  /* set $domain */
+  rc = match_isinlist(host->name, &dns_again_means_nonexist, 0, NULL, NULL,
+    MCL_DOMAIN, TRUE, NULL);
+  deliver_domain = save;
+  if (rc == OK)
+    {
+    DEBUG(D_host_lookup) debug_printf("%s is in dns_again_means_nonexist: "
+      "returning HOST_FIND_FAILED\n", host->name);
+    return HOST_FIND_FAILED;
+    }
+  #endif
+  return HOST_FIND_AGAIN;
+  }
 }
 
 
@@ -2876,17 +2917,19 @@ single MX preference value, IPv6 addresses come first. This can separate the
 addresses of a multihomed host, but that should not matter. */
 
 #if HAVE_IPV6
-if (h != last)
+if (h != last && !disable_ipv6)
   {
   for (h = host; h != last; h = h->next)
     {
     host_item temp;
     host_item *next = h->next;
-    if (h->mx != next->mx ||                /* If next is different MX value */
-        (h->sort_key % 1000) < 500 ||       /* OR this one is IPv6 */
-        (next->sort_key % 1000) >= 500)     /* OR next is IPv4 */
-      continue;                             /* move on to next */
-    temp = *h;
+    if (h->mx != next->mx ||                   /* If next is different MX */
+        h->address == NULL ||                  /* OR this one is unset */
+        Ustrchr(h->address, ':') != NULL ||    /* OR this one is IPv6 */
+        (next->address != NULL &&
+         Ustrchr(next->address, ':') == NULL)) /* OR next is IPv4 */
+      continue;                                /* move on to next */
+    temp = *h;                                 /* otherwise, swap */
     temp.next = next->next;
     *h = *next;
     h->next = next;
@@ -2952,6 +2995,7 @@ BOOL search_parents = FALSE;
 uschar **argv = USS cargv;
 uschar buffer[256];
 
+disable_ipv6 = FALSE;
 primary_hostname = US"";
 store_pool = POOL_MAIN;
 debug_selector = D_host_lookup|D_interface;
@@ -2999,6 +3043,7 @@ while (Ufgets(buffer, 256, stdin) != NULL)
   else if (Ustrcmp(buffer, "no_search_parents") == 0) search_parents = 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;
   else if (Ustrcmp(buffer, "res_debug") == 0)
     {
     _res.options ^= RES_DEBUG;
@@ -3029,7 +3074,7 @@ while (Ufgets(buffer, 256, stdin) != NULL)
     if (search_parents) flags |= HOST_FIND_SEARCH_PARENTS;
 
     rc = byname?
-      host_find_byname(&h, NULL, &fully_qualified_name, TRUE)
+      host_find_byname(&h, NULL, flags, &fully_qualified_name, TRUE)
       :
       host_find_bydns(&h, NULL, flags, US"smtp", NULL, NULL,
         &fully_qualified_name, NULL);