Allow port setting on lists of hosts in manualroute, queryprogram,
[exim.git] / src / src / host.c
index 0acafd8a83f97c262f9bfefc84bedc9237979a0d..9f3a068707fa758953e95829d54daecf04b8c335 100644 (file)
@@ -1,10 +1,10 @@
-/* $Cambridge: exim/src/src/host.c,v 1.1 2004/10/07 10:39:01 ph10 Exp $ */
+/* $Cambridge: exim/src/src/host.c,v 1.12 2005/08/09 13:31:52 ph10 Exp $ */
 
 /*************************************************
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
-/* Copyright (c) University of Cambridge 1995 - 2004 */
+/* Copyright (c) University of Cambridge 1995 - 2005 */
 /* See the file NOTICE for conditions of use and distribution. */
 
 /* Functions for finding hosts, either by gethostbyname(), gethostbyaddr(), or
@@ -40,6 +40,9 @@ with these comments:
   code by Stuart Levy
   as seen in comp.sys.sgi.admin
 
+August 2005: Apparently this is also needed for AIX systems; USE_INET_NTOA_FIX
+should now be set for them as well.
+
 Arguments:  sa  an in_addr structure
 Returns:        pointer to static text string
 */
@@ -90,6 +93,48 @@ return (unsigned int)(random_seed >> 16) % limit;
 
 
 
+/*************************************************
+*         Sort addresses when testing            *
+*************************************************/
+
+/* This function is called only when running in the test harness. It sorts a
+number of multihomed host IP addresses into the order, so as to get
+repeatability. This doesn't have to be efficient. But don't interchange IPv4
+and IPv6 addresses!
+
+Arguments:
+  host        -> the first host item
+  last        -> the last host item
+
+Returns:      nothing
+*/
+
+static void
+sort_addresses(host_item *host, host_item *last)
+{
+BOOL done = FALSE;
+while (!done)
+  {
+  host_item *h;
+  done = TRUE;
+  for (h = host; h != last; h = h->next)
+    {
+    if ((Ustrchr(h->address, ':') == NULL) !=
+        (Ustrchr(h->next->address, ':') == NULL))
+      continue;
+    if (Ustrcmp(h->address, h->next->address) > 0)
+      {
+      uschar *temp = h->address;
+      h->address = h->next->address;
+      h->next->address = temp;
+      done = FALSE;
+      }
+    }
+  }
+}
+
+
+
 /*************************************************
 *       Build chain of host items from list      *
 *************************************************/
@@ -192,7 +237,7 @@ Returns:     0 if there is no port, else the port number. If there's a syntax
 */
 
 int
-host_extract_port(uschar *address)
+host_address_extract_port(uschar *address)
 {
 int port = 0;
 uschar *endptr;
@@ -236,6 +281,59 @@ return port;
 }
 
 
+/*************************************************
+*         Get port from a host item's name       *
+*************************************************/
+
+/* This function is called when finding the IP address for a host that is in a
+list of hosts explicitly configured, such as in the manualroute router, or in a
+fallback hosts list. We see if there is a port specification at the end of the
+host name, and if so, remove it. A minimum length of 3 is required for the
+original name; nothing shorter is recognized as having a port.
+
+We test for a name ending with a sequence of digits; if preceded by colon we
+have a port if the character before the colon is ] and the name starts with [
+or if there are no other colons in the name (i.e. it's not an IPv6 address).
+
+Arguments:  pointer to the host item
+Returns:    a port number or PORT_NONE
+*/
+
+int
+host_item_get_port(host_item *h)
+{
+uschar *p;
+int port, x;
+int len = Ustrlen(h->name);
+
+if (len < 3 || (p = h->name + len - 1, !isdigit(*p))) return PORT_NONE;
+
+/* Extract potential port number */
+
+port = *p-- - '0';
+x = 10;
+
+while (p > h->name + 1 && isdigit(*p))
+  {
+  port += (*p-- - '0') * x;
+  x *= 10;
+  }
+
+/* The smallest value of p at this point is h->name + 1. */
+
+if (*p != ':') return PORT_NONE;
+
+if (p[-1] == ']' && h->name[0] == '[')
+  h->name = string_copyn(h->name + 1, p - h->name - 2);
+else if (Ustrchr(h->name, ':') == p)
+  h->name = string_copyn(h->name, p - h->name);
+else return PORT_NONE;
+
+DEBUG(D_route|D_host_lookup) debug_printf("host=%s port=%d\n", h->name, port);
+return port;
+}
+
+
 
 #ifndef STAND_ALONE    /* Omit when standalone testing */
 
