Add check_rfc2047_length to disable length checking for encoded words.
[exim.git] / src / src / host.c
index 0acafd8a83f97c262f9bfefc84bedc9237979a0d..f3652c3090e0907d3d6ab9c15ab0fb7913425b9f 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.17 2005/11/11 10:02:04 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,235 @@ 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!
+
+NOTE:
+This sorting is not necessary for the new test harness, because it
+doesn't call the real DNS resolver, and its output is repeatable. However,
+until the old test harness is discarded, we need to retain this capability.
+The new harness is being developed towards the end of 2005. It will be some
+time before it can do everything that the old one can do.
+
+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;
+      }
+    }
+  }
+}
+
+
+
+/*************************************************
+*       Replace gethostbyname() when testing     *
+*************************************************/
+
+/* This function is called instead of gethostbyname(), gethostbyname2(), or
+getipnodebyname() when running in the test harness. It recognizes the name
+"manyhome.test.ex" and generates a humungous number of IP addresses. It also
+recognizes an unqualified "localhost" and forces it to the appropriate loopback
+address. IP addresses are treated as literals. For other names, it uses the DNS
+to find the host name. In the new test harness, this means it will access only
+the fake DNS resolver. In the old harness it will call the real resolver and
+access the test zone.
+
+Arguments:
+  name          the host name or a textual IP address
+  af            AF_INET or AF_INET6
+  error_num     where to put an error code:
+                HOST_NOT_FOUND/TRY_AGAIN/NO_RECOVERY/NO_DATA
+
+Returns:        a hostent structure or NULL for an error
+*/
+
+static struct hostent *
+host_fake_gethostbyname(uschar *name, int af, int *error_num)
+{
+#if HAVE_IPV6
+int alen = (af == AF_INET)? sizeof(struct in_addr):sizeof(struct in6_addr);
+#else
+int alen = sizeof(struct in_addr);
+#endif
+
+int ipa;
+uschar *lname = name;
+uschar *adds;
+uschar **alist;
+struct hostent *yield;
+dns_answer dnsa;
+dns_scan dnss;
+dns_record *rr;
+
+DEBUG(D_host_lookup)
+  debug_printf("using host_fake_gethostbyname for %s (%s)\n", name,
+    (af == AF_INET)? "IPv4" : "IPv6");
+
+/* Handle the name that needs a vast number of IP addresses */
+
+if (Ustrcmp(name, "manyhome.test.ex") == 0 && af == AF_INET)
+  {
+  int i, j;
+  yield = store_get(sizeof(struct hostent));
+  alist = store_get(2049 * sizeof(char *));
+  adds  = store_get(2048 * alen);
+  yield->h_name = CS name;
+  yield->h_aliases = NULL;
+  yield->h_addrtype = af;
+  yield->h_length = alen;
+  yield->h_addr_list = CSS alist;
+  for (i = 104; i <= 111; i++)
+    {
+    for (j = 0; j <= 255; j++)
+      {
+      *alist++ = adds;
+      *adds++ = 10;
+      *adds++ = 250;
+      *adds++ = i;
+      *adds++ = j;
+      }
+    }
+  *alist = NULL;
+  return yield;
+  }
+
+/* Handle unqualified "localhost" */
+
+if (Ustrcmp(name, "localhost") == 0)
+  lname = (af == AF_INET)? US"127.0.0.1" : US"::1";
+
+/* Handle a literal IP address */
+
+ipa = string_is_ip_address(lname, NULL);
+if (ipa != 0)
+  {
+  if ((ipa == 4 && af == AF_INET) ||
+      (ipa == 6 && af == AF_INET6))
+    {
+    int i, n;
+    int x[4];
+    yield = store_get(sizeof(struct hostent));
+    alist = store_get(2 * sizeof(char *));
+    adds  = store_get(alen);
+    yield->h_name = CS name;
+    yield->h_aliases = NULL;
+    yield->h_addrtype = af;
+    yield->h_length = alen;
+    yield->h_addr_list = CSS alist;
+    *alist++ = adds;
+    n = host_aton(lname, x);
+    for (i = 0; i < n; i++)
+      {
+      int y = x[i];
+      *adds++ = (y >> 24) & 255;
+      *adds++ = (y >> 16) & 255;
+      *adds++ = (y >> 8) & 255;
+      *adds++ = y & 255;
+      }
+    *alist = NULL;
+    }
+
+  /* Wrong kind of literal address */
+
+  else
+    {
+    *error_num = HOST_NOT_FOUND;
+    return NULL;
+    }
+  }
+
+/* Handle a host name */
+
+else
+  {
+  int type = (af == AF_INET)? T_A:T_AAAA;
+  int rc = dns_lookup(&dnsa, lname, type, NULL);
+  int count = 0;
+
+  switch(rc)
+    {
+    case DNS_SUCCEED: break;
+    case DNS_NOMATCH: *error_num = HOST_NOT_FOUND; return NULL;
+    case DNS_NODATA:  *error_num = NO_DATA; return NULL;
+    case DNS_AGAIN:   *error_num = TRY_AGAIN; return NULL;
+    default:
+    case DNS_FAIL:    *error_num = NO_RECOVERY; return NULL;
+    }
+
+  for (rr = dns_next_rr(&dnsa, &dnss, RESET_ANSWERS);
+       rr != NULL;
+       rr = dns_next_rr(&dnsa, &dnss, RESET_NEXT))
+    {
+    if (rr->type == type) count++;
+    }
+
+  yield = store_get(sizeof(struct hostent));
+  alist = store_get((count + 1) * sizeof(char **));
+  adds  = store_get(count *alen);
+
+  yield->h_name = CS name;
+  yield->h_aliases = NULL;
+  yield->h_addrtype = af;
+  yield->h_length = alen;
+  yield->h_addr_list = CSS alist;
+
+  for (rr = dns_next_rr(&dnsa, &dnss, RESET_ANSWERS);
+       rr != NULL;
+       rr = dns_next_rr(&dnsa, &dnss, RESET_NEXT))
+    {
+    int i, n;
+    int x[4];
+    dns_address *da;
+    if (rr->type != type) continue;
+    da = dns_address_from_rr(&dnsa, rr);
+    *alist++ = adds;
+    n = host_aton(da->address, x);
+    for (i = 0; i < n; i++)
+      {
+      int y = x[i];
+      *adds++ = (y >> 24) & 255;
+      *adds++ = (y >> 16) & 255;
+      *adds++ = (y >> 8) & 255;
+      *adds++ = y & 255;
+      }
+    }
+  *alist = NULL;
+  }
+
+return yield;
+}
+
+
+
 /*************************************************
 *       Build chain of host items from list      *
 *************************************************/
