Fix logging of max-size log line
[exim.git] / src / src / host.c
index 7fda13d3cee036417fca7b7bff176dbd77067d9b..ecdc6d681dc98272f5e4419a8d4bd677d9aa45a2 100644 (file)
@@ -2,8 +2,10 @@
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
-/* Copyright (c) University of Cambridge 1995 - 2016 */
+/* Copyright (c) The Exim Maintainers 2020 - 2022 */
+/* Copyright (c) University of Cambridge 1995 - 2018 */
 /* See the file NOTICE for conditions of use and distribution. */
+/* SPDX-License-Identifier: GPL-2.0-or-later */
 
 /* Functions for finding hosts, either by gethostbyname(), gethostbyaddr(), or
 directly via the DNS. When IPv6 is supported, getipnodebyname() and
@@ -83,13 +85,13 @@ random_number(int limit)
 if (limit < 1)
   return 0;
 if (random_seed == 0)
-  {
-  if (running_in_test_harness) random_seed = 42; else
+  if (f.running_in_test_harness)
+    random_seed = 42;
+  else
     {
     int p = (int)getpid();
     random_seed = (int)time(NULL) ^ ((p << 16) | p);
     }
-  }
 random_seed = 1103515245 * random_seed + 12345;
 return (unsigned int)(random_seed >> 16) % limit;
 }
@@ -138,7 +140,7 @@ if (!slow_lookup_log)
 time_msec = get_time_in_ms();
 retval = dns_lookup(dnsa, name, type, fully_qualified_name);
 if ((time_msec = get_time_in_ms() - time_msec) > slow_lookup_log)
-  log_long_lookup(US"name", name, time_msec);
+  log_long_lookup(dns_text_type(type), name, time_msec);
 return retval;
 }
 
@@ -177,40 +179,35 @@ const uschar *lname = name;
 uschar *adds;
 uschar **alist;
 struct hostent *yield;
-dns_answer dnsa;
+dns_answer * dnsa = store_get_dns_answer();
 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");
+    af == AF_INET ? "IPv4" : "IPv6");
 
 /* Handle unqualified "localhost" */
 
 if (Ustrcmp(name, "localhost") == 0)
-  lname = (af == AF_INET)? US"127.0.0.1" : US"::1";
+  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))
+if ((ipa = string_is_ip_address(lname, NULL)) != 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 = store_get(sizeof(struct hostent), GET_UNTAINTED);
+    alist = store_get(2 * sizeof(char *), GET_UNTAINTED);
+    adds  = store_get(alen, GET_UNTAINTED);
     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++)
+    for (int n = host_aton(lname, x), i = 0; i < n; i++)
       {
       int y = x[i];
       *adds++ = (y >> 24) & 255;
@@ -226,16 +223,16 @@ if (ipa != 0)
   else
     {
     *error_num = HOST_NOT_FOUND;
-    return NULL;
+    yield = NULL;
+    goto out;
     }
-  }
 
 /* Handle a host name */
 
 else
   {
-  int type = (af == AF_INET)? T_A:T_AAAA;
-  int rc = dns_lookup_timerwrap(&dnsa, lname, type, NULL);
+  int type = af == AF_INET ? T_A:T_AAAA;
+  int rc = dns_lookup_timerwrap(dnsa, lname, type, NULL);
   int count = 0;
 
   lookup_dnssec_authenticated = NULL;
@@ -243,22 +240,21 @@ else
   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;
+    case DNS_NOMATCH: *error_num = HOST_NOT_FOUND; yield = NULL; goto out;
+    case DNS_NODATA:  *error_num = NO_DATA; yield = NULL; goto out;
+    case DNS_AGAIN:   *error_num = TRY_AGAIN; yield = NULL; goto out;
     default:
-    case DNS_FAIL:    *error_num = NO_RECOVERY; return NULL;
+    case DNS_FAIL:    *error_num = NO_RECOVERY; yield = NULL; goto out;
     }
 