@@ -450,8 +548,8 @@ ip_address_item *next;
 
 while ((s = string_nextinlist(&list, &sep, buffer, sizeof(buffer))) != NULL)
   {
-  int port = host_extract_port(s);            /* Leaves just the IP address */
-  if (!string_is_ip_address(s, NULL))
+  int port = host_address_extract_port(s);            /* Leaves just the IP address */
+  if (string_is_ip_address(s, NULL) == 0)
     log_write(0, LOG_MAIN|LOG_PANIC_DIE, "Malformed IP address \"%s\" in %s",
       s, name);
 
@@ -693,9 +791,10 @@ host_aton(uschar *address, int *bin)
 int x[4];
 int v4offset = 0;
 
-/* Handle IPv6 address, which may end with an IPv4 address. This code is NOT
-enclosed in #if HAVE_IPV6 in order that IPv6 addresses are recognized even if
-IPv6 is not supported. */
+/* Handle IPv6 address, which may end with an IPv4 address. It may also end
+with a "scope", introduced by a percent sign. This code is NOT enclosed in #if
+HAVE_IPV6 in order that IPv6 addresses are recognized even if IPv6 is not
+supported. */
 
 if (Ustrchr(address, ':') != NULL)
   {
@@ -712,12 +811,18 @@ if (Ustrchr(address, ':') != NULL)
 
   if (*p == ':') p++;
 
-  /* Split the address into components separated by colons. */
+  /* Split the address into components separated by colons. The input address
+  is supposed to be checked for syntax. There was a case where this was
+  overlooked; to guard against that happening again, check here and crash if
+  there are too many components. */
 
-  while (*p != 0)
+  while (*p != 0 && *p != '%')
     {
-    int len = Ustrcspn(p, ":");
+    int len = Ustrcspn(p, ":%");
     if (len == 0) nulloffset = ci;
+    if (ci > 7) log_write(0, LOG_MAIN|LOG_PANIC_DIE,
+      "Internal error: invalid IPv6 address \"%s\" passed to host_aton()",
+      address);
     component[ci++] = p;
     p += len;
     if (*p == ':') p++;
@@ -760,7 +865,7 @@ if (Ustrchr(address, ':') != NULL)
 
 /* Handle IPv4 address */
 
-sscanf(CS address, "%d.%d.%d.%d", x, x+1, x+2, x+3);
+(void)sscanf(CS address, "%d.%d.%d.%d", x, x+1, x+2, x+3);
 bin[v4offset] = (x[0] << 24) + (x[1] << 16) + (x[2] << 8) + x[3];
 return v4offset+1;
 }
@@ -814,22 +919,24 @@ for (i = 0; i < count; i++)
 /* We can't use host_ntoa() because it assumes the binary values are in network
 byte order, and these are the result of host_aton(), which puts them in ints in
 host byte order. Also, we really want IPv6 addresses to be in a canonical
-format, so we output them with no abbreviation. However, we can't use the
-normal colon separator in them because it terminates keys in lsearch files, so
-use dot instead.
+format, so we output them with no abbreviation. In a number of cases we can't
+use the normal colon separator in them because it terminates keys in lsearch
+files, so we want to use dot instead. There's an argument that specifies what
+to use for IPv6 addresses.
 
 Arguments:
   count       1 or 4 (number of ints)
   binary      points to the ints
   mask        mask value; if < 0 don't add to result
   buffer      big enough to hold the result
+  sep         component separator character for IPv6 addresses
 
 Returns:      the number of characters placed in buffer, not counting
               the final nul.
 */
 
 int
-host_nmtoa(int count, int *binary, int mask, uschar *buffer)
+host_nmtoa(int count, int *binary, int mask, uschar *buffer, int sep)
 {
 int i, j;
 uschar *tt = buffer;
@@ -848,12 +955,12 @@ else
   for (i = 0; i < 4; i++)
     {
     j = binary[i];
-    sprintf(CS tt, "%04x.%04x.", (j >> 16) & 0xffff, j & 0xffff);
+    sprintf(CS tt, "%04x%c%04x%c", (j >> 16) & 0xffff, sep, j & 0xffff, sep);
     while (*tt) tt++;
     }
   }
 
-tt--;   /* lose final . */
+tt--;   /* lose final separator */
 
 if (mask < 0)
   *tt = 0;
@@ -1285,9 +1392,11 @@ Returns:      OK on success, the answer being placed in the global variable
 
 The variable host_lookup_msg is set to an empty string on sucess, or to a
 reason for the failure otherwise, in a form suitable for tagging onto an error
-message, and also host_lookup_failed is set TRUE if the lookup failed. Any
-dynamically constructed string for host_lookup_msg must be in permanent store,
-because it might be used for several incoming messages on the same SMTP
+message, and also host_lookup_failed is set TRUE if the lookup failed. If there
+was a defer, host_lookup_deferred is set TRUE.
+
+Any dynamically constructed string for host_lookup_msg must be in permanent
+store, because it might be used for several incoming messages on the same SMTP
 connection. */
 
 int
@@ -1304,6 +1413,8 @@ dns_record *rr;
 dns_answer dnsa;
 dns_scan dnss;
 
+host_lookup_deferred = host_lookup_failed = FALSE;
+
 HDEBUG(D_host_lookup)
   debug_printf("looking up host name for %s\n", sender_host_address);
 
@@ -1315,6 +1426,7 @@ if (running_in_test_harness &&
   {
   HDEBUG(D_host_lookup)
     debug_printf("Test harness: host name lookup returns DEFER\n");
+  host_lookup_deferred = TRUE;
   return DEFER;
   }
 
@@ -1404,6 +1516,7 @@ while ((ordername = string_nextinlist(&list, &sep, buffer, sizeof(buffer)))
       {
       HDEBUG(D_host_lookup)
         debug_printf("IP address PTR lookup gave temporary error\n");
+      host_lookup_deferred = TRUE;
       return DEFER;
       }
     }
@@ -1414,9 +1527,12 @@ while ((ordername = string_nextinlist(&list, &sep, buffer, sizeof(buffer)))
     {
     HDEBUG(D_host_lookup)
       debug_printf("IP address lookup using gethostbyaddr()\n");
-
     rc = host_name_lookup_byaddr();
-    if (rc == DEFER) return rc;        /* Can't carry on */
+    if (rc == DEFER)
+      {
+      host_lookup_deferred = TRUE;
+      return rc;                       /* Can't carry on */
+      }
     if (rc == OK) break;               /* Found a name */
     }
   }      /* Loop for bydns/byaddr scanning */
@@ -1430,8 +1546,7 @@ if (sender_host_name == NULL)
     log_write(L_host_lookup_failed, LOG_MAIN, "no host name found for IP "
       "address %s", sender_host_address);
   host_lookup_msg = US" (failed to find host name from IP address)";
-
-host_lookup_failed = TRUE;
+  host_lookup_failed = TRUE;
   return FAIL;
   }
 
@@ -1519,6 +1634,7 @@ for (hname = sender_host_name; hname != NULL; hname = *aliases++)
   else if (rc == HOST_FIND_AGAIN)
     {
     HDEBUG(D_host_lookup) debug_printf("temporary error for host name lookup\n");
+    host_lookup_deferred = TRUE;
     return DEFER;
     }
   else
@@ -1563,7 +1679,6 @@ store_pool = POOL_PERM;
 host_lookup_msg = string_sprintf(" (%s does not match any IP address for %s)",
   sender_host_address, save_hostname);
 store_pool = old_pool;
-
 host_lookup_failed = TRUE;
 return FAIL;
 }
@@ -1613,6 +1728,19 @@ int i, yield, times;
 uschar **addrlist;
 host_item *last = NULL;
 BOOL temp_error = FALSE;
+#if HAVE_IPV6
+int af;
+#endif
+
+/* If we are in the test harness, a name ending in .test.again.dns always
+forces a temporary error response. */
+
+if (running_in_test_harness)
+  {
+  uschar *endname = host->name + Ustrlen(host->name);
+  if (Ustrcmp(endname - 14, "test.again.dns") == 0)
+    return HOST_FIND_AGAIN;
+  }
 
 /* In an IPv6 world, 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
@@ -1621,8 +1749,6 @@ matches the domain, we also just do IPv4 lookups here (except when testing
 standalone). */
 
 #if HAVE_IPV6
-  int af;
-
   #ifndef STAND_ALONE
   if (dns_ipv4_lookup != NULL &&
         match_isinlist(host->name, &dns_ipv4_lookup, 0, NULL, NULL, MCL_DOMAIN,
@@ -1791,31 +1917,9 @@ yield = local_host_check?
   host_scan_for_local_hosts(host, &last, NULL) : HOST_FOUND;
 
 /* When running in the test harness, sort into the order of addresses so as to
-get repeatability. This doesn't have to be efficient. But don't interchange
-IPv4 and IPv6 addresses! */
+get repeatability. */
 
-if (running_in_test_harness)
-  {
-  BOOL done = FALSE;
-  while (!done)
-    {
-    host_item *h;
-    done = TRUE;
-    for (h = host; h != last; h = h->next)
-      {
-      if ((Ustrchr(h->address, ':') == NULL) !=
-          (Ustrchr(h->next->address, ':') == NULL))
-        continue;
-      if (Ustrcmp(h->address, h->next->address) > 0)
-        {
-        uschar *temp = h->address;
-        h->address = h->next->address;
-        h->next->address = temp;
-        done = FALSE;
-        }
-      }
-    }
-  }
+if (running_in_test_harness) sort_addresses(host, last);
 
 HDEBUG(D_host_lookup)
   {
@@ -2242,6 +2346,11 @@ if (rc != DNS_SUCCEED)
   else
     if (rc == HOST_IGNORED) rc = HOST_FIND_FAILED;  /* No special action */
 
+  /* When running in the test harness, sort into the order of addresses so as
+  to get repeatability. */
+
+  if (running_in_test_harness) sort_addresses(host, last);
+
   DEBUG(D_host_lookup)
     {
     host_item *h;
@@ -2916,12 +3025,12 @@ while (Ufgets(buffer, 256, stdin) != NULL)
   else if (Ustrcmp(buffer, "no_search_parents") == 0) search_parents = FALSE;
   else if (Ustrncmp(buffer, "retrans", 7) == 0)
     {
-    sscanf(CS(buffer+8), "%d", &dns_retrans);
+    (void)sscanf(CS(buffer+8), "%d", &dns_retrans);
     _res.retrans = dns_retrans;
     }
   else if (Ustrncmp(buffer, "retry", 5) == 0)
     {
-    sscanf(CS(buffer+6), "%d", &dns_retry);
+    (void)sscanf(CS(buffer+6), "%d", &dns_retry);
     _res.retry = dns_retry;
     }
   else if (alldigits(buffer))