@@ -192,7 +424,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 +468,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 */
 
@@ -250,8 +535,9 @@ as follows:
 
 (a) No sender_host_name or sender_helo_name: "[ip address]"
 (b) Just sender_host_name: "host_name [ip address]"
-(c) Just sender_helo_name: "(helo_name) [ip address]"
-(d) The two are identical: "host_name [ip address]"
+(c) Just sender_helo_name: "(helo_name) [ip address]" unless helo is IP
+            in which case: "[ip address}"
+(d) The two are identical: "host_name [ip address]" includes helo = IP
 (e) The two are different: "host_name (helo_name) [ip address]"
 
 If log_incoming_port is set, the sending host's port number is added to the IP
@@ -272,7 +558,9 @@ Returns:    nothing
 void
 host_build_sender_fullhost(void)
 {
+BOOL show_helo = TRUE;
 uschar *address;
+int len;
 int old_pool = store_pool;
 
 if (sender_host_address == NULL) return;
@@ -288,6 +576,43 @@ address = string_sprintf("[%s]:%d", sender_host_address, sender_host_port);
 if ((log_extra_selector & LX_incoming_port) == 0 || sender_host_port <= 0)
   *(Ustrrchr(address, ':')) = 0;
 
+/* If there's no EHLO/HELO data, we can't show it. */
+
+if (sender_helo_name == NULL) show_helo = FALSE;
+
+/* If HELO/EHLO was followed by an IP literal, it's messy because of two
+features of IPv6. Firstly, there's the "IPv6:" prefix (Exim is liberal and
+doesn't require this, for historical reasons). Secondly, IPv6 addresses may not
+be given in canonical form, so we have to canonicize them before comparing. As
+it happens, the code works for both IPv4 and IPv6. */
+
+else if (sender_helo_name[0] == '[' &&
+         sender_helo_name[(len=Ustrlen(sender_helo_name))-1] == ']')
+  {
+  int offset = 1;
+  uschar *helo_ip;
+
+  if (strncmpic(sender_helo_name + 1, US"IPv6:", 5) == 0) offset += 5;
+  if (strncmpic(sender_helo_name + 1, US"IPv4:", 5) == 0) offset += 5;
+
+  helo_ip = string_copyn(sender_helo_name + offset, len - offset - 1);
+
+  if (string_is_ip_address(helo_ip, NULL) != 0)
+    {
+    int x[4], y[4];
+    int sizex, sizey;
+    uschar ipx[48], ipy[48];    /* large enough for full IPv6 */
+
+    sizex = host_aton(helo_ip, x);
+    sizey = host_aton(sender_host_address, y);
+
+    (void)host_nmtoa(sizex, x, -1, ipx, ':');
+    (void)host_nmtoa(sizey, y, -1, ipy, ':');
+
+    if (strcmpic(ipx, ipy) == 0) show_helo = FALSE;
+    }
+  }
+
 /* Host name is not verified */
 
 if (sender_host_name == NULL)
@@ -303,7 +628,7 @@ if (sender_host_name == NULL)
 
   sender_rcvhost = string_cat(NULL, &size, &ptr, address, adlen);
 
-  if (sender_ident != NULL || sender_helo_name != NULL || portptr != NULL)
+  if (sender_ident != NULL || show_helo || portptr != NULL)
     {
     int firstptr;
     sender_rcvhost = string_cat(sender_rcvhost, &size, &ptr, US" (", 2);
@@ -313,7 +638,7 @@ if (sender_host_name == NULL)
       sender_rcvhost = string_append(sender_rcvhost, &size, &ptr, 2, US"port=",
         portptr + 1);
 
-    if (sender_helo_name != NULL)
+    if (show_helo)
       sender_rcvhost = string_append(sender_rcvhost, &size, &ptr, 2,
         (firstptr == ptr)? US"helo=" : US" helo=", sender_helo_name);
 
@@ -332,24 +657,15 @@ if (sender_host_name == NULL)
   store_reset(sender_rcvhost + ptr + 1);
   }
 
-/* Host name is known and verified. */
+/* Host name is known and verified. Unless we've already found that the HELO
+data matches the IP address, compare it with the name. */
 
 else
   {
-  int len;
-  if (sender_helo_name == NULL ||
-      strcmpic(sender_host_name, sender_helo_name) == 0 ||
-        (sender_helo_name[0] == '[' &&
-         sender_helo_name[(len=Ustrlen(sender_helo_name))-1] == ']' &&
-         strncmpic(sender_helo_name+1, sender_host_address, len - 2) == 0))
-    {
-    sender_fullhost = string_sprintf("%s %s", sender_host_name, address);
-    sender_rcvhost = (sender_ident == NULL)?
-      string_sprintf("%s (%s)", sender_host_name, address) :
-      string_sprintf("%s (%s ident=%s)", sender_host_name, address,
-        sender_ident);
-    }
-  else
+  if (show_helo && strcmpic(sender_host_name, sender_helo_name) == 0)
+    show_helo = FALSE;
+
+  if (show_helo)
     {
     sender_fullhost = string_sprintf("%s (%s) %s", sender_host_name,
       sender_helo_name, address);
@@ -359,6 +675,14 @@ else
       string_sprintf("%s\n\t(%s helo=%s ident=%s)", sender_host_name,
         address, sender_helo_name, sender_ident);
     }
+  else
+    {
+    sender_fullhost = string_sprintf("%s %s", sender_host_name, address);
+    sender_rcvhost = (sender_ident == NULL)?
+      string_sprintf("%s (%s)", sender_host_name, address) :
+      string_sprintf("%s (%s ident=%s)", sender_host_name, address,
+        sender_ident);
+    }
   }
 
 store_pool = old_pool;
@@ -450,8 +774,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 +1017,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 +1037,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 +1091,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 +1145,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 +1181,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 +1618,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 +1639,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 +1652,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 +1742,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 +1753,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 +1772,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 +1860,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 +1905,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 +1954,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 +1975,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,
@@ -1657,16 +2009,27 @@ for (i = 1; i <= times;
   struct hostent *hostdata;
 
   #if HAVE_IPV6
+  if (running_in_test_harness)
+    hostdata = host_fake_gethostbyname(host->name, af, &error_num);
+  else
+    {
     #if HAVE_GETIPNODEBYNAME
     hostdata = getipnodebyname(CS host->name, af, 0, &error_num);
     #else
     hostdata = gethostbyname2(CS host->name, af);
     error_num = h_errno;
     #endif
-  #else
-  hostdata = gethostbyname(CS host->name);
-  error_num = h_errno;
-  #endif
+    }
+
+  #else    /* not HAVE_IPV6 */
+  if (running_in_test_harness)
+    hostdata = host_fake_gethostbyname(host->name, AF_INET, &error_num);
+  else
+    {
+    hostdata = gethostbyname(CS host->name);
+    error_num = h_errno;
+    }
+  #endif   /* HAVE_IPV6 */
 
   if (hostdata == NULL)
     {
@@ -1791,31 +2154,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)
   {
@@ -1919,7 +2260,6 @@ to do so. On an IPv4 system, go round the loop once only, looking only for A
 records. */
 
 #if HAVE_IPV6
-
   #ifndef STAND_ALONE
     if (dns_ipv4_lookup != NULL &&
         match_isinlist(host->name, &dns_ipv4_lookup, 0, NULL, NULL, MCL_DOMAIN,
@@ -2183,8 +2523,10 @@ if ((whichrrs & HOST_FIND_BY_SRV) != 0)
 
   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;
     DEBUG(D_host_lookup) debug_printf("DNS_%s treated as DNS_NODATA "
       "(domain in srv_fail_domains)\n", (rc == DNS_FAIL)? "FAIL":"AGAIN");
@@ -2205,8 +2547,10 @@ if (rc != DNS_SUCCEED && (whichrrs & HOST_FIND_BY_MX) != 0)
   if (rc == DNS_NOMATCH) return HOST_FIND_FAILED;
   if (rc == DNS_FAIL || rc == DNS_AGAIN)
     {
+    #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");
@@ -2242,6 +2586,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;
@@ -2847,18 +3196,6 @@ return yield;
 
 #ifdef STAND_ALONE
 
-BOOL alldigits(uschar *buffer)
-{
-if (!isdigit(*buffer)) return FALSE;
-if (*buffer == '0' && buffer[1] == 'x')
-  {
-  buffer++;
-  while (isxdigit(*(++buffer)));
-  }
-else while (isdigit(*(++buffer)));
-return (*buffer == 0);
-}
-
 int main(int argc, char **cargv)
 {
 host_item h;
@@ -2914,22 +3251,22 @@ while (Ufgets(buffer, 256, stdin) != NULL)
   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, "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, "res_debug") == 0)
+    {
+    _res.options ^= RES_DEBUG;
+    }
   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))
-    {
-    debug_selector = Ustrtol(buffer, NULL, 0);
-    _res.options &= ~RES_DEBUG;
-    DEBUG(D_resolver) _res.options |= RES_DEBUG;
-    }
   else
     {
     int flags = whichrrs;