-  for (rr = dns_next_rr(&dnsa, &dnss, RESET_ANSWERS);
+  for (dns_record * rr = dns_next_rr(dnsa, &dnss, RESET_ANSWERS);
        rr;
-       rr = dns_next_rr(&dnsa, &dnss, RESET_NEXT))
-    if (rr->type == type)
-      count++;
+       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 = store_get(sizeof(struct hostent), GET_UNTAINTED);
+  alist = store_get((count + 1) * sizeof(char *), GET_UNTAINTED);
+  adds  = store_get(count *alen, GET_UNTAINTED);
 
   yield->h_name = CS name;
   yield->h_aliases = NULL;
@@ -266,18 +262,15 @@ else
   yield->h_length = alen;
   yield->h_addr_list = CSS alist;
 
-  for (rr = dns_next_rr(&dnsa, &dnss, RESET_ANSWERS);
+  for (dns_record * rr = dns_next_rr(dnsa, &dnss, RESET_ANSWERS);
        rr;
-       rr = dns_next_rr(&dnsa, &dnss, RESET_NEXT))
+       rr = dns_next_rr(dnsa, &dnss, RESET_NEXT)) if (rr->type == type)
     {
-    int i, n;
     int x[4];
     dns_address *da;
-    if (rr->type != type) continue;
-    if (!(da = dns_address_from_rr(&dnsa, rr))) break;
+    if (!(da = dns_address_from_rr(dnsa, rr))) break;
     *alist++ = adds;
-    n = host_aton(da->address, x);
-    for (i = 0; i < n; i++)
+    for (int n = host_aton(da->address, x), i = 0; i < n; i++)
       {
       int y = x[i];
       *adds++ = (y >> 24) & 255;
@@ -289,6 +282,9 @@ else
   *alist = NULL;
   }
 
+out:
+
+store_free_dns_answer(dnsa);
 return yield;
 }
 
@@ -318,12 +314,12 @@ int sep = 0;
 int fake_mx = MX_NONE;          /* This value is actually -1 */
 uschar *name;
 
-if (list == NULL) return;
+if (!list) return;
 if (randomize) fake_mx--;       /* Start at -2 for randomizing */
 
 *anchor = NULL;
 
-while ((name = string_nextinlist(&list, &sep, NULL, 0)) != NULL)
+while ((name = string_nextinlist(&list, &sep, NULL, 0)))
   {
   host_item *h;
 
@@ -333,17 +329,17 @@ while ((name = string_nextinlist(&list, &sep, NULL, 0)) != NULL)
     continue;
     }
 
-  h = store_get(sizeof(host_item));
+  h = store_get(sizeof(host_item), GET_UNTAINTED);
   h->name = name;
   h->address = NULL;
   h->port = PORT_NONE;
   h->mx = fake_mx;
-  h->sort_key = randomize? (-fake_mx)*1000 + random_number(1000) : 0;
+  h->sort_key = randomize ? (-fake_mx)*1000 + random_number(1000) : 0;
   h->status = hstatus_unknown;
   h->why = hwhy_unknown;
   h->last_try = 0;
 
-  if (*anchor == NULL)
+  if (!*anchor)
     {
     h->next = NULL;
     *anchor = h;
@@ -358,7 +354,7 @@ while ((name = string_nextinlist(&list, &sep, NULL, 0)) != NULL)
       }
     else
       {
-      while (hh->next != NULL && h->sort_key >= (hh->next)->sort_key)
+      while (hh->next && h->sort_key >= hh->next->sort_key)
         hh = hh->next;
       h->next = hh->next;
       hh->next = h;
@@ -368,77 +364,6 @@ while ((name = string_nextinlist(&list, &sep, NULL, 0)) != NULL)
 }
 
 
-
-
-
-/*************************************************
-*        Extract port from address string        *
-*************************************************/
-
-/* In the spool file, and in the -oMa and -oMi options, a host plus port is
-given as an IP address followed by a dot and a port number. This function
-decodes this.
-
-An alternative format for the -oMa and -oMi options is [ip address]:port which
-is what Exim 4 uses for output, because it seems to becoming commonly used,
-whereas the dot form confuses some programs/people. So we recognize that form
-too.
-
-Argument:
-  address    points to the string; if there is a port, the '.' in the string
-             is overwritten with zero to terminate the address; if the string
-             is in the [xxx]:ppp format, the address is shifted left and the
-             brackets are removed
-
-Returns:     0 if there is no port, else the port number. If there's a syntax
-             error, leave the incoming address alone, and return 0.
-*/
-
-int
-host_address_extract_port(uschar *address)
-{
-int port = 0;
-uschar *endptr;
-
-/* Handle the "bracketed with colon on the end" format */
-
-if (*address == '[')
-  {
-  uschar *rb = address + 1;
-  while (*rb != 0 && *rb != ']') rb++;
-  if (*rb++ == 0) return 0;        /* Missing ]; leave invalid address */
-  if (*rb == ':')
-    {
-    port = Ustrtol(rb + 1, &endptr, 10);
-    if (*endptr != 0) return 0;    /* Invalid port; leave invalid address */
-    }
-  else if (*rb != 0) return 0;     /* Bad syntax; leave invalid address */
-  memmove(address, address + 1, rb - address - 2);
-  rb[-2] = 0;
-  }
-
-/* Handle the "dot on the end" format */
-
-else
-  {
-  int skip = -3;                   /* Skip 3 dots in IPv4 addresses */
-  address--;
-  while (*(++address) != 0)
-    {
-    int ch = *address;
-    if (ch == ':') skip = 0;       /* Skip 0 dots in IPv6 addresses */
-      else if (ch == '.' && skip++ >= 0) break;
-    }
-  if (*address == 0) return 0;
-  port = Ustrtol(address + 1, &endptr, 10);
-  if (*endptr != 0) return 0;      /* Invalid port; leave invalid address */
-  *address = 0;
-  }
-
-return port;
-}
-
-
 /*************************************************
 *         Get port from a host item's name       *
 *************************************************/
@@ -520,7 +445,9 @@ There wouldn't be two different variables if I had got all this right in the
 first place.
 
 Because this data may survive over more than one incoming SMTP message, it has
-to be in permanent store.
+to be in permanent store.  However, STARTTLS has to be forgotten and redone
+on a multi-message conn, so this will be called once per message then.  Hence
+we use malloc, so we can free.
 
 Arguments:  none
 Returns:    nothing
@@ -530,13 +457,13 @@ void
 host_build_sender_fullhost(void)
 {
 BOOL show_helo = TRUE;
-uschar *address;
+uschar * address, * fullhost, * rcvhost;
+rmark reset_point;
 int len;
-int old_pool = store_pool;
 
-if (sender_host_address == NULL) return;
+if (!sender_host_address) return;
 
-store_pool = POOL_PERM;
+reset_point = store_mark();
 
 /* Set up address, with or without the port. After discussion, it seems that
 the only format that doesn't cause trouble is [aaaa]:pppp. However, we can't
@@ -549,12 +476,12 @@ if (!LOGGING(incoming_port) || sender_host_port <= 0)
 
 /* If there's no EHLO/HELO data, we can't show it. */
 
-if (sender_helo_name == NULL) show_helo = FALSE;
+if (!sender_helo_name) 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
+be given in canonical form, so we have to canonicalize them before comparing. As
 it happens, the code works for both IPv4 and IPv6. */
 
 else if (sender_helo_name[0] == '[' &&
@@ -586,46 +513,40 @@ else if (sender_helo_name[0] == '[' &&
 
 /* Host name is not verified */
 
-if (sender_host_name == NULL)
+if (!sender_host_name)
   {
   uschar *portptr = Ustrstr(address, "]:");
-  int size = 0;
-  int ptr = 0;
+  gstring * g;
   int adlen;    /* Sun compiler doesn't like ++ in initializers */
 
-  adlen = (portptr == NULL)? Ustrlen(address) : (++portptr - address);
-  sender_fullhost = (sender_helo_name == NULL)? address :
-    string_sprintf("(%s) %s", sender_helo_name, address);
+  adlen = portptr ? (++portptr - address) : Ustrlen(address);
+  fullhost = sender_helo_name
+    ? string_sprintf("(%s) %s", sender_helo_name, address)
+    : address;
 
-  sender_rcvhost = string_catn(NULL, &size, &ptr, address, adlen);
+  g = string_catn(NULL, address, adlen);
 
-  if (sender_ident != NULL || show_helo || portptr != NULL)
+  if (sender_ident || show_helo || portptr)
     {
     int firstptr;
-    sender_rcvhost = string_catn(sender_rcvhost, &size, &ptr, US" (", 2);
-    firstptr = ptr;
+    g = string_catn(g, US" (", 2);
+    firstptr = g->ptr;
 
-    if (portptr != NULL)
-      sender_rcvhost = string_append(sender_rcvhost, &size, &ptr, 2, US"port=",
-        portptr + 1);
+    if (portptr)
+      g = string_append(g, 2, US"port=", portptr + 1);
 
     if (show_helo)
-      sender_rcvhost = string_append(sender_rcvhost, &size, &ptr, 2,
-        (firstptr == ptr)? US"helo=" : US" helo=", sender_helo_name);
+      g = string_append(g, 2,
+        firstptr == g->ptr ? US"helo=" : US" helo=", sender_helo_name);
 
-    if (sender_ident != NULL)
-      sender_rcvhost = string_append(sender_rcvhost, &size, &ptr, 2,
-        (firstptr == ptr)? US"ident=" : US" ident=", sender_ident);
+    if (sender_ident)
+      g = string_append(g, 2,
+        firstptr == g->ptr ? US"ident=" : US" ident=", sender_ident);
 
-    sender_rcvhost = string_catn(sender_rcvhost, &size, &ptr, US")", 1);
+    g = string_catn(g, US")", 1);
     }
 
-  sender_rcvhost[ptr] = 0;   /* string_cat() always leaves room */
-
-  /* Release store, because string_cat allocated a minimum of 100 bytes that
-  are rarely completely used. */
-
-  store_reset(sender_rcvhost + ptr + 1);
+  rcvhost = string_from_gstring(g);
   }
 
 /* Host name is known and verified. Unless we've already found that the HELO
@@ -638,25 +559,28 @@ else
 
   if (show_helo)
     {
-    sender_fullhost = string_sprintf("%s (%s) %s", sender_host_name,
+    fullhost = string_sprintf("%s (%s) %s", sender_host_name,
       sender_helo_name, address);
-    sender_rcvhost = (sender_ident == NULL)?
-      string_sprintf("%s (%s helo=%s)", sender_host_name,
-        address, sender_helo_name) :
-      string_sprintf("%s\n\t(%s helo=%s ident=%s)", sender_host_name,
-        address, sender_helo_name, sender_ident);
+    rcvhost = sender_ident
+      ?  string_sprintf("%s\n\t(%s helo=%s ident=%s)", sender_host_name,
+        address, sender_helo_name, sender_ident)
+      : string_sprintf("%s (%s helo=%s)", sender_host_name,
+        address, sender_helo_name);
     }
   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);
+    fullhost = string_sprintf("%s %s", sender_host_name, address);
+    rcvhost = sender_ident
+      ?  string_sprintf("%s (%s ident=%s)", sender_host_name, address,
+        sender_ident)
+      : string_sprintf("%s (%s)", sender_host_name, address);
     }
   }
 
-store_pool = old_pool;
+sender_fullhost = string_copy_perm(fullhost, TRUE);
+sender_rcvhost = string_copy_perm(rcvhost, TRUE);
+
+store_reset(reset_point);
 
 DEBUG(D_host_lookup) debug_printf("sender_fullhost = %s\n", sender_fullhost);
 DEBUG(D_host_lookup) debug_printf("sender_rcvhost = %s\n", sender_rcvhost);
@@ -676,6 +600,8 @@ return depends on whether sender_fullhost and sender_ident are set or not:
   ident set, no host  => U=ident
   ident set, host set => H=sender_fullhost U=ident
 
+Use taint-unchecked routines on the assumption we'll never expand the results.
+
 Arguments:
   useflag   TRUE if first item to be flagged (H= or U=); if there are two
               items, the second is always flagged
@@ -686,23 +612,21 @@ Returns:    pointer to a string in big_buffer
 uschar *
 host_and_ident(BOOL useflag)
 {
-if (sender_fullhost == NULL)
-  {
-  (void)string_format(big_buffer, big_buffer_size, "%s%s", useflag? "U=" : "",
-     (sender_ident == NULL)? US"unknown" : sender_ident);
-  }
+if (!sender_fullhost)
+  string_format_nt(big_buffer, big_buffer_size, "%s%s", useflag ? "U=" : "",
+     sender_ident ? sender_ident : US"unknown");
 else
   {
-  uschar *flag = useflag? US"H=" : US"";
-  uschar *iface = US"";
-  if (LOGGING(incoming_interface) && interface_address != NULL)
+  uschar * flag = useflag ? US"H=" : US"";
+  uschar * iface = US"";
+  if (LOGGING(incoming_interface) && interface_address)
     iface = string_sprintf(" I=[%s]:%d", interface_address, interface_port);
-  if (sender_ident == NULL)
-    (void)string_format(big_buffer, big_buffer_size, "%s%s%s",
-      flag, sender_fullhost, iface);
-  else
-    (void)string_format(big_buffer, big_buffer_size, "%s%s%s U=%s",
+  if (sender_ident)
+    string_format_nt(big_buffer, big_buffer_size, "%s%s%s U=%s",
       flag, sender_fullhost, iface, sender_ident);
+  else
+    string_format_nt(big_buffer, big_buffer_size, "%s%s%s",
+      flag, sender_fullhost, iface);
   }
 return big_buffer;
 }
@@ -737,16 +661,14 @@ host_build_ifacelist(const uschar *list, uschar *name)
 {
 int sep = 0;
 uschar *s;
-uschar buffer[64];
-ip_address_item *yield = NULL;
-ip_address_item *last = NULL;
-ip_address_item *next;
+ip_address_item * yield = NULL, * last = NULL, * next;
 
-while ((s = string_nextinlist(&list, &sep, buffer, sizeof(buffer))) != NULL)
+while ((s = string_nextinlist(&list, &sep, NULL, 0)))
   {
   int ipv;
   int port = host_address_extract_port(s);            /* Leaves just the IP address */
-  if ((ipv = string_is_ip_address(s, NULL)) == 0)
+
+  if (!(ipv = string_is_ip_address(s, NULL)))
     log_write(0, LOG_MAIN|LOG_PANIC_DIE, "Malformed IP address \"%s\" in %s",
       s, name);
 
@@ -758,13 +680,16 @@ while ((s = string_nextinlist(&list, &sep, buffer, sizeof(buffer))) != NULL)
   address above. The field in the ip_address_item is large enough to hold an
   IPv6 address. */
 
-  next = store_get(sizeof(ip_address_item));
+  next = store_get(sizeof(ip_address_item), list);
   next->next = NULL;
   Ustrcpy(next->address, s);
   next->port = port;
   next->v6_include_v4 = FALSE;
+  next->log = NULL;
 
-  if (yield == NULL) yield = last = next; else
+  if (!yield)
+    yield = last = next;
+  else
     {
     last->next = next;
     last = next;
@@ -807,9 +732,9 @@ static ip_address_item *
 add_unique_interface(ip_address_item *list, ip_address_item *ipa)
 {
 ip_address_item *ipa2;
-for (ipa2 = list; ipa2 != NULL; ipa2 = ipa2->next)
+for (ipa2 = list; ipa2; ipa2 = ipa2->next)
   if (Ustrcmp(ipa2->address, ipa->address) == 0) return list;
-ipa2 = store_get_perm(sizeof(ip_address_item));
+ipa2 = store_get_perm(sizeof(ip_address_item), FALSE);
 *ipa2 = *ipa;
 ipa2->next = list;
 return ipa2;
@@ -823,36 +748,34 @@ host_find_interfaces(void)
 {
 ip_address_item *running_interfaces = NULL;
 
-if (local_interface_data == NULL)
+if (!local_interface_data)
   {
-  void *reset_item = store_get(0);
+  void *reset_item = store_mark();
   ip_address_item *dlist = host_build_ifacelist(CUS local_interfaces,
     US"local_interfaces");
   ip_address_item *xlist = host_build_ifacelist(CUS extra_local_interfaces,
     US"extra_local_interfaces");
   ip_address_item *ipa;
 
-  if (dlist == NULL) dlist = xlist; else
+  if (!dlist) dlist = xlist;
+  else
     {
-    for (ipa = dlist; ipa->next != NULL; ipa = ipa->next);
+    for (ipa = dlist; ipa->next; ipa = ipa->next) ;
     ipa->next = xlist;
     }
 
-  for (ipa = dlist; ipa != NULL; ipa = ipa->next)
+  for (ipa = dlist; ipa; ipa = ipa->next)
     {
     if (Ustrcmp(ipa->address, "0.0.0.0") == 0 ||
         Ustrcmp(ipa->address, "::0") == 0)
       {
-      ip_address_item *ipa2;
       BOOL ipv6 = ipa->address[0] == ':';
-      if (running_interfaces == NULL)
+      if (!running_interfaces)
         running_interfaces = os_find_running_interfaces();
-      for (ipa2 = running_interfaces; ipa2 != NULL; ipa2 = ipa2->next)
-        {
+      for (ip_address_item * ipa2 = running_interfaces; ipa2; ipa2 = ipa2->next)
         if ((Ustrchr(ipa2->address, ':') != NULL) == ipv6)
           local_interface_data = add_unique_interface(local_interface_data,
-          ipa2);
-        }
+                                                     ipa2);
       }
     else
       {
@@ -919,21 +842,21 @@ if (type < 0)
   if (family == AF_INET6)
     {
     struct sockaddr_in6 *sk = (struct sockaddr_in6 *)arg;
-    yield = (uschar *)inet_ntop(family, &(sk->sin6_addr), CS addr_buffer,
+    yield = US inet_ntop(family, &(sk->sin6_addr), CS addr_buffer,
       sizeof(addr_buffer));
-    if (portptr != NULL) *portptr = ntohs(sk->sin6_port);
+    if (portptr) *portptr = ntohs(sk->sin6_port);
     }
   else
     {
     struct sockaddr_in *sk = (struct sockaddr_in *)arg;
-    yield = (uschar *)inet_ntop(family, &(sk->sin_addr), CS addr_buffer,
+    yield = US inet_ntop(family, &(sk->sin_addr), CS addr_buffer,
       sizeof(addr_buffer));
-    if (portptr != NULL) *portptr = ntohs(sk->sin_port);
+    if (portptr) *portptr = ntohs(sk->sin_port);
     }
   }
 else
   {
-  yield = (uschar *)inet_ntop(type, arg, CS addr_buffer, sizeof(addr_buffer));
+  yield = US inet_ntop(type, arg, CS addr_buffer, sizeof(addr_buffer));
   }
 
 /* If the result is a mapped IPv4 address, show it in V4 format. */
@@ -947,7 +870,7 @@ if (Ustrncmp(yield, "::ffff:", 7) == 0) yield += 7;
 if (type < 0)
   {
   yield = US inet_ntoa(((struct sockaddr_in *)arg)->sin_addr);
-  if (portptr != NULL) *portptr = ntohs(((struct sockaddr_in *)arg)->sin_port);
+  if (portptr) *portptr = ntohs(((struct sockaddr_in *)arg)->sin_port);
   }
 else
   yield = US inet_ntoa(*((struct in_addr *)arg));
@@ -955,13 +878,15 @@ else
 
 /* If there is no buffer, put the string into some new store. */
 
-if (buffer == NULL) return string_copy(yield);
+if (!buffer) buffer = store_get(46, GET_UNTAINTED);
 
 /* Callers of this function with a non-NULL buffer must ensure that it is
 large enough to hold an IPv6 address, namely, at least 46 bytes. That's what
-makes this use of strcpy() OK. */
+makes this use of strcpy() OK.
+If the library returned apparently an apparently tainted string, clean it;
+we trust IP addresses. */
 
-Ustrcpy(buffer, yield);
+string_format_nt(buffer, 46, "%s", yield);
 return buffer;
 }
 
@@ -1090,9 +1015,8 @@ Returns:       nothing
 void
 host_mask(int count, int *binary, int mask)
 {
-int i;
 if (mask < 0) mask = 99999;
-for (i = 0; i < count; i++)
+for (int i = 0; i < count; i++)
   {
   int wordmask;
   if (mask == 0) wordmask = 0;
@@ -1139,17 +1063,17 @@ Returns:      the number of characters placed in buffer, not counting
 int
 host_nmtoa(int count, int *binary, int mask, uschar *buffer, int sep)
 {
-int i, j;
+int j;
 uschar *tt = buffer;
 
 if (count == 1)
   {
   j = binary[0];
-  for (i = 24; i >= 0; i -= 8)
+  for (int i = 24; i >= 0; i -= 8)
     tt += sprintf(CS tt, "%d.", (j >> i) & 255);
   }
 else
-  for (i = 0; i < 4; i++)
+  for (int i = 0; i < 4; i++)
     {
     j = binary[i];
     tt += sprintf(CS tt, "%04x%c%04x%c", (j >> 16) & 0xffff, sep, j & 0xffff, sep);
@@ -1160,10 +1084,7 @@ tt--;   /* lose final separator */
 if (mask < 0)
   *tt = 0;
 else
-  {
-  sprintf(CS tt, "/%d", mask);
-  while (*tt) tt++;
-  }
+  tt += sprintf(CS tt, "/%d", mask);
 
 return tt - buffer;
 }
@@ -1209,9 +1130,9 @@ for (c = buffer, k = -1, i = 0; i < 8; i++)
   c++;
   }
 
-c[-1] = '\0';  /* drop trailing colon */
+*--c = '\0';   /* drop trailing colon */
 
-/* debug_printf("%s: D k %d <%s> <%s>\n", __FUNCTION__, k, d, d + 2*(k+1)); */
+/* debug_printf("%s: D k %d <%s> <%s>\n", __FUNCTION__, k, buffer, buffer + 2*(k+1)); */
 if (k >= 0)
   {                    /* collapse */
   c = d + 2*(k+1);
@@ -1244,14 +1165,11 @@ BOOL
 host_is_tls_on_connect_port(int port)
 {
 int sep = 0;
-uschar buffer[32];
-const uschar *list = tls_in.on_connect_ports;
-uschar *s;
-uschar *end;
+const uschar * list = tls_in.on_connect_ports;
 
 if (tls_in.on_connect) return TRUE;
 
-while ((s = string_nextinlist(&list, &sep, buffer, sizeof(buffer))))
+for (uschar * s, * end; s = string_nextinlist(&list, &sep, NULL, 0); )
   if (Ustrtol(s, &end, 10) == port)
     return TRUE;
 
@@ -1282,7 +1200,6 @@ Returns:
 BOOL
 host_is_in_net(const uschar *host, const uschar *net, int maskoffset)
 {
-int i;
 int address[4];
 int incoming[4];
 int mlen;
@@ -1315,7 +1232,7 @@ if (insize != size) return FALSE;
 
 /* Else do the masked comparison. */
 
-for (i = 0; i < size; i++)
+for (int i = 0; i < size; i++)
   {
   int mask;
   if (mlen == 0) mask = 0;
@@ -1410,9 +1327,8 @@ for (h = host; h != last->next; h = h->next)
 
   if (h->address != NULL)
     {
-    ip_address_item *ip;
     if (Ustrcmp(h->address, "0.0.0.0") == 0) goto FOUND_LOCAL;
-    for (ip = local_interface_data; ip != NULL; ip = ip->next)
+    for (ip_address_item * ip = local_interface_data; ip; ip = ip->next)
       if (Ustrcmp(h->address, ip->address) == 0) goto FOUND_LOCAL;
     yield = HOST_FOUND;  /* At least one remote address has been found */
     }
@@ -1517,9 +1433,7 @@ Returns:     OK, DEFER, FAIL
 static int
 host_name_lookup_byaddr(void)
 {
-int len;
-uschar *s, *t;
-struct hostent *hosts;
+struct hostent * hosts;
 struct in_addr addr;
 unsigned long time_msec = 0;   /* init to quieten dumb static analysis */
 
@@ -1562,11 +1476,11 @@ hosts = gethostbyaddr(CS(&addr), sizeof(addr), AF_INET);
 if (  slow_lookup_log
    && (time_msec = get_time_in_ms() - time_msec) > slow_lookup_log
    )
-  log_long_lookup(US"name", sender_host_address, time_msec);
+  log_long_lookup(US"gethostbyaddr", sender_host_address, time_msec);
 
 /* Failed to look up the host. */
 
-if (hosts == NULL)
+if (!hosts)
   {
   HDEBUG(D_host_lookup) debug_printf("IP address lookup failed: h_errno=%d\n",
     h_errno);
@@ -1577,7 +1491,7 @@ if (hosts == NULL)
 treat this as non-existent. In some operating systems, this is returned as an
 empty string; in others as a single dot. */
 
-if (hosts->h_name == NULL || hosts->h_name[0] == 0 || hosts->h_name[0] == '.')
+if (!hosts->h_name || !hosts->h_name[0] || hosts->h_name[0] == '.')
   {
   HDEBUG(D_host_lookup) debug_printf("IP address lookup yielded an empty name: "
     "treated as non-existent host name\n");
@@ -1587,29 +1501,29 @@ if (hosts->h_name == NULL || hosts->h_name[0] == 0 || hosts->h_name[0] == '.')
 /* Copy and lowercase the name, which is in static storage in many systems.
 Put it in permanent memory. */
 
-s = (uschar *)hosts->h_name;
-len = Ustrlen(s) + 1;
-t = sender_host_name = store_get_perm(len);
-while (*s != 0) *t++ = tolower(*s++);
-*t = 0;
+  {
+  int old_pool = store_pool;
+  store_pool = POOL_TAINT_PERM;                /* names are tainted */
 
-/* If the host has aliases, build a copy of the alias list */
+  sender_host_name = string_copylc(US hosts->h_name);
 
-if (hosts->h_aliases != NULL)
-  {
-  int count = 1;
-  uschar **aliases, **ptr;
-  for (aliases = USS hosts->h_aliases; *aliases != NULL; aliases++) count++;
-  ptr = sender_host_aliases = store_get_perm(count * sizeof(uschar *));
-  for (aliases = USS hosts->h_aliases; *aliases != NULL; aliases++)
+  /* If the host has aliases, build a copy of the alias list */
+
+  if (hosts->h_aliases)
     {
-    uschar *s = *aliases;
-    int len = Ustrlen(s) + 1;
-    uschar *t = *ptr++ = store_get_perm(len);
-    while (*s != 0) *t++ = tolower(*s++);
-    *t = 0;
+    int count = 1;  /* need 1 more for terminating NULL */
+    uschar **ptr;
+
+    for (uschar ** aliases = USS hosts->h_aliases; *aliases; aliases++) count++;
+    store_pool = POOL_PERM;
+    ptr = sender_host_aliases = store_get(count * sizeof(uschar *), GET_UNTAINTED);
+    store_pool = POOL_TAINT_PERM;
+
+    for (uschar ** aliases = USS hosts->h_aliases; *aliases; aliases++)
+      *ptr++ = string_copylc(*aliases);
+    *ptr = NULL;
     }
-  *ptr = NULL;
+  store_pool = old_pool;
   }
 
 return OK;
@@ -1646,7 +1560,7 @@ Returns:      OK on success, the answer being placed in the global variable
               FAIL if no host name can be found
               DEFER if a temporary error was encountered
 
-The variable host_lookup_msg is set to an empty string on sucess, or to a
+The variable host_lookup_msg is set to an empty string on success, 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. If there
 was a defer, host_lookup_deferred is set TRUE.
@@ -1660,13 +1574,11 @@ host_name_lookup(void)
 {
 int old_pool, rc;
 int sep = 0;
-uschar *hname, *save_hostname;
+uschar *save_hostname;
 uschar **aliases;
-uschar buffer[256];
 uschar *ordername;
 const uschar *list = host_lookup_order;
-dns_record *rr;
-dns_answer dnsa;
+dns_answer * dnsa = store_get_dns_answer();
 dns_scan dnss;
 
 sender_host_dnssec = host_lookup_deferred = host_lookup_failed = FALSE;
@@ -1677,7 +1589,7 @@ HDEBUG(D_host_lookup)
 /* For testing the case when a lookup does not complete, we have a special
 reserved IP address. */
 
-if (running_in_test_harness &&
+if (f.running_in_test_harness &&
     Ustrcmp(sender_host_address, "99.99.99.99") == 0)
   {
   HDEBUG(D_host_lookup)
@@ -1689,13 +1601,14 @@ if (running_in_test_harness &&
 /* Do lookups directly in the DNS or via gethostbyaddr() (or equivalent), in
 the order specified by the host_lookup_order option. */
 
-while ((ordername = string_nextinlist(&list, &sep, buffer, sizeof(buffer))))
+while ((ordername = string_nextinlist(&list, &sep, NULL, 0)))
   {
   if (strcmpic(ordername, US"bydns") == 0)
     {
+    uschar * name = dns_build_reverse(sender_host_address);
+
     dns_init(FALSE, FALSE, FALSE);    /* dnssec ctrl by dns_dnssec_ok glbl */
-    dns_build_reverse(sender_host_address, buffer);
-    rc = dns_lookup_timerwrap(&dnsa, buffer, T_PTR, NULL);
+    rc = dns_lookup_timerwrap(dnsa, name, T_PTR, NULL);
 
     /* The first record we come across is used for the name; others are
     considered to be aliases. We have to scan twice, in order to find out the
@@ -1707,66 +1620,70 @@ while ((ordername = string_nextinlist(&list, &sep, buffer, sizeof(buffer))))
       {
       uschar **aptr = NULL;
       int ssize = 264;
-      int count = 0;
+      int count = 1;  /* need 1 more for terminating NULL */
       int old_pool = store_pool;
 
-      sender_host_dnssec = dns_is_secure(&dnsa);
+      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);
+      for (dns_record * rr = dns_next_rr(dnsa, &dnss, RESET_ANSWERS);
            rr;
-           rr = dns_next_rr(&dnsa, &dnss, RESET_NEXT))
-        if (rr->type == T_PTR)
-         count++;
+           rr = dns_next_rr(dnsa, &dnss, RESET_NEXT)) if (rr->type == T_PTR)
+       count++;
 
       /* Get store for the list of aliases. For compatibility with
       gethostbyaddr, we make an empty list if there are none. */
 
-      aptr = sender_host_aliases = store_get(count * sizeof(uschar *));
+      aptr = sender_host_aliases = store_get(count * sizeof(uschar *), GET_UNTAINTED);
 
       /* Re-scan and extract the names */
 
-      for (rr = dns_next_rr(&dnsa, &dnss, RESET_ANSWERS);
+      for (dns_record * rr = dns_next_rr(dnsa, &dnss, RESET_ANSWERS);
            rr;
-           rr = dns_next_rr(&dnsa, &dnss, RESET_NEXT))
+           rr = dns_next_rr(dnsa, &dnss, RESET_NEXT)) if (rr->type == T_PTR)
         {
-        uschar *s = NULL;
-        if (rr->type != T_PTR) continue;
-        s = store_get(ssize);
+        uschar * s = store_get(ssize, GET_TAINTED);    /* names are tainted */
+       unsigned slen;
 
         /* If an overlong response was received, the data will have been
         truncated and dn_expand may fail. */
 
-        if (dn_expand(dnsa.answer, dnsa.answer + dnsa.answerlen,
-             (uschar *)(rr->data), (DN_EXPAND_ARG4_TYPE)(s), ssize) < 0)
+        if (dn_expand(dnsa->answer, dnsa->answer + dnsa->answerlen,
+             US (rr->data), (DN_EXPAND_ARG4_TYPE)(s), ssize) < 0)
           {
           log_write(0, LOG_MAIN, "host name alias list truncated for %s",
             sender_host_address);
           break;
           }
 
-        store_reset(s + Ustrlen(s) + 1);
-        if (s[0] == 0)
+        store_release_above(s + (slen = Ustrlen(s)) + 1);
+        if (!*s)
           {
           HDEBUG(D_host_lookup) debug_printf("IP address lookup yielded an "
             "empty name: treated as non-existent host name\n");
           continue;
           }
+       if (Ustrspn(s, letter_digit_hyphen_dot) != slen)
+          {
+          HDEBUG(D_host_lookup) debug_printf("IP address lookup yielded an "
+            "illegal name (bad char): treated as non-existent host name\n");
+          continue;
+          }
         if (!sender_host_name) sender_host_name = s;
        else *aptr++ = s;
-        while (*s != 0) { *s = tolower(*s); s++; }
+        while (*s) { *s = tolower(*s); s++; }
         }
 
       *aptr = NULL;            /* End of alias list */
       store_pool = old_pool;   /* Reset store pool */
 
-      /* If we've found a names, break out of the "order" loop */
+      /* If we've found a name, break out of the "order" loop */
 
-      if (sender_host_name != NULL) break;
+      if (sender_host_name) break;
       }
 
     /* If the DNS lookup deferred, we must also defer. */
@@ -1799,9 +1716,9 @@ while ((ordername = string_nextinlist(&list, &sep, buffer, sizeof(buffer))))
 /* If we have failed to find a name, return FAIL and log when required.
 NB host_lookup_msg must be in permanent store.  */
 
-if (sender_host_name == NULL)
+if (!sender_host_name)
   {
-  if (host_checking || !log_testing_mode)
+  if (host_checking || !f.log_testing_mode)
     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)";
@@ -1813,7 +1730,7 @@ HDEBUG(D_host_lookup)
   {
   uschar **aliases = sender_host_aliases;
   debug_printf("IP address lookup yielded \"%s\"\n", sender_host_name);
-  while (*aliases != NULL) debug_printf("  alias \"%s\"\n", *aliases++);
+  while (*aliases) debug_printf("  alias \"%s\"\n", *aliases++);
   }
 
 /* We need to verify that a forward lookup on the name we found does indeed
@@ -1831,26 +1748,19 @@ the names, and accepts only those that have the correct IP address. */
 
 save_hostname = sender_host_name;   /* Save for error messages */
 aliases = sender_host_aliases;
-for (hname = sender_host_name; hname != NULL; hname = *aliases++)
+for (uschar * hname = sender_host_name; hname; hname = *aliases++)
   {
   int rc;
   BOOL ok = FALSE;
-  host_item h;
-  dnssec_domains d;
+  host_item h = { .next = NULL, .name = hname, .mx = MX_NONE, .address = NULL };
+  dnssec_domains d =
+    { .request = sender_host_dnssec ? US"*" : NULL, .require = NULL };
 
-  h.next = NULL;
-  h.name = hname;
-  h.mx = MX_NONE;
-  h.address = NULL;
-  d.request = sender_host_dnssec ? US"*" : NULL;;
-  d.require = NULL;
-
-  if (  (rc = host_find_bydns(&h, NULL, HOST_FIND_BY_A,
+  if (  (rc = host_find_bydns(&h, NULL, HOST_FIND_BY_A | HOST_FIND_BY_AAAA,
          NULL, NULL, NULL, &d, NULL, NULL)) == HOST_FOUND
      || rc == HOST_FOUND_LOCAL
      )
     {
-    host_item *hh;
     HDEBUG(D_host_lookup) debug_printf("checking addresses for %s\n", hname);
 
     /* If the forward lookup was not secure we cancel the is-secure variable */
@@ -1859,7 +1769,7 @@ for (hname = sender_host_name; hname != NULL; hname = *aliases++)
          h.dnssec == DS_YES ? "DNSSEC verified (AD)" : "unverified");
     if (h.dnssec != DS_YES) sender_host_dnssec = FALSE;
 
-    for (hh = &h; hh != NULL; hh = hh->next)
+    for (host_item * hh = &h; hh; hh = hh->next)
       if (host_is_in_net(hh->address, sender_host_address, 0))
         {
         HDEBUG(D_host_lookup) debug_printf("  %s OK\n", hh->address);
@@ -1970,12 +1880,16 @@ int
 host_find_byname(host_item *host, const uschar *ignore_target_hosts, int flags,
   const uschar **fully_qualified_name, BOOL local_host_check)
 {
-int i, yield, times;
-uschar **addrlist;
+int yield, times;
 host_item *last = NULL;
 BOOL temp_error = FALSE;
-#if HAVE_IPV6
 int af;
+
+#ifndef DISABLE_TLS
+/* Copy the host name at this point to the value which is used for
+TLS certificate name checking, before anything modifies it.  */
+
+host->certname = host->name;
 #endif
 
 /* Make sure DNS options are set as required. This appears to be necessary in
@@ -1995,10 +1909,10 @@ lookups here (except when testing standalone). */
   #ifdef STAND_ALONE
   if (disable_ipv6)
   #else
-  if (disable_ipv6 ||
-    (dns_ipv4_lookup != NULL &&
-        match_isinlist(host->name, CUSS &dns_ipv4_lookup, 0, NULL, NULL,
-         MCL_DOMAIN, TRUE, NULL) == OK))
+  if (  disable_ipv6
+     ||    dns_ipv4_lookup
+       && match_isinlist(host->name, CUSS &dns_ipv4_lookup, 0,
+           &domainlist_anchor, NULL, MCL_DOMAIN, TRUE, NULL) == OK)
   #endif
 
     { af = AF_INET; times = 1; }
@@ -2008,17 +1922,17 @@ lookups here (except when testing standalone). */
 /* No IPv6 support */
 
 #else   /* HAVE_IPV6 */
-  times = 1;
+  af = AF_INET; times = 1;
 #endif  /* HAVE_IPV6 */
 
 /* Initialize the flag that gets set for DNS syntax check errors, so that the
 interface to this function can be similar to host_find_bydns. */
 
-host_find_failed_syntax = FALSE;
+f.host_find_failed_syntax = FALSE;
 
 /* Loop to look up both kinds of address in an IPv6 world */
 
-for (i = 1; i <= times;
+for (int i = 1; i <= times;
      #if HAVE_IPV6
        af = AF_INET,     /* If 2 passes, IPv4 on the second */
      #endif
@@ -2036,7 +1950,7 @@ for (i = 1; i <= times;
   if (slow_lookup_log) time_msec = get_time_in_ms();
 
   #if HAVE_IPV6
-  if (running_in_test_harness)
+  if (f.running_in_test_harness)
     hostdata = host_fake_gethostbyname(host->name, af, &error_num);
   else
     {
@@ -2049,8 +1963,8 @@ for (i = 1; i <= times;
     }
 
   #else    /* not HAVE_IPV6 */
-  if (running_in_test_harness)
-    hostdata = host_fake_gethostbyname(host->name, AF_INET, &error_num);
+  if (f.running_in_test_harness)
+    hostdata = host_fake_gethostbyname(host->name, af, &error_num);
   else
     {
     hostdata = gethostbyname(CS host->name);
@@ -2060,47 +1974,46 @@ for (i = 1; i <= times;
 
   if (   slow_lookup_log
       && (time_msec = get_time_in_ms() - time_msec) > slow_lookup_log)
-    log_long_lookup(US"name", host->name, time_msec);
+    log_long_lookup(US"gethostbyname", host->name, time_msec);
 
-  if (hostdata == NULL)
+  if (!hostdata)
     {
-    uschar *error;
+    uschar * error;
     switch (error_num)
       {
-      case HOST_NOT_FOUND: error = US"HOST_NOT_FOUND"; break;
-      case TRY_AGAIN:      error = US"TRY_AGAIN"; break;
-      case NO_RECOVERY:    error = US"NO_RECOVERY"; break;
-      case NO_DATA:        error = US"NO_DATA"; break;
-      #if NO_DATA != NO_ADDRESS
-      case NO_ADDRESS:     error = US"NO_ADDRESS"; break;
-      #endif
+      case HOST_NOT_FOUND: error = US"HOST_NOT_FOUND"; break;
+      case TRY_AGAIN:      error = US"TRY_AGAIN";   temp_error = TRUE; break;
+      case NO_RECOVERY:    error = US"NO_RECOVERY"; temp_error = TRUE; break;
+      case NO_DATA:        error = US"NO_DATA";                break;
+    #if NO_DATA != NO_ADDRESS
+      case NO_ADDRESS:     error = US"NO_ADDRESS";     break;
+    #endif
       default: error = US"?"; break;
       }
 
-    DEBUG(D_host_lookup) debug_printf("%s returned %d (%s)\n",
-      #if HAVE_IPV6
-        #if HAVE_GETIPNODEBYNAME
-        (af == AF_INET6)? "getipnodebyname(af=inet6)" : "getipnodebyname(af=inet)",
-        #else
-        (af == AF_INET6)? "gethostbyname2(af=inet6)" : "gethostbyname2(af=inet)",
-        #endif
-      #else
-      "gethostbyname",
-      #endif
-      error_num, error);
+    DEBUG(D_host_lookup) debug_printf("%s(af=%s) returned %d (%s)\n",
+      f.running_in_test_harness ? "host_fake_gethostbyname" :
+#if HAVE_IPV6
+# if HAVE_GETIPNODEBYNAME
+        "getipnodebyname",
+# else
+        "gethostbyname2",
+# endif
+#else
+       "gethostbyname",
+#endif
+      af == AF_INET ? "inet" : "inet6", error_num, error);
 
-    if (error_num == TRY_AGAIN || error_num == NO_RECOVERY) temp_error = TRUE;
     continue;
     }
-  if ((hostdata->h_addr_list)[0] == NULL) continue;
+  if (!(hostdata->h_addr_list)[0]) continue;
 
   /* Replace the name with the fully qualified one if necessary, and fill in
   the fully_qualified_name pointer. */
 
-  if (hostdata->h_name[0] != 0 &&
-      Ustrcmp(host->name, hostdata->h_name) != 0)
-    host->name = string_copy_dnsdomain((uschar *)hostdata->h_name);
-  if (fully_qualified_name != NULL) *fully_qualified_name = host->name;
+  if (hostdata->h_name[0] && Ustrcmp(host->name, hostdata->h_name) != 0)
+    host->name = string_copy_dnsdomain(US hostdata->h_name);
+  if (fully_qualified_name) *fully_qualified_name = host->name;
 
   /* Get the list of addresses. IPv4 and IPv6 addresses can be distinguished
   by their different lengths. Scan the list, ignoring any that are to be
@@ -2108,15 +2021,15 @@ for (i = 1; i <= times;
 
   ipv4_addr = hostdata->h_length == sizeof(struct in_addr);
 
-  for (addrlist = USS hostdata->h_addr_list; *addrlist != NULL; addrlist++)
+  for (uschar ** addrlist = USS hostdata->h_addr_list; *addrlist; addrlist++)
     {
     uschar *text_address =
       host_ntoa(ipv4_addr? AF_INET:AF_INET6, *addrlist, NULL, NULL);
 
     #ifndef STAND_ALONE
-    if (ignore_target_hosts != NULL &&
-        verify_check_this_host(&ignore_target_hosts, NULL, host->name,
-          text_address, NULL) == OK)
+    if (  ignore_target_hosts
+       && verify_check_this_host(&ignore_target_hosts, NULL, host->name,
+           text_address, NULL) == OK)
       {
       DEBUG(D_host_lookup)
         debug_printf("ignored host %s [%s]\n", host->name, text_address);
@@ -2124,10 +2037,10 @@ for (i = 1; i <= times;
       }
     #endif
 
-    /* If this is the first address, last == NULL and we put the data in the
+    /* If this is the first address, last is NULL and we put the data in the
     original block. */
 
-    if (last == NULL)
+    if (!last)
       {
       host->address = text_address;
       host->port = PORT_NONE;
@@ -2142,8 +2055,11 @@ for (i = 1; i <= times;
 
     else
       {
-      host_item *next = store_get(sizeof(host_item));
+      host_item *next = store_get(sizeof(host_item), GET_UNTAINTED);
       next->name = host->name;
+#ifndef DISABLE_TLS
+      next->certname = host->certname;
+#endif
       next->mx = host->mx;
       next->address = text_address;
       next->port = PORT_NONE;
@@ -2162,19 +2078,19 @@ for (i = 1; i <= times;
 NULL. If temp_error is set, at least one of the lookups gave a temporary error,
 so we pass that back. */
 
-if (host->address == NULL)
+if (!host->address)
   {
   uschar *msg =
     #ifndef STAND_ALONE
-    (message_id[0] == 0 && smtp_in != NULL)?
-      string_sprintf("no IP address found for host %s (during %s)", host->name,
+    !message_id[0] && smtp_in
+      string_sprintf("no IP address found for host %s (during %s)", host->name,
           smtp_get_connection_info()) :
     #endif
     string_sprintf("no IP address found for host %s", host->name);
 
   HDEBUG(D_host_lookup) debug_printf("%s\n", msg);
   if (temp_error) goto RETURN_AGAIN;
-  if (host_checking || !log_testing_mode)
+  if (host_checking || !f.log_testing_mode)
     log_write(L_host_lookup_failed, LOG_MAIN, "%s", msg);
   return HOST_FIND_FAILED;
   }
@@ -2188,8 +2104,7 @@ yield = local_host_check?
 
 HDEBUG(D_host_lookup)
   {
-  const host_item *h;
-  if (fully_qualified_name != NULL)
+  if (fully_qualified_name)
     debug_printf("fully qualified name = %s\n", *fully_qualified_name);
   debug_printf("%s looked up these IP addresses:\n",
     #if HAVE_IPV6
@@ -2202,9 +2117,9 @@ HDEBUG(D_host_lookup)
     "gethostbyname"
     #endif
     );
-  for (h = host; h != last->next; h = h->next)
+  for (const host_item * h = host; h != last->next; h = h->next)
     debug_printf("  name=%s address=%s\n", h->name,
-      (h->address == NULL)? US"<null>" : h->address);
+      h->address ? h->address : US"<null>");
   }
 
 /* Return the found status. */
@@ -2216,12 +2131,12 @@ dns_again_means_nonexist, return permanent rather than temporary failure. */
 
 RETURN_AGAIN:
   {
-  #ifndef STAND_ALONE
+#ifndef STAND_ALONE
   int rc;
   const uschar *save = deliver_domain;
   deliver_domain = host->name;  /* set $domain */
-  rc = match_isinlist(host->name, CUSS &dns_again_means_nonexist, 0, NULL, NULL,
-    MCL_DOMAIN, TRUE, NULL);
+  rc = match_isinlist(host->name, CUSS &dns_again_means_nonexist, 0,
+    &domainlist_anchor, NULL, MCL_DOMAIN, TRUE, NULL);
   deliver_domain = save;
   if (rc == OK)
     {
@@ -2229,7 +2144,7 @@ RETURN_AGAIN:
       "returning HOST_FIND_FAILED\n", host->name);
     return HOST_FIND_FAILED;
     }
-  #endif
+#endif
   return HOST_FIND_AGAIN;
   }
 }
@@ -2245,9 +2160,7 @@ field set to NULL, fill in its IP address from the DNS. If it is multi-homed,
 create additional host items for the additional addresses, copying all the
 other fields, and randomizing the order.
 
-On IPv6 systems, A6 records are sought first (but only if support for A6 is
-configured - they may never become mainstream), then AAAA records are sought,
-and finally A records are sought as well.
+On IPv6 systems, AAAA records are sought first, then A records.
 
 The host name may be changed if the DNS returns a different name - e.g. fully
 qualified or changed via CNAME. If fully_qualified_name is not NULL, dns_lookup
@@ -2270,9 +2183,11 @@ Arguments:
                           to something)
   dnssec_request       if TRUE request the AD bit
   dnssec_require       if TRUE require the AD bit
+  whichrrs             select ipv4, ipv6 results
 
 Returns:       HOST_FIND_FAILED     couldn't find A record
                HOST_FIND_AGAIN      try again later
+              HOST_FIND_SECURITY   dnssec required but not acheived
                HOST_FOUND           found AAAA and/or A record(s)
                HOST_IGNORED         found, but all IPs ignored
 */
@@ -2281,12 +2196,20 @@ static int
 set_address_from_dns(host_item *host, host_item **lastptr,
   const uschar *ignore_target_hosts, BOOL allow_ip,
   const uschar **fully_qualified_name,
-  BOOL dnssec_request, BOOL dnssec_require)
+  BOOL dnssec_request, BOOL dnssec_require, int whichrrs)
 {
-dns_record *rr;
 host_item *thishostlast = NULL;    /* Indicates not yet filled in anything */
 BOOL v6_find_again = FALSE;
+BOOL dnssec_fail = FALSE;
 int i;
+dns_answer * dnsa;
+
+#ifndef DISABLE_TLS
+/* Copy the host name at this point to the value which is used for
+TLS certificate name checking, before any CNAME-following modifies it.  */
+
+host->certname = host->name;
+#endif
 
 /* If allow_ip is set, a name which is an IP address returns that value
 as its address. This is used for MX records when allow_mx_to_ip is set, for
@@ -2295,8 +2218,8 @@ those sites that feel they have to flaunt the RFC rules. */
 if (allow_ip && string_is_ip_address(host->name, NULL) != 0)
   {
   #ifndef STAND_ALONE
-  if (ignore_target_hosts != NULL &&
-        verify_check_this_host(&ignore_target_hosts, NULL, host->name,
+  if (  ignore_target_hosts
+     && verify_check_this_host(&ignore_target_hosts, NULL, host->name,
         host->name, NULL) == OK)
     return HOST_IGNORED;
   #endif
@@ -2305,17 +2228,21 @@ if (allow_ip && string_is_ip_address(host->name, NULL) != 0)
   return HOST_FOUND;
   }
 
+dnsa = store_get_dns_answer();
+
 /* On an IPv6 system, unless IPv6 is disabled, go round the loop up to twice,
-looking for AAAA records the first time. However, unless
-doing standalone testing, we force an IPv4 lookup if the domain matches
-dns_ipv4_lookup is set.  On an IPv4 system, go round the
-loop once only, looking only for A records. */
+looking for AAAA records the first time. However, unless doing standalone
+testing, we force an IPv4 lookup if the domain matches dns_ipv4_lookup global.
+On an IPv4 system, go round the loop once only, looking only for A records. */
 
 #if HAVE_IPV6
   #ifndef STAND_ALONE
-    if (disable_ipv6 || (dns_ipv4_lookup != NULL &&
-        match_isinlist(host->name, CUSS &dns_ipv4_lookup, 0, NULL, NULL,
-         MCL_DOMAIN, TRUE, NULL) == OK))
+    if (  disable_ipv6
+       || !(whichrrs & HOST_FIND_BY_AAAA)
+       ||    dns_ipv4_lookup
+          && match_isinlist(host->name, CUSS &dns_ipv4_lookup, 0,
+             &domainlist_anchor, NULL, MCL_DOMAIN, TRUE, NULL) == OK
+       )
       i = 0;    /* look up A records only */
     else
   #endif        /* STAND_ALONE */
@@ -2332,18 +2259,18 @@ for (; i >= 0; i--)
   {
   static int types[] = { T_A, T_AAAA };
   int type = types[i];
-  int randoffset = (i == 0)? 500 : 0;  /* Ensures v6 sorts before v4 */
-  dns_answer dnsa;
+  int randoffset = i == (whichrrs & HOST_FIND_IPV4_FIRST ? 1 : 0)
+    ? 500 : 0;  /* Ensures v6/4 sort order */
   dns_scan dnss;
 
-  int rc = dns_lookup_timerwrap(&dnsa, host->name, type, fully_qualified_name);
+  int rc = dns_lookup_timerwrap(dnsa, host->name, type, fully_qualified_name);
   lookup_dnssec_authenticated = !dnssec_request ? NULL
-    : dns_is_secure(&dnsa) ? US"yes" : US"no";
+    : dns_is_secure(dnsa) ? US"yes" : US"no";
 
   DEBUG(D_dns)
     if (  (dnssec_request || dnssec_require)
-       && !dns_is_secure(&dnsa)
-       && dns_is_aa(&dnsa)
+       && !dns_is_secure(dnsa)
+       && dns_is_aa(dnsa)
        )
       debug_printf("DNS lookup of %.256s (A/AAAA) requested AD, but got AA\n", host->name);
 
@@ -2356,10 +2283,13 @@ for (; i >= 0; i--)
     {
     if (i == 0)  /* Just tried for an A record, i.e. end of loop */
       {
-      if (host->address != NULL) return HOST_FOUND;  /* AAAA was found */
-      if (rc == DNS_AGAIN || rc == DNS_FAIL || v6_find_again)
-        return HOST_FIND_AGAIN;
-      return HOST_FIND_FAILED;    /* DNS_NOMATCH or DNS_NODATA */
+      if (host->address != NULL)
+        i = HOST_FOUND;  /* AAAA was found */
+      else if (rc == DNS_AGAIN || rc == DNS_FAIL || v6_find_again)
+        i = HOST_FIND_AGAIN;
+      else
+       i = HOST_FIND_FAILED;    /* DNS_NOMATCH or DNS_NODATA */
+      goto out;
       }
 
     /* Tried for an AAAA record: remember if this was a temporary
@@ -2371,7 +2301,7 @@ for (; i >= 0; i--)
 
   if (dnssec_request)
     {
-    if (dns_is_secure(&dnsa))
+    if (dns_is_secure(dnsa))
       {
       DEBUG(D_host_lookup) debug_printf("%s A DNSSEC\n", host->name);
       if (host->dnssec == DS_UNK) /* set in host_find_bydns() */
@@ -2381,8 +2311,8 @@ for (; i >= 0; i--)
       {
       if (dnssec_require)
        {
-       log_write(L_host_lookup_failed, LOG_MAIN,
-               "dnssec fail on %s for %.256s",
+       dnssec_fail = TRUE;
+       DEBUG(D_host_lookup) debug_printf("dnssec fail on %s for %.256s",
                i>0 ? "AAAA" : "A", host->name);
        continue;
        }
@@ -2402,112 +2332,117 @@ for (; i >= 0; i--)
 
   fully_qualified_name = NULL;
 
-  for (rr = dns_next_rr(&dnsa, &dnss, RESET_ANSWERS);
+  for (dns_record * rr = dns_next_rr(dnsa, &dnss, RESET_ANSWERS);
        rr;
-       rr = dns_next_rr(&dnsa, &dnss, RESET_NEXT))
+       rr = dns_next_rr(dnsa, &dnss, RESET_NEXT)) if (rr->type == type)
     {
-    if (rr->type == type)
-      {
-      dns_address *da = dns_address_from_rr(&dnsa, rr);
+    dns_address * da = dns_address_from_rr(dnsa, rr);
 
-      DEBUG(D_host_lookup)
-        if (!da) debug_printf("no addresses extracted from A6 RR for %s\n",
-            host->name);
+    DEBUG(D_host_lookup)
+      if (!da) debug_printf("no addresses extracted from A6 RR for %s\n",
+         host->name);
 
-      /* This loop runs only once for A and AAAA records, but may run
-      several times for an A6 record that generated multiple addresses. */
+    /* This loop runs only once for A and AAAA records, but may run
+    several times for an A6 record that generated multiple addresses. */
 
-      for (; da; da = da->next)
-        {
-        #ifndef STAND_ALONE
-        if (ignore_target_hosts != NULL &&
-              verify_check_this_host(&ignore_target_hosts, NULL,
-                host->name, da->address, NULL) == OK)
-          {
-          DEBUG(D_host_lookup)
-            debug_printf("ignored host %s [%s]\n", host->name, da->address);
-          continue;
-          }
-        #endif
+    for (; da; da = da->next)
+      {
+      #ifndef STAND_ALONE
+      if (ignore_target_hosts != NULL &&
+           verify_check_this_host(&ignore_target_hosts, NULL,
+             host->name, da->address, NULL) == OK)
+       {
+       DEBUG(D_host_lookup)
+         debug_printf("ignored host %s [%s]\n", host->name, da->address);
+       continue;
+       }
+      #endif
 
-        /* If this is the first address, stick it in the given host block,
-        and change the name if the returned RR has a different name. */
+      /* If this is the first address, stick it in the given host block,
+      and change the name if the returned RR has a different name. */
 
-        if (thishostlast == NULL)
-          {
-          if (strcmpic(host->name, rr->name) != 0)
-            host->name = string_copy_dnsdomain(rr->name);
-          host->address = da->address;
-          host->sort_key = host->mx * 1000 + random_number(500) + randoffset;
-          host->status = hstatus_unknown;
-          host->why = hwhy_unknown;
-          thishostlast = host;
-          }
+      if (thishostlast == NULL)
+       {
+       if (strcmpic(host->name, rr->name) != 0)
+         host->name = string_copy_dnsdomain(rr->name);
+       host->address = da->address;
+       host->sort_key = host->mx * 1000 + random_number(500) + randoffset;
+       host->status = hstatus_unknown;
+       host->why = hwhy_unknown;
+       thishostlast = host;
+       }
 
-        /* Not the first address. Check for, and ignore, duplicates. Then
-        insert in the chain at a random point. */
+      /* Not the first address. Check for, and ignore, duplicates. Then
+      insert in the chain at a random point. */
 
-        else
-          {
-          int new_sort_key;
-          host_item *next;
-
-          /* End of our local chain is specified by "thishostlast". */
-
-          for (next = host;; next = next->next)
-            {
-            if (Ustrcmp(CS da->address, next->address) == 0) break;
-            if (next == thishostlast) { next = NULL; break; }
-            }
-          if (next != NULL) continue;  /* With loop for next address */
-
-          /* Not a duplicate */
-
-          new_sort_key = host->mx * 1000 + random_number(500) + randoffset;
-          next = store_get(sizeof(host_item));
-
-          /* New address goes first: insert the new block after the first one
-          (so as not to disturb the original pointer) but put the new address
-          in the original block. */
-
-          if (new_sort_key < host->sort_key)
-            {
-            *next = *host;                                  /* Copies port */
-            host->next = next;
-            host->address = da->address;
-            host->sort_key = new_sort_key;
-            if (thishostlast == host) thishostlast = next;  /* Local last */
-            if (*lastptr == host) *lastptr = next;          /* Global last */
-            }
-
-          /* Otherwise scan down the addresses for this host to find the
-          one to insert after. */
-
-          else
-            {
-            host_item *h = host;
-            while (h != thishostlast)
-              {
-              if (new_sort_key < h->next->sort_key) break;
-              h = h->next;
-              }
-            *next = *h;                                 /* Copies port */
-            h->next = next;
-            next->address = da->address;
-            next->sort_key = new_sort_key;
-            if (h == thishostlast) thishostlast = next; /* Local last */
-            if (h == *lastptr) *lastptr = next;         /* Global last */
-            }
-          }
-        }
+      else
+       {
+       int new_sort_key;
+       host_item *next;
+
+       /* End of our local chain is specified by "thishostlast". */
+
+       for (next = host;; next = next->next)
+         {
+         if (Ustrcmp(CS da->address, next->address) == 0) break;
+         if (next == thishostlast) { next = NULL; break; }
+         }
+       if (next != NULL) continue;  /* With loop for next address */
+
+       /* Not a duplicate */
+
+       new_sort_key = host->mx * 1000 + random_number(500) + randoffset;
+       next = store_get(sizeof(host_item), GET_UNTAINTED);
+
+       /* New address goes first: insert the new block after the first one
+       (so as not to disturb the original pointer) but put the new address
+       in the original block. */
+
+       if (new_sort_key < host->sort_key)
+         {
+         *next = *host;                                  /* Copies port */
+         host->next = next;
+         host->address = da->address;
+         host->sort_key = new_sort_key;
+         if (thishostlast == host) thishostlast = next;  /* Local last */
+         if (*lastptr == host) *lastptr = next;          /* Global last */
+         }
+
+       /* Otherwise scan down the addresses for this host to find the
+       one to insert after. */
+
+       else
+         {
+         host_item *h = host;
+         while (h != thishostlast)
+           {
+           if (new_sort_key < h->next->sort_key) break;
+           h = h->next;
+           }
+         *next = *h;                                 /* Copies port */
+         h->next = next;
+         next->address = da->address;
+         next->sort_key = new_sort_key;
+         if (h == thishostlast) thishostlast = next; /* Local last */
+         if (h == *lastptr) *lastptr = next;         /* Global last */
+         }
+       }
       }
     }
   }
 
-/* Control gets here only if the econdookup (the A record) succeeded.
+/* Control gets here only if the second lookup (the A record) succeeded.
 However, the address may not be filled in if it was ignored. */
 
-return host->address ? HOST_FOUND : HOST_IGNORED;
+i = host->address
+  ? HOST_FOUND
+  : dnssec_fail
+  ? HOST_FIND_SECURITY
+  : HOST_IGNORED;
+
+out:
+  store_free_dns_answer(dnsa);
+  return i;
 }
 
 
@@ -2530,10 +2465,13 @@ Arguments:
   whichrrs              flags indicating which RRs to look for:
                           HOST_FIND_BY_SRV  => look for SRV
                           HOST_FIND_BY_MX   => look for MX
-                          HOST_FIND_BY_A    => look for A or AAAA
+                          HOST_FIND_BY_A    => look for A
+                          HOST_FIND_BY_AAAA => look for AAAA
                         also flags indicating how the lookup is done
                           HOST_FIND_QUALIFY_SINGLE   ) passed to the
                           HOST_FIND_SEARCH_PARENTS   )   resolver
+                         HOST_FIND_IPV4_FIRST => reverse usual result ordering
+                         HOST_FIND_IPV4_ONLY  => MX results elide ipv6
   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
@@ -2546,6 +2484,7 @@ Returns:                HOST_FIND_FAILED  Failed to find the host or domain;
                                           if there was a syntax error,
                                           host_find_failed_syntax is set.
                         HOST_FIND_AGAIN   Could not resolve at this time
+                       HOST_FIND_SECURITY dnsssec required but not acheived
                         HOST_FOUND        Host found
                         HOST_FOUND_LOCAL  The lowest MX record points to this
                                           machine, if MX records were found, or
@@ -2560,19 +2499,18 @@ host_find_bydns(host_item *host, const uschar *ignore_target_hosts, int whichrrs
   const uschar **fully_qualified_name, BOOL *removed)
 {
 host_item *h, *last;
-dns_record *rr;
 int rc = DNS_FAIL;
 int ind_type = 0;
 int yield;
-dns_answer dnsa;
+dns_answer * dnsa = store_get_dns_answer();
 dns_scan dnss;
 BOOL dnssec_require = dnssec_d
-                   && match_isinlist(host->name, CUSS &dnssec_d->require,
-                                   0, NULL, NULL, MCL_DOMAIN, TRUE, NULL) == OK;
+  && match_isinlist(host->name, CUSS &dnssec_d->require,
+                 0, &domainlist_anchor, NULL, MCL_DOMAIN, TRUE, NULL) == OK;
 BOOL dnssec_request = dnssec_require
-                   || (  dnssec_d
-                      && match_isinlist(host->name, CUSS &dnssec_d->request,
-                                   0, NULL, NULL, MCL_DOMAIN, TRUE, NULL) == OK);
+    || (  dnssec_d
+       && match_isinlist(host->name, CUSS &dnssec_d->request,
+                   0, &domainlist_anchor, NULL, MCL_DOMAIN, TRUE, NULL) == OK);
 dnssec_status_t dnssec;
 
 /* Set the default fully qualified name to the incoming name, initialize the
@@ -2583,20 +2521,21 @@ if (fully_qualified_name != NULL) *fully_qualified_name = host->name;
 dns_init((whichrrs & HOST_FIND_QUALIFY_SINGLE) != 0,
          (whichrrs & HOST_FIND_SEARCH_PARENTS) != 0,
         dnssec_request);
-host_find_failed_syntax = FALSE;
+f.host_find_failed_syntax = FALSE;
 
 /* First, if requested, look for SRV records. The service name is given; we
 assume TCP protocol. DNS domain names are constrained to a maximum of 256
 characters, so the code below should be safe. */
 
-if ((whichrrs & HOST_FIND_BY_SRV) != 0)
+if (whichrrs & HOST_FIND_BY_SRV)
   {
-  uschar buffer[300];
-  uschar *temp_fully_qualified_name = buffer;
+  gstring * g;
+  uschar * temp_fully_qualified_name;
   int prefix_length;
 
-  (void)sprintf(CS buffer, "_%s._tcp.%n%.256s", srv_service, &prefix_length,
-    host->name);
+  g = string_fmt_append(NULL, "_%s._tcp.%n%.256s",
+       srv_service, &prefix_length, host->name);
+  temp_fully_qualified_name = string_from_gstring(g);
   ind_type = T_SRV;
 
   /* Search for SRV records. If the fully qualified name is different to
@@ -2605,29 +2544,30 @@ if ((whichrrs & HOST_FIND_BY_SRV) != 0)
 
   dnssec = DS_UNK;
   lookup_dnssec_authenticated = NULL;
-  rc = dns_lookup_timerwrap(&dnsa, buffer, ind_type, CUSS &temp_fully_qualified_name);
+  rc = dns_lookup_timerwrap(dnsa, temp_fully_qualified_name, ind_type,
+       CUSS &temp_fully_qualified_name);
 
   DEBUG(D_dns)
     if ((dnssec_request || dnssec_require)
-       & !dns_is_secure(&dnsa)
-       & dns_is_aa(&dnsa))
+       && !dns_is_secure(dnsa)
+       && dns_is_aa(dnsa))
       debug_printf("DNS lookup of %.256s (SRV) requested AD, but got AA\n", host->name);
 
   if (dnssec_request)
     {
-    if (dns_is_secure(&dnsa))
+    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)
+  if (temp_fully_qualified_name != g->s && 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))
+  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);
@@ -2635,13 +2575,13 @@ if ((whichrrs & HOST_FIND_BY_SRV) != 0)
     }
   if (rc == DNS_FAIL || rc == DNS_AGAIN)
     {
-    #ifndef STAND_ALONE
-    if (match_isinlist(host->name, CUSS &srv_fail_domains, 0, NULL, NULL,
-       MCL_DOMAIN, TRUE, NULL) != OK)
-    #endif
+#ifndef STAND_ALONE
+    if (match_isinlist(host->name, CUSS &srv_fail_domains, 0,
+       &domainlist_anchor, 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 srv_fail_domains)\n", (rc == DNS_FAIL)? "FAIL":"AGAIN");
+      "(domain in srv_fail_domains)\n", rc == DNS_FAIL ? "FAIL":"AGAIN");
     }
   }
 
@@ -2652,31 +2592,29 @@ same domain. The result will be DNS_NODATA if the domain exists but has no MX
 records. On DNS failures, we give the "try again" error unless the domain is
 listed as one for which we continue. */
 
-if (rc != DNS_SUCCEED && (whichrrs & HOST_FIND_BY_MX) != 0)
+if (rc != DNS_SUCCEED  &&  whichrrs & HOST_FIND_BY_MX)
   {
   ind_type = T_MX;
   dnssec = DS_UNK;
   lookup_dnssec_authenticated = NULL;
-  rc = dns_lookup_timerwrap(&dnsa, host->name, ind_type, fully_qualified_name);
+  rc = dns_lookup_timerwrap(dnsa, host->name, ind_type, fully_qualified_name);
 
   DEBUG(D_dns)
-    if ((dnssec_request || dnssec_require)
-       & !dns_is_secure(&dnsa)
-       & dns_is_aa(&dnsa))
+    if (  (dnssec_request || dnssec_require)
+       && !dns_is_secure(dnsa)
+       && dns_is_aa(dnsa))
       debug_printf("DNS lookup of %.256s (MX) requested AD, but got AA\n", host->name);
 
   if (dnssec_request)
-    {
-    if (dns_is_secure(&dnsa))
+    if (dns_is_secure(dnsa))
       {
-      DEBUG(D_host_lookup) debug_printf("%s MX DNSSEC\n", host->name);
+      DEBUG(D_host_lookup) debug_printf("%s (MX resp) DNSSEC\n", host->name);
       dnssec = DS_YES; lookup_dnssec_authenticated = US"yes";
       }
     else
       {
       dnssec = DS_NO; lookup_dnssec_authenticated = US"no";
       }
-    }
 
   switch (rc)
     {
@@ -2684,19 +2622,24 @@ if (rc != DNS_SUCCEED && (whichrrs & HOST_FIND_BY_MX) != 0)
       yield = HOST_FIND_FAILED; goto out;
 
     case DNS_SUCCEED:
-      if (!dnssec_require || dns_is_secure(&dnsa))
+      if (!dnssec_require || dns_is_secure(dnsa))
        break;
-      log_write(L_host_lookup_failed, LOG_MAIN,
-                 "dnssec fail on MX for %.256s", host->name);
+      DEBUG(D_host_lookup)
+       debug_printf("dnssec fail on MX for %.256s", host->name);
+#ifndef STAND_ALONE
+      if (match_isinlist(host->name, CUSS &mx_fail_domains, 0,
+         &domainlist_anchor, NULL, MCL_DOMAIN, TRUE, NULL) != OK)
+       { yield = HOST_FIND_SECURITY; goto out; }
+#endif
       rc = DNS_FAIL;
       /*FALLTHROUGH*/
 
     case DNS_FAIL:
     case DNS_AGAIN:
-      #ifndef STAND_ALONE
-      if (match_isinlist(host->name, CUSS &mx_fail_domains, 0, NULL, NULL,
-         MCL_DOMAIN, TRUE, NULL) != OK)
-      #endif
+#ifndef STAND_ALONE
+      if (match_isinlist(host->name, CUSS &mx_fail_domains, 0,
+         &domainlist_anchor, 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");
@@ -2710,7 +2653,7 @@ host. */
 
 if (rc != DNS_SUCCEED)
   {
-  if ((whichrrs & HOST_FIND_BY_A) == 0)
+  if (!(whichrrs & (HOST_FIND_BY_A | HOST_FIND_BY_AAAA)))
     {
     DEBUG(D_host_lookup) debug_printf("Address records are not being sought\n");
     yield = HOST_FIND_FAILED;
@@ -2723,7 +2666,7 @@ if (rc != DNS_SUCCEED)
   host->dnssec = DS_UNK;
   lookup_dnssec_authenticated = NULL;
   rc = set_address_from_dns(host, &last, ignore_target_hosts, FALSE,
-    fully_qualified_name, dnssec_request, dnssec_require);
+    fully_qualified_name, dnssec_request, dnssec_require, whichrrs);
 
   /* 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
@@ -2737,18 +2680,15 @@ if (rc != DNS_SUCCEED)
     if (rc == HOST_IGNORED) rc = HOST_FIND_FAILED;  /* No special action */
 
   DEBUG(D_host_lookup)
-    {
-    host_item *h;
-    if (host->address != NULL)
+    if (host->address)
       {
-      if (fully_qualified_name != NULL)
+      if (fully_qualified_name)
         debug_printf("fully qualified name = %s\n", *fully_qualified_name);
-      for (h = host; h != last->next; h = h->next)
+      for (host_item * h = host; h != last->next; h = h->next)
         debug_printf("%s %s mx=%d sort=%d %s\n", h->name,
-          (h->address == NULL)? US"<null>" : h->address, h->mx, h->sort_key,
-          (h->status >= hstatus_unusable)? US"*" : US"");
+          h->address ? h->address : US"<null>", h->mx, h->sort_key,
+          h->status >= hstatus_unusable ? US"*" : US"");
       }
-    }
 
   yield = rc;
   goto out;
@@ -2776,18 +2716,15 @@ host which is not the primary hostname. */
 
 last = NULL;    /* Indicates that not even the first item is filled yet */
 
-for (rr = dns_next_rr(&dnsa, &dnss, RESET_ANSWERS);
-     rr != NULL;
-     rr = dns_next_rr(&dnsa, &dnss, RESET_NEXT))
+for (dns_record * rr = dns_next_rr(dnsa, &dnss, RESET_ANSWERS);
+     rr;
+     rr = dns_next_rr(dnsa, &dnss, RESET_NEXT)) if (rr->type == ind_type)
   {
-  int precedence;
-  int weight = 0;        /* For SRV records */
+  int precedence, weight;
   int port = PORT_NONE;
-  uschar *s;             /* MUST be unsigned for GETSHORT */
+  const uschar * s = rr->data; /* MUST be unsigned for GETSHORT */
   uschar data[256];
 
-  if (rr->type != ind_type) continue;
-  s = rr->data;
   GETSHORT(precedence, s);      /* Pointer s is advanced */
 
   /* For MX records, we use a random "weight" which causes multiple records of
@@ -2795,20 +2732,18 @@ for (rr = dns_next_rr(&dnsa, &dnss, RESET_ANSWERS);
 
   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
-  records of equal priority (precedence). */
-
   else
     {
+    /* 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
+    records of equal priority (precedence). */
     GETSHORT(weight, s);
     GETSHORT(port, s);
     }
 
   /* Get the name of the host pointed to. */
 
-  (void)dn_expand(dnsa.answer, dnsa.answer + dnsa.answerlen, s,
+  (void)dn_expand(dnsa->answer, dnsa->answer + dnsa->answerlen, s,
     (DN_EXPAND_ARG4_TYPE)data, sizeof(data));
 
   /* Check that we haven't already got this host on the chain; if we have,
@@ -2816,17 +2751,16 @@ for (rr = dns_next_rr(&dnsa, &dnss, RESET_ANSWERS);
   never know what junk might get into the DNS (and this case has been seen on
   more than one occasion). */
 
-  if (last != NULL)       /* This is not the first record */
+  if (last)       /* This is not the first record */
     {
     host_item *prev = NULL;
 
     for (h = host; h != last->next; prev = h, h = h->next)
-      {
       if (strcmpic(h->name, data) == 0)
         {
         DEBUG(D_host_lookup)
           debug_printf("discarded duplicate host %s (MX=%d)\n", data,
-            (precedence > h->mx)? precedence : h->mx);
+            precedence > h->mx ? precedence : h->mx);
         if (precedence >= h->mx) goto NEXT_MX_RR; /* Skip greater precedence */
         if (h == host)                            /* Override first item */
           {
@@ -2842,14 +2776,13 @@ for (rr = dns_next_rr(&dnsa, &dnss, RESET_ANSWERS);
         if (h == last) last = prev;
         break;
         }
-      }
     }
 
   /* If this is the first MX or SRV record, put the data into the existing host
   block. Otherwise, add a new block in the correct place; if it has to be
   before the first block, copy the first block's data to a new second block. */
 
-  if (last == NULL)
+  if (!last)
     {
     host->name = string_copy_dnsdomain(data);
     host->address = NULL;
@@ -2861,13 +2794,12 @@ for (rr = dns_next_rr(&dnsa, &dnss, RESET_ANSWERS);
     host->dnssec = dnssec;
     last = host;
     }
+  else
 
   /* Make a new host item and seek the correct insertion place */
-
-  else
     {
     int sort_key = precedence * 1000 + weight;
-    host_item *next = store_get(sizeof(host_item));
+    host_item * next = store_get(sizeof(host_item), GET_UNTAINTED);
     next->name = string_copy_dnsdomain(data);
     next->address = NULL;
     next->port = port;
@@ -2889,21 +2821,18 @@ for (rr = dns_next_rr(&dnsa, &dnss, RESET_ANSWERS);
       host->next = next;
       if (last == host) last = next;
       }
+    else
 
     /* Else scan down the items we have inserted as part of this exercise;
     don't go further. */
-
-    else
       {
       for (h = host; h != last; h = h->next)
-        {
         if (sort_key < h->next->sort_key)
           {
           next->next = h->next;
           h->next = next;
           break;
           }
-        }
 
       /* Join on after the last host item that's part of this
       processing if we haven't stopped sooner. */
@@ -2936,7 +2865,7 @@ remaining in the same priority group. */
 
 if (ind_type == T_SRV)
   {
-  host_item **pptr;
+  host_item ** pptr;
 
   if (host == last && host->name[0] == 0)
     {
@@ -2980,10 +2909,9 @@ if (ind_type == T_SRV)
 
       for (ppptr = pptr, hhh = h;
            hhh != hh;
-           ppptr = &(hhh->next), hhh = hhh->next)
-        {
-        if (hhh->sort_key >= randomizer) break;
-        }
+           ppptr = &hhh->next, hhh = hhh->next)
+        if (hhh->sort_key >= randomizer)
+         break;
 
       /* hhh now points to the host that should go first; ppptr points to the
       place that points to it. Unfortunately, if the start of the minilist is
@@ -3008,7 +2936,6 @@ if (ind_type == T_SRV)
           hhh->next = temp.next;
           h->next = hhh;
           }
-
         else
           {
           hhh->next = h;               /* The rest of the chain follows it */
@@ -3060,17 +2987,19 @@ for (h = host; h != last->next; h = h->next)
   if (h->address) continue;  /* Inserted by a multihomed host */
 
   rc = set_address_from_dns(h, &last, ignore_target_hosts, allow_mx_to_ip,
-    NULL, dnssec_request, dnssec_require);
+    NULL, dnssec_request, dnssec_require,
+    whichrrs & HOST_FIND_IPV4_ONLY
+    ?  HOST_FIND_BY_A  :  HOST_FIND_BY_A | HOST_FIND_BY_AAAA);
   if (rc != HOST_FOUND)
     {
     h->status = hstatus_unusable;
-    if (rc == HOST_FIND_AGAIN)
+    switch (rc)
       {
-      yield = rc;
-      h->why = hwhy_deferred;
+      case HOST_FIND_AGAIN:    yield = rc; h->why = hwhy_deferred; break;
+      case HOST_FIND_SECURITY: yield = rc; h->why = hwhy_insecure; break;
+      case HOST_IGNORED:       h->why = hwhy_ignored; break;
+      default:                 h->why = hwhy_failed; break;
       }
-    else
-      h->why = rc == HOST_IGNORED ? hwhy_ignored : hwhy_failed;
     }
   }
 
@@ -3079,7 +3008,7 @@ been explicitly ignored, and remove them from the list, as if they did not
 exist. If we end up with just a single, ignored host, flatten its fields as if
 nothing was found. */
 
-if (ignore_target_hosts != NULL)
+if (ignore_target_hosts)
   {
   host_item *prev = NULL;
   for (h = host; h != last->next; h = h->next)
@@ -3115,24 +3044,32 @@ 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 && !disable_ipv6)
+if (h != last && !disable_ipv6) for (h = host; h != last; h = h->next)
   {
-  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 */
-        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;
-    *next = temp;
-    }
+  host_item temp;
+  host_item *next = h->next;
+
+  if (  h->mx != next->mx                      /* If next is different MX */
+     || !h->address                            /* OR this one is unset */
+     )
+    continue;                                  /* move on to next */
+
+  if (  whichrrs & HOST_FIND_IPV4_FIRST
+     ?     !Ustrchr(h->address, ':')           /* OR this one is IPv4 */
+        || next->address
+           && Ustrchr(next->address, ':')      /* OR next is IPv6 */
+
+     :     Ustrchr(h->address, ':')            /* OR this one is IPv6 */
+        || next->address
+           && !Ustrchr(next->address, ':')     /* OR next is IPv4 */
+     )
+    continue;                                /* move on to next */
+
+  temp = *h;                                 /* otherwise, swap */
+  temp.next = next->next;
+  *h = *next;
+  h->next = next;
+  *next = temp;
   }
 #endif
 
@@ -3151,13 +3088,14 @@ if (rc != HOST_FIND_FAILED) yield = rc;
 
 DEBUG(D_host_lookup)
   {
-  if (fully_qualified_name != NULL)
+  if (fully_qualified_name)
     debug_printf("fully qualified name = %s\n", *fully_qualified_name);
   debug_printf("host_find_bydns yield = %s (%d); returned hosts:\n",
-    (yield == HOST_FOUND)? "HOST_FOUND" :
-    (yield == HOST_FOUND_LOCAL)? "HOST_FOUND_LOCAL" :
-    (yield == HOST_FIND_AGAIN)? "HOST_FIND_AGAIN" :
-    (yield == HOST_FIND_FAILED)? "HOST_FIND_FAILED" : "?",
+    yield == HOST_FOUND                ? "HOST_FOUND" :
+    yield == HOST_FOUND_LOCAL  ? "HOST_FOUND_LOCAL" :
+    yield == HOST_FIND_SECURITY        ? "HOST_FIND_SECURITY" :
+    yield == HOST_FIND_AGAIN   ? "HOST_FIND_AGAIN" :
+    yield == HOST_FIND_FAILED  ? "HOST_FIND_FAILED" : "?",
     yield);
   for (h = host; h != last->next; h = h->next)
     {
@@ -3173,9 +3111,83 @@ DEBUG(D_host_lookup)
 out:
 
 dns_init(FALSE, FALSE, FALSE); /* clear the dnssec bit for getaddrbyname */
+store_free_dns_answer(dnsa);
 return yield;
 }
 
+
+
+
+#ifdef SUPPORT_DANE
+/* Lookup TLSA record for host/port.
+Return:  OK            success with dnssec; DANE mode
+         DEFER         Do not use this host now, may retry later
+        FAIL_FORCED    No TLSA record; DANE not usable
+        FAIL           Do not use this connection
+*/
+
+int
+tlsa_lookup(const host_item * host, dns_answer * dnsa, BOOL dane_required)
+{
+uschar buffer[300];
+const uschar * fullname = buffer;
+int rc;
+BOOL sec;
+
+/* TLSA lookup string */
+(void)sprintf(CS buffer, "_%d._tcp.%.256s", host->port, host->name);
+
+rc = dns_lookup_timerwrap(dnsa, buffer, T_TLSA, &fullname);
+sec = dns_is_secure(dnsa);
+DEBUG(D_transport)
+  debug_printf("TLSA lookup ret %s %sDNSSEC\n", dns_rc_names[rc], sec ? "" : "not ");
+
+switch (rc)
+  {
+  case DNS_AGAIN:
+    return DEFER; /* just defer this TLS'd conn */
+
+  case DNS_SUCCEED:
+    if (sec)
+      {
+      DEBUG(D_transport)
+       {
+       dns_scan dnss;
+       for (dns_record * rr = dns_next_rr(dnsa, &dnss, RESET_ANSWERS); rr;
+            rr = dns_next_rr(dnsa, &dnss, RESET_NEXT))
+         if (rr->type == T_TLSA && rr->size > 3)
+           {
+           uint16_t payload_length = rr->size - 3;
+           uschar s[MAX_TLSA_EXPANDED_SIZE], * sp = s, * p = US rr->data;
+
+           sp += sprintf(CS sp, "%d ", *p++); /* usage */
+           sp += sprintf(CS sp, "%d ", *p++); /* selector */
+           sp += sprintf(CS sp, "%d ", *p++); /* matchtype */
+           while (payload_length-- > 0 && sp-s < (MAX_TLSA_EXPANDED_SIZE - 4))
+             sp += sprintf(CS sp, "%02x", *p++);
+
+           debug_printf(" %s\n", s);
+           }
+       }
+      return OK;
+      }
+    log_write(0, LOG_MAIN,
+      "DANE error: TLSA lookup for %s not DNSSEC", host->name);
+    /*FALLTRHOUGH*/
+
+  case DNS_NODATA:     /* no TLSA RR for this lookup */
+  case DNS_NOMATCH:    /* no records at all for this lookup */
+    return dane_required ? FAIL : FAIL_FORCED;
+
+  default:
+  case DNS_FAIL:
+    return dane_required ? FAIL : DEFER;
+  }
+}
+#endif /*SUPPORT_DANE*/
+
+
+
 /*************************************************
 **************************************************
 *             Stand-alone test program           *
@@ -3187,7 +3199,7 @@ return yield;
 int main(int argc, char **cargv)
 {
 host_item h;
-int whichrrs = HOST_FIND_BY_MX | HOST_FIND_BY_A;
+int whichrrs = HOST_FIND_BY_MX | HOST_FIND_BY_A | HOST_FIND_BY_AAAA;
 BOOL byname = FALSE;
 BOOL qualify_single = TRUE;
 BOOL search_parents = FALSE;
@@ -3198,6 +3210,7 @@ uschar buffer[256];
 
 disable_ipv6 = FALSE;
 primary_hostname = US"";
+store_init();
 store_pool = POOL_MAIN;
 debug_selector = D_host_lookup|D_interface;
 debug_file = stdout;
@@ -3229,15 +3242,15 @@ while (Ufgets(buffer, 256, stdin) != NULL)
 
   if (Ustrcmp(buffer, "byname") == 0) byname = TRUE;
   else if (Ustrcmp(buffer, "no_byname") == 0) byname = FALSE;
-  else if (Ustrcmp(buffer, "a_only") == 0) whichrrs = HOST_FIND_BY_A;
+  else if (Ustrcmp(buffer, "a_only") == 0) whichrrs = HOST_FIND_BY_A | HOST_FIND_BY_AAAA;
   else if (Ustrcmp(buffer, "mx_only") == 0) whichrrs = HOST_FIND_BY_MX;
   else if (Ustrcmp(buffer, "srv_only") == 0) whichrrs = HOST_FIND_BY_SRV;
   else if (Ustrcmp(buffer, "srv+a") == 0)
-    whichrrs = HOST_FIND_BY_SRV | HOST_FIND_BY_A;
+    whichrrs = HOST_FIND_BY_SRV | HOST_FIND_BY_A | HOST_FIND_BY_AAAA;
   else if (Ustrcmp(buffer, "srv+mx") == 0)
     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;
+    whichrrs = HOST_FIND_BY_SRV | HOST_FIND_BY_MX | HOST_FIND_BY_A | HOST_FIND_BY_AAAA;
   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;
@@ -3245,9 +3258,9 @@ while (Ufgets(buffer, 256, stdin) != NULL)
   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, "no_require_dnssec") == 0) require_dnssec = FALSE;
   else if (Ustrcmp(buffer, "test_harness") == 0)
-    running_in_test_harness = !running_in_test_harness;
+    f.running_in_test_harness = !f.running_in_test_harness;
   else if (Ustrcmp(buffer, "ipv6") == 0) disable_ipv6 = !disable_ipv6;
   else if (Ustrcmp(buffer, "res_debug") == 0)
     {
@@ -3287,9 +3300,13 @@ while (Ufgets(buffer, 256, stdin) != NULL)
       : host_find_bydns(&h, NULL, flags, US"smtp", NULL, NULL,
                        &d, &fully_qualified_name, NULL);
 
-    if (rc == HOST_FIND_FAILED) printf("Failed\n");
-      else if (rc == HOST_FIND_AGAIN) printf("Again\n");
-        else if (rc == HOST_FOUND_LOCAL) printf("Local\n");
+    switch (rc)
+      {
+      case HOST_FIND_FAILED:   printf("Failed\n");     break;
+      case HOST_FIND_AGAIN:    printf("Again\n");      break;
+      case HOST_FIND_SECURITY: printf("Security\n");   break;
+      case HOST_FOUND_LOCAL:   printf("Local\n");      break;
+      }
     }
 
   printf("\n> ");
@@ -3299,7 +3316,6 @@ printf("Testing host_aton\n");
 printf("> ");
 while (Ufgets(buffer, 256, stdin) != NULL)
   {
-  int i;
   int x[4];
   int len = Ustrlen(buffer);
 
@@ -3310,7 +3326,7 @@ while (Ufgets(buffer, 256, stdin) != NULL)
 
   len = host_aton(buffer, x);
   printf("length = %d ", len);
-  for (i = 0; i < len; i++)
+  for (int i = 0; i < len; i++)
     {
     printf("%04x ", (x[i] >> 16) & 0xffff);
     printf("%04x ", x[i] & 0xffff);