commit 88a9e485e3709a77867542f5e9a5f7ac60238f49
Author: Jeremy Harris <jgh146exb@wizmail.org>
Date:   Wed Nov 29 22:18:18 2017 +0000

    TLS: Fix excessive calling of smtp_auth_acl under AUTH_TLS.  Bug 2203

diff --git a/src/src/smtp_in.c b/src/src/smtp_in.c
index 0aabc535..76c72de9 100644
--- a/src/src/smtp_in.c
+++ b/src/src/smtp_in.c
@@ -187,7 +187,7 @@ static smtp_cmd_list cmd_list[] = {
   { "auth",       sizeof("auth")-1,       AUTH_CMD, TRUE,  TRUE  },
   #ifdef SUPPORT_TLS
   { "starttls",   sizeof("starttls")-1,   STARTTLS_CMD, FALSE, FALSE },
-  { "tls_auth",   0,                      TLS_AUTH_CMD, FALSE, TRUE },
+  { "tls_auth",   0,                      TLS_AUTH_CMD, FALSE, FALSE },
   #endif
 
 /* If you change anything above here, also fix the definitions below. */
@@ -2826,8 +2826,12 @@ if (check_proxy_protocol_host())
   smtps port for use with older style SSL MTAs. */
 
 #ifdef SUPPORT_TLS
-  if (tls_in.on_connect && tls_server_start(tls_require_ciphers, &user_msg) != OK)
-    return smtp_log_tls_fail(user_msg);
+  if (tls_in.on_connect)
+    {
+    if (tls_server_start(tls_require_ciphers, &user_msg) != OK)
+      return smtp_log_tls_fail(user_msg);
+    cmd_list[CMD_LIST_TLS_AUTH].is_mail_cmd = TRUE;
+    }
 #endif
 
 /* Run the connect ACL if it exists */
@@ -3800,7 +3804,6 @@ cmd_list[CMD_LIST_HELO].is_mail_cmd = TRUE;
 cmd_list[CMD_LIST_EHLO].is_mail_cmd = TRUE;
 #ifdef SUPPORT_TLS
 cmd_list[CMD_LIST_STARTTLS].is_mail_cmd = TRUE;
-cmd_list[CMD_LIST_TLS_AUTH].is_mail_cmd = TRUE;
 #endif
 
 /* Set the local signal handler for SIGTERM - it tries to end off tidily */

commit 2d9c1e31d619a61ca09b30a3abfe73ddfed67ce5
Author: Jeremy Harris <jgh146exb@wizmail.org>
Date:   Wed Nov 29 23:22:34 2017 +0000

    TLS: avoid calling smtp_auth_acl on client cert when no tls authenticator is configured

diff --git a/src/src/smtp_in.c b/src/src/smtp_in.c
index 76c72de9..00e9d41a 100644
--- a/src/src/smtp_in.c
+++ b/src/src/smtp_in.c
@@ -3849,24 +3849,24 @@ while (done <= 0)
      )
     {
     cmd_list[CMD_LIST_TLS_AUTH].is_mail_cmd = FALSE;
-    if (  acl_smtp_auth
-       && (rc = acl_check(ACL_WHERE_AUTH, NULL, acl_smtp_auth,
-		  &user_msg, &log_msg)) != OK
-       )
-      {
-      done = smtp_handle_acl_fail(ACL_WHERE_AUTH, rc, user_msg, log_msg);
-      continue;
-      }
 
     for (au = auths; au; au = au->next)
       if (strcmpic(US"tls", au->driver_name) == 0)
 	{
-	smtp_cmd_data = NULL;
-
-	if (smtp_in_auth(au, &s, &ss) == OK)
-	  { DEBUG(D_auth) debug_printf("tls auth succeeded\n"); }
+	if (  acl_smtp_auth
+	   && (rc = acl_check(ACL_WHERE_AUTH, NULL, acl_smtp_auth,
+		      &user_msg, &log_msg)) != OK
+	   )
+	  done = smtp_handle_acl_fail(ACL_WHERE_AUTH, rc, user_msg, log_msg);
 	else
-	  { DEBUG(D_auth) debug_printf("tls auth not succeeded\n"); }
+	  {
+	  smtp_cmd_data = NULL;
+
+	  if (smtp_in_auth(au, &s, &ss) == OK)
+	    { DEBUG(D_auth) debug_printf("tls auth succeeded\n"); }
+	  else
+	    { DEBUG(D_auth) debug_printf("tls auth not succeeded\n"); }
+	  }
 	break;
 	}
     }

commit f9d9829cee89e77043113209b0fb619d66274bed
Author: Jeremy Harris <jgh146exb@wizmail.org>
Date:   Fri Dec 1 22:43:19 2017 +0000

    Debug: fix coding in dnssec reporting.  Bug 2205

diff --git a/src/src/host.c b/src/src/host.c
index 05bde3fb..1f0d9195 100644
--- a/src/src/host.c
+++ b/src/src/host.c
@@ -2612,8 +2612,8 @@ if ((whichrrs & HOST_FIND_BY_SRV) != 0)
 
   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)

commit c73a4d073e195c11bb5b03b91c61478ab6935593
Author: Heiko Schlittermann (HS12-RIPE) <hs@schlittermann.de>
Date:   Sun Dec 3 18:17:43 2017 +0100

    DKIM: Ignore non-DKIM TXT records in DNS response. Bug 2207

diff --git a/src/src/dkim.c b/src/src/dkim.c
index 5e97c1b7..9731a63d 100644
--- a/src/src/dkim.c
+++ b/src/src/dkim.c
@@ -73,6 +73,9 @@ for (rr = dns_next_rr(&dnsa, &dnss, RESET_ANSWERS);
       if (answer_offset >= PDKIM_DNS_TXT_MAX_RECLEN)
 	return PDKIM_FAIL;	/*XXX better error detail?  logging? */
       }
+
+    /* check if this looks like a DKIM record */
+    if (strncasecmp(answer, "v=dkim", 6) != 0) continue;
     return PDKIM_OK;
     }
 
@@ -148,7 +151,7 @@ if (!(s = sig->domain)) s = US"<UNSET>";
 logmsg = string_append(logmsg, 2, "d=", s);
 if (!(s = sig->selector)) s = US"<UNSET>";
 logmsg = string_append(logmsg, 2, " s=", s);
-logmsg = string_append(logmsg, 7, 
+logmsg = string_append(logmsg, 7,
 " c=", sig->canon_headers == PDKIM_CANON_SIMPLE ? "simple" : "relaxed",
 "/",   sig->canon_body    == PDKIM_CANON_SIMPLE ? "simple" : "relaxed",
 " a=", dkim_sig_to_a_tag(sig),
@@ -371,7 +374,7 @@ for (sig = dkim_signatures; sig; sig = sig->next)
 
     dkim_verify_status = dkim_exim_expand_query(DKIM_VERIFY_STATUS);
     dkim_verify_reason = dkim_exim_expand_query(DKIM_VERIFY_REASON);
-    
+
     if ((rc = dkim_acl_call(id, res_ptr, user_msgptr, log_msgptr)) != OK)
       return rc;
     }

commit c1f0ee1044f2d39648ffdd96194566020d3d164e
Author: Andreas Piesk <a.piesk@mailbox.org>
Date:   Fri Dec 22 10:05:02 2017 +0000

    Fix build of nisplus lookup

diff --git a/src/src/lookups/nisplus.c b/src/src/lookups/nisplus.c
index e4184bbd..7cd00eed 100644
--- a/src/src/lookups/nisplus.c
+++ b/src/src/lookups/nisplus.c
@@ -54,7 +54,6 @@ nis_object *tno, *eno;
 struct entry_obj *eo;
 struct table_obj *ta;
 uschar *p = query + length;
-uschar *yield = NULL;
 gstring * yield = NULL;
 
 do_cache = do_cache;   /* Placate picky compilers */
@@ -173,7 +172,7 @@ for (i = 0; i < eo->en_cols.en_cols_len; i++)
       yield = string_catn(yield, US"\"", 1);
       }
     else
-    eyield = string_catn(yield, value, len);
+      yield = string_catn(yield, value, len);
 
     yield = string_catn(yield, US" ", 1);
     }

commit 032e3aa3d12b3c939c8602ff801847a8305bd849
Author: Jeremy Harris <jgh146exb@wizmail.org>
Date:   Fri Dec 22 10:25:56 2017 +0000

    Fix const issue in nisplus lookup

diff --git a/src/src/lookups/nisplus.c b/src/src/lookups/nisplus.c
index 7cd00eed..1f226d52 100644
--- a/src/src/lookups/nisplus.c
+++ b/src/src/lookups/nisplus.c
@@ -42,18 +42,18 @@ yield is the concatenation of all the fields, preceded by their names and an
 equals sign. */
 
 static int
-nisplus_find(void *handle, uschar *filename, uschar *query, int length,
+nisplus_find(void *handle, uschar *filename, const uschar *query, int length,
   uschar **result, uschar **errmsg, uint *do_cache)
 {
 int i;
 int error_error = FAIL;
-uschar *field_name = NULL;
+const uschar * field_name = NULL;
 nis_result *nrt = NULL;
 nis_result *nre = NULL;
 nis_object *tno, *eno;
 struct entry_obj *eo;
 struct table_obj *ta;
-uschar *p = query + length;
+const uschar * p = query + length;
 gstring * yield = NULL;
 
 do_cache = do_cache;   /* Placate picky compilers */
@@ -63,12 +63,15 @@ has been given. */
 
 while (p > query && p[-1] != ':') p--;
 
-if (p > query)
+if (p > query)		/* get the query without the result-field */
   {
+  uint len = p-1 - query;
   field_name = p;
-  p[-1] = 0;
+  query = string_copyn(query, len);
+  p = query + len;
   }
-else p = query + length;
+else
+  p = query + length;
 
 /* Now search backwards to find the comma that starts the
 table name. */
@@ -100,7 +103,7 @@ if (tno->zo_data.zo_type != TABLE_OBJ)
   *errmsg = string_sprintf("NIS+ error: %s is not a table", p);
   goto NISPLUS_EXIT;
   }
-ta = &(tno->zo_data.objdata_u.ta_data);
+ta = &tno->zo_data.objdata_u.ta_data;
 
 /* Now look up the entry in the table, check that we got precisely one
 object and that it is a table entry. */
@@ -152,7 +155,7 @@ for (i = 0; i < eo->en_cols.en_cols_len; i++)
 
   /* Concatenate all fields if no specific one selected */
 
-  if (field_name == NULL)
+  if (!field_name)
     {
     yield = string_cat (yield, tc->tc_name);
     yield = string_catn(yield, US"=", 1);
@@ -195,11 +198,9 @@ if (field_name)
 else
   store_reset(yield->s + yield->ptr + 1);
 
-/* Restore the colon in the query, and free result store before
-finishing. */
+/* Free result store before finishing. */
 
 NISPLUS_EXIT:
-if (field_name) field_name[-1] = ':';
 if (nrt) nis_freeresult(nrt);
 if (nre) nis_freeresult(nre);
 

commit 8143ff45b2b9edd3532fe4056d5736ecc4e0a766
Author: Jeremy Harris <jgh146exb@wizmail.org>
Date:   Sun Dec 24 21:30:20 2017 +0000

    Lookups: fix pgsql multiple-row, single-column return
    
    Report & fix from James <list@xdrv.co.uk>; additional tidying and testcase by JGH
    
    Broken-by: acec9514b1

diff --git a/src/src/lookups/pgsql.c b/src/src/lookups/pgsql.c
index 0b771f59..cece43b6 100644
--- a/src/src/lookups/pgsql.c
+++ b/src/src/lookups/pgsql.c
@@ -140,7 +140,7 @@ has the password removed. This copy is also used for debugging output. */
 for (i = 2; i >= 0; i--)
   {
   uschar *pp = Ustrrchr(server, '/');
-  if (pp == NULL)
+  if (!pp)
     {
     *errmsg = string_sprintf("incomplete pgSQL server data: %s",
       (i == 2)? server : server_copy);
@@ -156,18 +156,16 @@ for (i = 2; i >= 0; i--)
 start is the identification of the server (host or path). See if we have a
 cached connection to the server. */
 
-for (cn = pgsql_connections; cn != NULL; cn = cn->next)
-  {
+for (cn = pgsql_connections; cn; cn = cn->next)
   if (Ustrcmp(cn->server, server_copy) == 0)
     {
     pg_conn = cn->handle;
     break;
     }
-  }
 
 /* If there is no cached connection, we must set one up. */
 
-if (cn == NULL)
+if (!cn)
   {
   uschar *port = US"";
 
@@ -178,7 +176,7 @@ if (cn == NULL)
     uschar *last_slash, *last_dot, *p;
 
     p = ++server;
-    while (*p != 0 && *p != ')') p++;
+    while (*p && *p != ')') p++;
     *p = 0;
 
     last_slash = Ustrrchr(server, '/');
@@ -191,10 +189,9 @@ if (cn == NULL)
     We have to call PQsetdbLogin with '/var/run/postgresql' as the hostname
     argument and put '5432' into the port variable. */
 
-    if (last_slash == NULL || last_dot == NULL)
+    if (!last_slash || !last_dot)
       {
-      *errmsg = string_sprintf("PGSQL invalid filename for socket: %s",
-        server);
+      *errmsg = string_sprintf("PGSQL invalid filename for socket: %s", server);
       *defer_break = TRUE;
       return DEFER;
       }
@@ -211,13 +208,13 @@ if (cn == NULL)
   else
     {
     uschar *p;
-    if ((p = Ustrchr(server, ':')) != NULL)
+    if ((p = Ustrchr(server, ':')))
       {
       *p++ = 0;
       port = p;
       }
 
-    if (Ustrchr(server, '/') != NULL)
+    if (Ustrchr(server, '/'))
       {
       *errmsg = string_sprintf("unexpected slash in pgSQL server hostname: %s",
         server);
@@ -280,37 +277,37 @@ else
 
 /* Run the query */
 
-  pg_result = PQexec(pg_conn, CS query);
-  switch(PQresultStatus(pg_result))
-    {
-    case PGRES_EMPTY_QUERY:
-    case PGRES_COMMAND_OK:
-      /* The command was successful but did not return any data since it was
-      not SELECT but either an INSERT, UPDATE or DELETE statement. Tell the
-      high level code to not cache this query, and clean the current cache for
-      this handle by setting *do_cache zero. */
-
-      result = string_cat(result, US PQcmdTuples(pg_result));
-      *do_cache = 0;
-      DEBUG(D_lookup) debug_printf("PGSQL: command does not return any data "
-	"but was successful. Rows affected: %s\n", result->s);
-      break;
+pg_result = PQexec(pg_conn, CS query);
+switch(PQresultStatus(pg_result))
+  {
+  case PGRES_EMPTY_QUERY:
+  case PGRES_COMMAND_OK:
+    /* The command was successful but did not return any data since it was
+    not SELECT but either an INSERT, UPDATE or DELETE statement. Tell the
+    high level code to not cache this query, and clean the current cache for
+    this handle by setting *do_cache zero. */
+
+    result = string_cat(result, US PQcmdTuples(pg_result));
+    *do_cache = 0;
+    DEBUG(D_lookup) debug_printf("PGSQL: command does not return any data "
+      "but was successful. Rows affected: %s\n", string_from_gstring(result));
+    break;
 
-    case PGRES_TUPLES_OK:
-      break;
+  case PGRES_TUPLES_OK:
+    break;
 
-    default:
-      /* This was the original code:
-      *errmsg = string_sprintf("PGSQL: query failed: %s\n",
-			       PQresultErrorMessage(pg_result));
-      This was suggested by a user:
-      */
-
-      *errmsg = string_sprintf("PGSQL: query failed: %s (%s) (%s)\n",
-                             PQresultErrorMessage(pg_result),
-                             PQresStatus(PQresultStatus(pg_result)), query);
-      goto PGSQL_EXIT;
-    }
+  default:
+    /* This was the original code:
+    *errmsg = string_sprintf("PGSQL: query failed: %s\n",
+			     PQresultErrorMessage(pg_result));
+    This was suggested by a user:
+    */
+
+    *errmsg = string_sprintf("PGSQL: query failed: %s (%s) (%s)\n",
+			   PQresultErrorMessage(pg_result),
+			   PQresStatus(PQresultStatus(pg_result)), query);
+    goto PGSQL_EXIT;
+  }
 
 /* Result is in pg_result. Find the number of fields returned. If this is one,
 we don't add field names to the data. Otherwise we do. If the query did not
@@ -329,7 +326,7 @@ for (i = 0; i < num_tuples; i++)
     result = string_catn(result, US"\n", 1);
 
   if (num_fields == 1)
-    result = string_catn(NULL,
+    result = string_catn(result,
 	US PQgetvalue(pg_result, i, 0), PQgetlength(pg_result, i, 0));
   else
     {
@@ -342,17 +339,13 @@ for (i = 0; i < num_tuples; i++)
     }
   }
 
-/* If result is NULL then no data has been found and so we return FAIL.
-Otherwise, we must terminate the string which has been built; string_cat()
-always leaves enough room for a terminating zero. */
+/* If result is NULL then no data has been found and so we return FAIL. */
 
 if (!result)
   {
   yield = FAIL;
   *errmsg = US"PGSQL: no data found";
   }
-else
-  store_reset(result->s + result->ptr + 1);
 
 /* Get here by goto from various error checks. */
 
@@ -367,6 +360,7 @@ if (pg_result) PQclear(pg_result);
 
 if (result)
   {
+  store_reset(result->s + result->ptr + 1);
   *resultptr = string_from_gstring(result);
   return OK;
   }

commit db98649c6f4fc9ae797effb07302cb4f2dd48690
Author: Jeremy Harris <jgh146exb@wizmail.org>
Date:   Thu Dec 28 20:51:28 2017 +0000

    DKIM: tighter checking while parsing signature headers.  Bug 2217

diff --git a/src/src/pdkim/pdkim.c b/src/src/pdkim/pdkim.c
index 20366a46..b884671d 100644
--- a/src/src/pdkim/pdkim.c
+++ b/src/src/pdkim/pdkim.c
@@ -490,7 +490,12 @@ for (p = raw_hdr; ; p++)
 
     if (c == ';' || c == '\0')
       {
-      if (cur_tag && cur_val)
+      /* We must have both tag and value, and tags must be one char except
+      for the possibility of "bh". */
+
+      if (  cur_tag && cur_val
+	 && (cur_tag->ptr == 1 || *cur_tag->s == 'b')
+	 )
         {
 	(void) string_from_gstring(cur_val);
 	pdkim_strtrim(cur_val);
@@ -500,8 +505,14 @@ for (p = raw_hdr; ; p++)
 	switch (*cur_tag->s)
 	  {
 	  case 'b':
-	    pdkim_decode_base64(cur_val->s,
-			    cur_tag->s[1] == 'h' ? &sig->bodyhash : &sig->sighash);
+	    switch (cur_tag->s[1])
+	      {
+	      case '\0': pdkim_decode_base64(cur_val->s, &sig->sighash); break;
+	      case 'h':  if (cur_tag->ptr == 2)
+			   pdkim_decode_base64(cur_val->s, &sig->bodyhash);
+			 break;
+	      default:   break;
+	      }
 	    break;
 	  case 'v':
 	      /* We only support version 1, and that is currently the

commit 4c35563b96a3f37ea7d215c47f7725d24c1e4a1f
Author: Jeremy Harris <jgh146exb@wizmail.org>
Date:   Thu Dec 28 20:09:05 2017 +0000

    Fix crash associated with dnsdb lookup done from DKIM ACL.  Bug 2215
    
    Broken-by: cc55f4208e

diff --git a/src/src/expand.c b/src/src/expand.c
index e754fbc8..3a63a7c7 100644
--- a/src/src/expand.c
+++ b/src/src/expand.c
@@ -7546,7 +7546,7 @@ DEBUG(D_expand)
   if (expand_string_forcedfail)
     debug_printf_indent(UTF8_UP_RIGHT "failure was forced\n");
   }
-if (resetok_p) *resetok_p = resetok;
+if (resetok_p && !resetok) *resetok_p = FALSE;
 expand_level--;
 return NULL;
 }
@@ -7560,28 +7560,35 @@ Returns:  the expanded string, or NULL if expansion failed; if failure was
           due to a lookup deferring, search_find_defer will be TRUE
 */
 
-uschar *
-expand_string(uschar *string)
+const uschar *
+expand_cstring(const uschar * string)
 {
-search_find_defer = FALSE;
-malformed_header = FALSE;
-return (Ustrpbrk(string, "$\\") == NULL)? string :
-  expand_string_internal(string, FALSE, NULL, FALSE, TRUE, NULL);
+if (Ustrpbrk(string, "$\\") != NULL)
+  {
+  int old_pool = store_pool;
+  uschar * s;
+
+  search_find_defer = FALSE;
+  malformed_header = FALSE;
+  store_pool = POOL_MAIN;
+    s = expand_string_internal(string, FALSE, NULL, FALSE, TRUE, NULL);
+  store_pool = old_pool;
+  return s;
+  }
+return string;
 }
 
 
-
-const uschar *
-expand_cstring(const uschar *string)
+uschar *
+expand_string(uschar * string)
 {
-search_find_defer = FALSE;
-malformed_header = FALSE;
-return (Ustrpbrk(string, "$\\") == NULL)? string :
-  expand_string_internal(string, FALSE, NULL, FALSE, TRUE, NULL);
+return US expand_cstring(CUS string);
 }
 
 
 
+
+
 /*************************************************
 *              Expand and copy                   *
 *************************************************/
@@ -7812,8 +7819,6 @@ return (  (  Ustrstr(s, "failed to expand") != NULL
 * Error-checking for testsuite                   *
 *************************************************/
 typedef struct {
-  const char *	filename;
-  int		linenumber;
   uschar * 	region_start;
   uschar *	region_end;
   const uschar *var_name;
@@ -7834,7 +7839,8 @@ if (var_data >= e->region_start  &&  var_data < e->region_end)
 void
 assert_no_variables(void * ptr, int len, const char * filename, int linenumber)
 {
-err_ctx e = {filename, linenumber, ptr, US ptr + len, NULL };
+err_ctx e = { .region_start = ptr, .region_end = US ptr + len,
+	      .var_name = NULL, .var_data = NULL };
 int i;
 var_entry * v;
 
@@ -7855,10 +7861,16 @@ for (v = var_table; v < var_table + var_table_size; v++)
   if (v->type == vtype_stringptr)
     assert_variable_notin(US v->name, *(USS v->value), &e);
 
+/* check dns and address trees */
+tree_walk(tree_dns_fails,     assert_variable_notin, &e);
+tree_walk(tree_duplicates,    assert_variable_notin, &e);
+tree_walk(tree_nonrecipients, assert_variable_notin, &e);
+tree_walk(tree_unusable,      assert_variable_notin, &e);
+
 if (e.var_name)
   log_write(0, LOG_MAIN|LOG_PANIC_DIE,
     "live variable '%s' destroyed by reset_store at %s:%d\n- value '%.64s'",
-    e.var_name, e.filename, e.linenumber, e.var_data);
+    e.var_name, filename, linenumber, e.var_data);
 }
 
 
diff --git a/src/src/queue.c b/src/src/queue.c
index f9468119..09a149e9 100644
--- a/src/src/queue.c
+++ b/src/src/queue.c
@@ -699,6 +699,7 @@ for (i = queue_run_in_order ? -1 : 0;
       }
     }                                  /* End loop for list of messages */
 
+  tree_nonrecipients = NULL;
   store_reset(reset_point1);           /* Scavenge list of messages */
 
   /* If this was the first time through for random order processing, and

commit 70ba304da5933bcf3a9afb040228b2a37b3beefe
Author: Jeremy Harris <jgh146exb@wizmail.org>
Date:   Wed Dec 27 23:32:02 2017 +0000

    Fix issue with continued-connections when the DNS shifts unreliably

diff --git a/src/src/transports/smtp.c b/src/src/transports/smtp.c
index 61e8d8a4..5d351dd7 100644
--- a/src/src/transports/smtp.c
+++ b/src/src/transports/smtp.c
@@ -3924,7 +3924,9 @@ for (cutoff_retry = 0;
   {
   host_item *nexthost = NULL;
   int unexpired_hosts_tried = 0;
+  BOOL continue_host_tried = FALSE;
 
+retry_non_continued:
   for (host = hostlist;
           host
        && unexpired_hosts_tried < ob->hosts_max_try
@@ -4057,14 +4059,16 @@ for (cutoff_retry = 0;
     result of the lookup. Set expired FALSE, to save the outer loop executing
     twice. */
 
-    if (  continue_hostname
-       && (  Ustrcmp(continue_hostname, host->name) != 0
-          || Ustrcmp(continue_host_address, host->address) != 0
-       )  )
-      {
-      expired = FALSE;
-      continue;      /* With next host */
-      }
+    if (continue_hostname)
+      if (  Ustrcmp(continue_hostname, host->name) != 0
+         || Ustrcmp(continue_host_address, host->address) != 0
+	 )
+	{
+	expired = FALSE;
+	continue;      /* With next host */
+	}
+      else
+	continue_host_tried = TRUE;
 
     /* Reset the default next host in case a multihomed host whose addresses
     are not looked up till just above added to the host list. */
@@ -4522,6 +4526,32 @@ for (cutoff_retry = 0;
       }
     }   /* End of loop for trying multiple hosts. */
 
+  /* If we failed to find a matching host in the list, for an already-open
+  connection, just close it and start over with the list.  This can happen
+  for routing that changes from run to run, or big multi-IP sites with
+  round-robin DNS. */
+
+  if (continue_hostname && !continue_host_tried)
+    {
+    int fd = cutthrough.fd >= 0 ? cutthrough.fd : 0;
+
+    DEBUG(D_transport) debug_printf("no hosts match already-open connection\n");
+#ifdef SUPPORT_TLS
+    if (tls_out.active == fd)
+      {
+      (void) tls_write(FALSE, US"QUIT\r\n", 6, FALSE);
+      tls_close(FALSE, TRUE);
+      }
+    else
+#else
+      (void) write(fd, US"QUIT\r\n", 6);
+#endif
+    (void) close(fd);
+    cutthrough.fd = -1;
+    continue_hostname = NULL;
+    goto retry_non_continued;
+    }
+
   /* This is the end of the loop that repeats iff expired is TRUE and
   ob->delay_after_cutoff is FALSE. The second time round we will
   try those hosts that haven't been tried since the message arrived. */

commit 13ca5f2522f4165a0aa6f1b7143a1a1cfa37bf5a
Author: Jeremy Harris <jgh146exb@wizmail.org>
Date:   Sat Dec 30 13:55:54 2017 +0000

    MIME ACL: fix SMTP response for non-accept result of the ACL.  Bug 2214.
    
    As far as I can see this was broken back in 2013, f4c1088 for 4.82

diff --git a/src/src/receive.c b/src/src/receive.c
index 1f1954c5..3a550cfe 100644
--- a/src/src/receive.c
+++ b/src/src/receive.c
@@ -1348,7 +1348,7 @@ run_mime_acl(uschar *acl, BOOL *smtp_yield_ptr, uschar **smtp_reply_ptr,
   uschar **blackholed_by_ptr)
 {
 FILE *mbox_file;
-uschar rfc822_file_path[2048];
+uschar * rfc822_file_path = NULL;
 unsigned long mbox_size;
 header_line *my_headerlist;
 uschar *user_msg, *log_msg;
@@ -1356,8 +1356,6 @@ int mime_part_count_buffer = -1;
 uschar * mbox_filename;
 int rc = OK;
 
-memset(CS rfc822_file_path,0,2048);
-
 /* check if it is a MIME message */
 
 for (my_headerlist = header_list; my_headerlist; my_headerlist = my_headerlist->next)
@@ -1397,7 +1395,7 @@ mime_part_count = -1;
 rc = mime_acl_check(acl, mbox_file, NULL, &user_msg, &log_msg);
 (void)fclose(mbox_file);
 
-if (Ustrlen(rfc822_file_path) > 0)
+if (rfc822_file_path)
   {
   mime_part_count = mime_part_count_buffer;
 
@@ -1405,36 +1403,31 @@ if (Ustrlen(rfc822_file_path) > 0)
     {
     log_write(0, LOG_PANIC,
          "acl_smtp_mime: can't unlink RFC822 spool file, skipping.");
-      goto END_MIME_ACL;
+    goto END_MIME_ACL;
     }
+  rfc822_file_path = NULL;
   }
 
 /* check if we must check any message/rfc822 attachments */
 if (rc == OK)
   {
-  uschar * scandir;
+  uschar * scandir = string_copyn(mbox_filename,
+	      Ustrrchr(mbox_filename, '/') - mbox_filename);
   struct dirent * entry;
   DIR * tempdir;
 
-  scandir = string_copyn(mbox_filename, Ustrrchr(mbox_filename, '/') - mbox_filename);
-
-  tempdir = opendir(CS scandir);
-  for (;;)
-    {
-    if (!(entry = readdir(tempdir)))
-      break;
+  for (tempdir = opendir(CS scandir); entry = readdir(tempdir); )
     if (strncmpic(US entry->d_name, US"__rfc822_", 9) == 0)
       {
-      (void) string_format(rfc822_file_path, sizeof(rfc822_file_path),
-	"%s/%s", scandir, entry->d_name);
-      DEBUG(D_receive) debug_printf("RFC822 attachment detected: running MIME ACL for '%s'\n",
-	rfc822_file_path);
+      rfc822_file_path = string_sprintf("%s/%s", scandir, entry->d_name);
+      DEBUG(D_receive)
+	debug_printf("RFC822 attachment detected: running MIME ACL for '%s'\n",
+	  rfc822_file_path);
       break;
       }
-    }
   closedir(tempdir);
 
-  if (entry)
+  if (rfc822_file_path)
     {
     if ((mbox_file = Ufopen(rfc822_file_path, "rb")))
       {
@@ -1463,10 +1456,10 @@ else if (rc != OK)
 #ifdef EXPERIMENTAL_DCC
   dcc_ok = 0;
 #endif
-  if (  smtp_input
-     && smtp_handle_acl_fail(ACL_WHERE_MIME, rc, user_msg, log_msg) != 0)
+  if (smtp_input)
     {
-    *smtp_yield_ptr = FALSE;    /* No more messages after dropped connection */
+    if (smtp_handle_acl_fail(ACL_WHERE_MIME, rc, user_msg, log_msg) != 0)
+      *smtp_yield_ptr = FALSE;  /* No more messages after dropped connection */
     *smtp_reply_ptr = US"";     /* Indicate reply already sent */
     }
   message_id[0] = 0;            /* Indicate no message accepted */
@@ -3481,9 +3474,10 @@ else
 #endif /* DISABLE_DKIM */
 
 #ifdef WITH_CONTENT_SCAN
-    if (recipients_count > 0 &&
-        acl_smtp_mime != NULL &&
-        !run_mime_acl(acl_smtp_mime, &smtp_yield, &smtp_reply, &blackholed_by))
+    if (  recipients_count > 0
+       && acl_smtp_mime
+       && !run_mime_acl(acl_smtp_mime, &smtp_yield, &smtp_reply, &blackholed_by)
+       )
       goto TIDYUP;
 #endif /* WITH_CONTENT_SCAN */
 
@@ -3603,9 +3597,10 @@ else
     {
 
 #ifdef WITH_CONTENT_SCAN
-    if (acl_not_smtp_mime != NULL &&
-        !run_mime_acl(acl_not_smtp_mime, &smtp_yield, &smtp_reply,
-          &blackholed_by))
+    if (  acl_not_smtp_mime
+       && !run_mime_acl(acl_not_smtp_mime, &smtp_yield, &smtp_reply,
+          &blackholed_by)
+       )
       goto TIDYUP;
 #endif /* WITH_CONTENT_SCAN */
 
diff --git a/src/src/smtp_in.c b/src/src/smtp_in.c
index 00e9d41a..2603da25 100644
--- a/src/src/smtp_in.c
+++ b/src/src/smtp_in.c
@@ -3142,7 +3142,7 @@ return;
 
 /* This function is called when acl_check() fails. As well as calls from within
 this module, it is called from receive.c for an ACL after DATA. It sorts out
-logging the incident, and sets up the error response. A message containing
+logging the incident, and sends the error response. A message containing
 newlines is turned into a multiline SMTP response, but for logging, only the
 first line is used.
 

commit 20913a313f33fd8ae2dea9f379552975867c6394
Author: Jeremy Harris <jgh146exb@wizmail.org>
Date:   Sun Jan 7 15:03:25 2018 +0000

    DKIM: permit dkim_private_key to override dkim_strict on signing.  Bug 2220

diff --git a/src/src/dkim.c b/src/src/dkim.c
index 9731a63d..18427fe9 100644
--- a/src/src/dkim.c
+++ b/src/src/dkim.c
@@ -531,8 +531,12 @@ switch (what)
 }
 
 
-/* Generate signatures for the given file, returning a string.
+/* Generate signatures for the given file.
 If a prefix is given, prepend it to the file for the calculations.
+
+Return:
+  NULL:		error; error string written
+  string: 	signature header(s), or a zero-length string (not an error)
 */
 
 gstring *
@@ -702,9 +706,15 @@ while ((dkim_signing_domain = string_nextinlist(&dkim_domain, &sep, NULL, 0)))
       }
     }
   }
+if (!ctx.sig)
+  {
+  DEBUG(D_transport) debug_printf("DKIM: no viable signatures to use\n");
+  sigbuf = string_get(1);	/* return a zero-len string */
+  goto CLEANUP;
+  }
 
-if (prefix)
-  pdkim_feed(&ctx, prefix, Ustrlen(prefix));
+if (prefix && (pdkim_feed(&ctx, prefix, Ustrlen(prefix))) != PDKIM_OK)
+  goto pk_bad;
 
 if (lseek(fd, off, SEEK_SET) < 0)
   sread = -1;
@@ -729,9 +739,8 @@ if ((pdkim_rc = pdkim_feed_finish(&ctx, &sig, errstr)) != PDKIM_OK)
 for (sigbuf = NULL; sig; sig = sig->next)
   sigbuf = string_append(sigbuf, 2, US sig->signature_header, US"\r\n");
 
-(void) string_from_gstring(sigbuf);
-
 CLEANUP:
+  (void) string_from_gstring(sigbuf);
   store_pool = old_pool;
   errno = save_errno;
   return sigbuf;

commit 0a4484f261a51cfe0fc6ece78f47f9507a34e2ba
Author: Jeremy Harris <jgh146exb@wizmail.org>
Date:   Sat Jan 13 18:11:21 2018 +0000

    Lookups: fix mysql lookup returns for no-data "queries",
    when the number of rows affected is returned.  Bug 2223
    
    Broken-by: acec9514b1
    
    Also enhance the testsuite mysql testcase to be standalone and move to standard-run set
    and add a specific testcase for this bug.
    
    Testcase working on Fedora at least - we'll see what happens on other platforms
    where executable locaation may vary.

diff --git a/src/src/lookups/mysql.c b/src/src/lookups/mysql.c
index ff1ef832..8d101b27 100644
--- a/src/src/lookups/mysql.c
+++ b/src/src/lookups/mysql.c
@@ -281,7 +281,7 @@ we return the number of rows affected by the command. In this event, we do NOT
 want to cache the result; also the whole cache for the handle must be cleaned
 up. Setting do_cache zero requests this. */
 
-if ((mysql_result = mysql_use_result(mysql_handle)) == NULL)
+if (!(mysql_result = mysql_use_result(mysql_handle)))
   {
   if ( mysql_field_count(mysql_handle) == 0 )
     {
@@ -314,34 +314,32 @@ while ((mysql_row_data = mysql_fetch_row(mysql_result)))
   if (result)
     result = string_catn(result, US"\n", 1);
 
-  if (num_fields == 1)
-    {
-    if (mysql_row_data[0] != NULL)    /* NULL value yields nothing */
-      {
-      result = string_catn(result, US mysql_row_data[0],
-        lengths[0]);
-      (void) string_from_gstring(result);
-      }
-    }
+  if (num_fields != 1)
+    for (i = 0; i < num_fields; i++)
+      result = lf_quote(US fields[i].name, US mysql_row_data[i], lengths[i],
+			result);
 
-  else for (i = 0; i < num_fields; i++)
-    result = lf_quote(US fields[i].name, US mysql_row_data[i], lengths[i], result);
+  else if (mysql_row_data[0] != NULL)    /* NULL value yields nothing */
+      result = string_catn(result, US mysql_row_data[0], lengths[0]);
   }
 
 /* more results? -1 = no, >0 = error, 0 = yes (keep looping)
    This is needed because of the CLIENT_MULTI_RESULTS on mysql_real_connect(),
    we don't expect any more results. */
 
-while((i = mysql_next_result(mysql_handle)) >= 0) {
-   if(i == 0) {   /* Just ignore more results */
-     DEBUG(D_lookup) debug_printf("MYSQL: got unexpected more results\n");
-     continue;
-   }
+while((i = mysql_next_result(mysql_handle)) >= 0)
+  {
+  if(i == 0)	/* Just ignore more results */
+    {
+    DEBUG(D_lookup) debug_printf("MYSQL: got unexpected more results\n");
+    continue;
+    }
 
-   *errmsg = string_sprintf("MYSQL: lookup result error when checking for more results: %s\n",
-       mysql_error(mysql_handle));
-   goto MYSQL_EXIT;
-}
+  *errmsg = string_sprintf(
+	"MYSQL: lookup result error when checking for more results: %s\n",
+	mysql_error(mysql_handle));
+  goto MYSQL_EXIT;
+  }
 
 /* If result is NULL then no data has been found and so we return FAIL.
 Otherwise, we must terminate the string which has been built; string_cat()
@@ -352,11 +350,6 @@ if (!result)
   yield = FAIL;
   *errmsg = US"MYSQL: no data found";
   }
-else
-  {
-  (void) string_from_gstring(result);
-  store_reset(result->s + result->ptr + 1);
-  }
 
 /* Get here by goto from various error checks and from the case where no data
 was read (e.g. an update query). */
@@ -372,7 +365,8 @@ if (mysql_result) mysql_free_result(mysql_result);
 
 if (result)
   {
-  *resultptr = result->s;
+  *resultptr = string_from_gstring(result);
+  store_reset(result->s + (result->size = result->ptr + 1));
   return OK;
   }
 else

commit d2fe8622a815e36bf66b04eb772d5ec0ba8e13af
Author: Heiko Schlittermann (HS12-RIPE) <hs@schlittermann.de>
Date:   Tue Jan 16 16:06:24 2018 +0100

    Fix %D string expansion to not use millisec
    
    log_selector +millisec should not change the expansion of %D
    (used in log_file_path and maybe other places)

diff --git a/src/src/tod.c b/src/src/tod.c
index 55933dc8..290dc6dd 100644
--- a/src/src/tod.c
+++ b/src/src/tod.c
@@ -54,7 +54,6 @@ tod_stamp(int type)
 {
 struct timeval now;
 struct tm * t;
-int off = 0;
 
 gettimeofday(&now, NULL);
 
@@ -90,9 +89,17 @@ t = timestamps_utc ? gmtime(&now.tv_sec) : localtime(&now.tv_sec);
 switch(type)
   {
   case tod_log_bare:          /* Format used in logging without timezone */
-    off = sprintf(CS timebuf, "%04d-%02d-%02d %02d:%02d:%02d",
+#ifndef COMPILE_UTILITY
+    if (LOGGING(millisec))
+      sprintf(CS timebuf, "%04d-%02d-%02d %02d:%02d:%02d.%03d",
+      1900 + t->tm_year, 1 + t->tm_mon, t->tm_mday,
+      t->tm_hour, t->tm_min, t->tm_sec, (int)(now.tv_usec/1000));
+    else
+#endif
+      sprintf(CS timebuf, "%04d-%02d-%02d %02d:%02d:%02d",
       1900 + t->tm_year, 1 + t->tm_mon, t->tm_mday,
       t->tm_hour, t->tm_min, t->tm_sec);
+
     break;
 
     /* Format used as suffix of log file name when 'log_datestamp' is active. For
@@ -101,19 +108,19 @@ switch(type)
 #ifdef TESTING_LOG_DATESTAMP
   case tod_log_datestamp_daily:
   case tod_log_datestamp_monthly:
-    off = sprintf(CS timebuf, "%04d%02d%02d%02d%02d",
+    sprintf(CS timebuf, "%04d%02d%02d%02d%02d",
       1900 + t->tm_year, 1 + t->tm_mon, t->tm_mday, t->tm_hour, t->tm_min);
     break;
 
 #else
   case tod_log_datestamp_daily:
-    off = sprintf(CS timebuf, "%04d%02d%02d",
+    sprintf(CS timebuf, "%04d%02d%02d",
       1900 + t->tm_year, 1 + t->tm_mon, t->tm_mday);
     break;
 
   case tod_log_datestamp_monthly:
 #ifndef COMPILE_UTILITY
-    off = sprintf(CS timebuf, "%04d%02d",
+    sprintf(CS timebuf, "%04d%02d",
       1900 + t->tm_year, 1 + t->tm_mon);
 #endif
     break;
@@ -212,13 +219,6 @@ switch(type)
     break;
   }
 
-#ifndef COMPILE_UTILITY
-if (LOGGING(millisec) && off > 0)
-  (void) sprintf(CS timebuf + off, ".%03d", (int)(now.tv_usec/1000));
-#else
-off = off;	/* Compiler quietening */
-#endif
-
 return timebuf;
 }
 

commit 0b187e3ccb921c482601ee4a9e13c0caa2710794
Author: Jeremy Harris <jgh146exb@wizmail.org>
Date:   Sun Jan 14 18:40:50 2018 +0000

    DKIM: DNS records having no v= tag are acceptable.  Bug 2207
    
    Broken-by c73a4d073e

diff --git a/src/src/dkim.c b/src/src/dkim.c
index 18427fe9..b4aecbc3 100644
--- a/src/src/dkim.c
+++ b/src/src/dkim.c
@@ -75,7 +75,8 @@ for (rr = dns_next_rr(&dnsa, &dnss, RESET_ANSWERS);
       }
 
     /* check if this looks like a DKIM record */
-    if (strncasecmp(answer, "v=dkim", 6) != 0) continue;
+    if (strncmp(answer, "v=", 2) == 0 && strncasecmp(answer, "v=dkim", 6) != 0)
+      continue;
     return PDKIM_OK;
     }
 
diff --git a/src/src/pdkim/pdkim.c b/src/src/pdkim/pdkim.c
index b884671d..186258a6 100644
--- a/src/src/pdkim/pdkim.c
+++ b/src/src/pdkim/pdkim.c
@@ -654,7 +654,8 @@ while ((ele = string_nextinlist(&raw_record, &sep, NULL, 0)))
   }
 
 /* Set fallback defaults */
-if (!pub->version    ) pub->version     = string_copy(PDKIM_PUB_RECORD_VERSION);
+if (!pub->version)
+  pub->version = string_copy(PDKIM_PUB_RECORD_VERSION);
 else if (Ustrcmp(pub->version, PDKIM_PUB_RECORD_VERSION) != 0)
   {
   DEBUG(D_acl) debug_printf(" Bad v= field\n");

commit 4360153635f43c3fffee39f6767ff17b11f38c55
Author: Jeremy Harris <jgh146exb@wizmail.org>
Date:   Thu Jan 25 21:27:00 2018 +0000

    Cutthrough: fix multi-message initiating connections.  Bug 2230

diff --git a/src/src/acl.c b/src/src/acl.c
index 739cd91a..750fd2da 100644
--- a/src/src/acl.c
+++ b/src/src/acl.c
@@ -3293,6 +3293,8 @@ for (; cb != NULL; cb = cb->next)
 		p = pp;
 		}
 	      }
+	    else
+	      DEBUG(D_acl) debug_printf(" cutthrough request ignored for nonfirst rcpt\n");
 	    break;
 	    }
 	  *log_msgptr = string_sprintf("\"control=%s\" on %s item",
@@ -4422,22 +4424,29 @@ switch (where)
     else if (  rc == OK
 	    && cutthrough.delivery
 	    && rcpt_count > cutthrough.nrcpt
-	    && (rc = open_cutthrough_connection(addr)) == DEFER
 	    )
-      if (cutthrough.defer_pass)
-	{
-	uschar * s = addr->message;
-	/* Horrid kludge to recover target's SMTP message */
-	while (*s) s++;
-	do --s; while (!isdigit(*s));
-	if (*--s && isdigit(*s) && *--s && isdigit(*s)) *user_msgptr = s;
-	acl_temp_details = TRUE;
-	}
-      else
-	{
-	HDEBUG(D_acl) debug_printf_indent("cutthrough defer; will spool\n");
-	rc = OK;
-	}
+      {
+      if ((rc = open_cutthrough_connection(addr)) == DEFER)
+	if (cutthrough.defer_pass)
+	  {
+	  uschar * s = addr->message;
+	  /* Horrid kludge to recover target's SMTP message */
+	  while (*s) s++;
+	  do --s; while (!isdigit(*s));
+	  if (*--s && isdigit(*s) && *--s && isdigit(*s)) *user_msgptr = s;
+	  acl_temp_details = TRUE;
+	  }
+	else
+	  {
+	  HDEBUG(D_acl) debug_printf_indent("cutthrough defer; will spool\n");
+	  rc = OK;
+	  }
+      }
+    else HDEBUG(D_acl) if (cutthrough.delivery)
+      if (rcpt_count <= cutthrough.nrcpt)
+	debug_printf_indent("ignore cutthrough request; nonfirst message\n");
+      else if (rc != OK)
+	debug_printf_indent("ignore cutthrough request; ACL did not accept\n");
     break;
 
   case ACL_WHERE_PREDATA:
diff --git a/src/src/verify.c b/src/src/verify.c
index b957c709..0f0430b5 100644
--- a/src/src/verify.c
+++ b/src/src/verify.c
@@ -1380,6 +1380,7 @@ if(fd >= 0)
   _cutthrough_puts(US"QUIT\r\n", 6);	/* avoid recursion */
   _cutthrough_flush_send();
   cutthrough.fd = -1;			/* avoid recursion via read timeout */
+  cutthrough.nrcpt = 0;			/* permit re-cutthrough on subsequent message */
 
   /* Wait a short time for response, and discard it */
   cutthrough_response(fd, '2', NULL, 1);

commit baabef80f200a5e028a6f1487276a441caa40255
Author: Jeremy Harris <jgh146exb@wizmail.org>
Date:   Fri Jan 26 18:40:41 2018 +0000

    Cutthrough: fix for port-number defined by router.  Bug 2229

diff --git a/src/src/verify.c b/src/src/verify.c
index 0f0430b5..5486ca23 100644
--- a/src/src/verify.c
+++ b/src/src/verify.c
@@ -388,18 +388,21 @@ if (addr->transport == cutthrough.addr.transport)
 
       host_af = Ustrchr(host->address, ':') ? AF_INET6 : AF_INET;
 
-      if (!smtp_get_interface(tf->interface, host_af, addr, &interface,
-	      US"callout") ||
-	  !smtp_get_port(tf->port, addr, &port, US"callout"))
+      if (  !smtp_get_interface(tf->interface, host_af, addr, &interface,
+	      US"callout")
+	 || !smtp_get_port(tf->port, addr, &port, US"callout")
+	 )
 	log_write(0, LOG_MAIN|LOG_PANIC, "<%s>: %s", addr->address,
 	  addr->message);
 
+      smtp_port_for_connect(host, port);
+
       if (  (  interface == cutthrough.interface
 	    || (  interface
 	       && cutthrough.interface
 	       && Ustrcmp(interface, cutthrough.interface) == 0
 	    )  )
-	 && port == cutthrough.host.port
+	 && host->port == cutthrough.host.port
 	 )
 	{
 	uschar * resp = NULL;

commit 4b8ff36089f93f3315b94da2bdb68285340790a7
Author: Jeremy Harris <jgh146exb@wizmail.org>
Date:   Sat Jan 27 15:03:01 2018 +0000

    GnuTLS: fix to ignore timeout on unrelated callout connection.  Bug 2174

diff --git a/src/src/tls-gnu.c b/src/src/tls-gnu.c
index 527ad28b..fc3aba59 100644
--- a/src/src/tls-gnu.c
+++ b/src/src/tls-gnu.c
@@ -2105,10 +2105,8 @@ DEBUG(D_tls) debug_printf("about to gnutls_handshake\n");
 sigalrm_seen = FALSE;
 alarm(ob->command_timeout);
 do
-  {
   rc = gnutls_handshake(state->session);
-  } while ((rc == GNUTLS_E_AGAIN) ||
-      (rc == GNUTLS_E_INTERRUPTED && !sigalrm_seen));
+while (rc == GNUTLS_E_AGAIN || rc == GNUTLS_E_INTERRUPTED && !sigalrm_seen);
 alarm(0);
 
 if (rc != GNUTLS_E_SUCCESS)
@@ -2225,6 +2223,7 @@ ssize_t inbytes;
 DEBUG(D_tls) debug_printf("Calling gnutls_record_recv(%p, %p, %u)\n",
   state->session, state->xfer_buffer, ssl_xfer_buffer_size);
 
+sigalrm_seen = FALSE;
 if (smtp_receive_timeout > 0) alarm(smtp_receive_timeout);
 inbytes = gnutls_record_recv(state->session, state->xfer_buffer,
   MIN(ssl_xfer_buffer_size, lim));

commit 2dfd20fe244da439b1f6becb4e29c8cb83e2d399
Author: Heiko Schlittermann (HS12-RIPE) <hs@schlittermann.de>
Date:   Sat Jan 27 22:58:03 2018 +0100

    Build: .git may be a file when this repo is a submodule

diff --git a/src/scripts/reversion b/src/scripts/reversion
index c82d9c0a..00fc028b 100755
--- a/src/scripts/reversion
+++ b/src/scripts/reversion
@@ -37,7 +37,7 @@ fi
 
 # If this tree is a git working directory, use that to get version information.
 
-if [ -d ../../.git ] || [ "$1" = "release" ]
+if [ -d ../../.git ] || [ -f ../../.git ] || [ "$1" = "release" ]
 then
 	# Modify the output of git describe into separate parts for
 	# the name "exim" and the release and variant versions.

commit 20532ba65b0a952e5e471a502be79767efcae8d0
Author: Jeremy Harris <jgh146exb@wizmail.org>
Date:   Sun Jan 28 20:32:28 2018 +0000

    Debugging: fix potential null-derefs in DSN debug_printfs

diff --git a/src/src/deliver.c b/src/src/deliver.c
index de552f4c..9f9990be 100644
--- a/src/src/deliver.c
+++ b/src/src/deliver.c
@@ -6152,7 +6152,7 @@ if (process_recipients != RECIP_IGNORE)
       new->dsn_flags = r->dsn_flags & rf_dsnflags;
       new->dsn_orcpt = r->orcpt;
       DEBUG(D_deliver) debug_printf("DSN: set orcpt: %s  flags: %d\n",
-	new->dsn_orcpt, new->dsn_flags);
+	new->dsn_orcpt ? new->dsn_orcpt : US"", new->dsn_flags);
 
       switch (process_recipients)
         {
@@ -7221,11 +7221,12 @@ for (addr_dsntmp = addr_succeed; addr_dsntmp; addr_dsntmp = addr_dsntmp->next)
       "DSN: envid: %s  ret: %d\n"
       "DSN: Final recipient: %s\n"
       "DSN: Remote SMTP server supports DSN: %d\n",
-      addr_dsntmp->router->name,
+      addr_dsntmp->router ? addr_dsntmp->router->name : US"(unknown)",
       addr_dsntmp->address,
       sender_address,
-      addr_dsntmp->dsn_orcpt, addr_dsntmp->dsn_flags,
-      dsn_envid, dsn_ret,
+      addr_dsntmp->dsn_orcpt ? addr_dsntmp->dsn_orcpt : US"NULL",
+      addr_dsntmp->dsn_flags,
+      dsn_envid ? dsn_envid : US"NULL", dsn_ret,
       addr_dsntmp->address,
       addr_dsntmp->dsn_aware
       );

commit d3c2dcc991b7f267ad56f0294827febff3d6688d
Author: Heiko Schlittermann (HS12-RIPE) <hs@schlittermann.de>
Date:   Thu Feb 1 22:41:13 2018 +0100

    Add release-process/micro-release script

diff --git a/release-process/micro-release b/release-process/micro-release
new file mode 100755
index 00000000..f0975130
--- /dev/null
+++ b/release-process/micro-release
@@ -0,0 +1,146 @@
+#!/usr/bin/perl
+
+use v5.10.1;
+use strict;
+use warnings;
+use feature 'say';
+use Getopt::Long;
+use File::Temp qw(tempfile);
+use Pod::Usage;
+
+my $BRANCH_PATTERN = qr/^
+    (?<base>(?<name>exim)-
+    (?<version>
+    (?<major>\d+)
+    _(?<minor>\d+)
+    )(?:_(?<security>\d+))?
+    )\+fixes/x;
+
+sub trim { return join '', shift =~ /^\s*(.*?)\s*$/; }
+
+my %o = (edit => 1, dotted => 1, force => 0, auto => 0);
+
+GetOptions(
+    \%o,
+    'edit!',
+    'dotted!',
+    'force!', 'auto!',
+    'help|h' => sub { pod2usage(-verbose => 1, -exit => 0) },
+    'man|m'  => sub {
+        pod2usage(
+            -verbose   => 2,
+            -exit      => 0,
+            -noperldoc => system('perldoc -V >/dev/null 2>&1')
+        );
+    },
+) or pod2usage;
+
+my $branch = trim `git rev-parse --abbrev-ref HEAD`;    # exim-4_90+fixes
+die "$0: Branch `$branch` does not match `$BRANCH_PATTERN`"
+  unless "$branch" =~ /$BRANCH_PATTERN/;
+
+my $name = $+{name};
+my $base = $+{base};    # exim-4_90 (branch base)
+
+my $release_legacy = $+{version} . ($+{security} // '_0');    # 4.90_0
+my $release_dotted = join '.', $+{major}, $+{minor}, $+{security} // 0; # 4.90.0
+
+my $distance = trim `git rev-list --count $base..`;
+
+my $micro_legacy = "${release_legacy}_$distance";    # 4_90_0_20
+my $micro_dotted = "${release_dotted}.$distance";    # 4.90.0.20
+
+my $tag = "$name-" . ($o{dotted} ? $micro_dotted : $micro_legacy);
+
+# my $commit = trim `git show --no-patch --pretty=format:%h`;
+
+my ($fh, $filename) = tempfile();
+print $fh <<XXX;
+Exim fixup release $micro_dotted
+# Enter a one line tag description. The above is the sensible default.
+XXX
+close $fh;
+
+system(
+      defined $ENV{VISUAL} ? $ENV{VISUAL}
+    : defined $ENV{EDITOR} ? $ENV{EDITOR}
+    :                        'vi' => $filename
+  ) == 0
+  or exit $?
+  if $o{edit};
+
+if (not $o{auto}) {
+    print "Commit tag `$tag` to local repository? [Y/n] ";
+    exit 0 unless <STDIN> =~ /^y(?:es)?|$/i;
+}
+
+system(
+    git => 'tag',
+    '--sign', ($o{force} ? '--force' : ()),
+    -a => $tag,
+    -F => $filename,
+  ) == 0
+  or exit $?;
+
+exec git => 'tag',
+  '--verify'    => $tag;
+
+__END__
+
+=head1 NAME
+
+ micro-release - tag the current commit on a +fixes branch
+
+=head1 SYNOPSIS
+
+ micro-release [--[no]-edit] [--[no]-dotted] [--force] [TAG]
+
+=head1 DESCRIPTION
+
+This tools is for internal use only. It tags the current commit
+of a F<+fixes> branch as a "micro release".
+
+The tag is taken from the command line parameter I<TAG> or is calculated from the
+branch name and the distance to the release tag. The branch name needs
+to match the branch tag.
+
+    branch tag:    exim-4_90
+    branch name:   exim-4_90+fixes
+    resulting tag: exim-4.90.0.<distance>   (dotted version)
+                   exim-4_90_0_<distance>   (classic version)
+
+    branch tag:    exim-4_90_1   (security release)
+    branch name:   exim-4_90_1+fixes
+    resulting tag: exim-4.90.1.<distance>   (dotted version)
+                   exim-4_90_1_<distance>   (classic version)
+
+The tag will be GPG signed.
+
+=head1 OPTIONS
+
+=over
+
+=item B<--[no]-edit>
+
+Spawn or do not spawn an editor. The default message will be "Exim
+<tagname> fixup release".
+
+=item B<--dotted>
+
+Generate a dotted tag name. (default: on)
+
+=item B<--force>
+
+(Re)Move an existing tag name. Use with care!
+
+=item B<-h>|B<--help>
+
+Short help.
+
+=item B<-m>|B<--man>
+
+The manual page.
+
+=back
+
+=cut

commit 9227485d83bbff9bcb4cb54d8f1d3a225b46f06c
Author: Heiko Schlittermann (HS12-RIPE) <hs@schlittermann.de>
Date:   Sat Feb 3 17:05:09 2018 +0100

    Small fixups for micro-release (now: tag-fixes-release) script

diff --git a/release-process/micro-release b/release-process/scripts/tag-fixes-release
similarity index 77%
rename from release-process/micro-release
rename to release-process/scripts/tag-fixes-release
index f0975130..8d0beee0 100755
--- a/release-process/micro-release
+++ b/release-process/scripts/tag-fixes-release
@@ -52,7 +52,11 @@ my $micro_dotted = "${release_dotted}.$distance";    # 4.90.0.20
 
 my $tag = "$name-" . ($o{dotted} ? $micro_dotted : $micro_legacy);
 
-# my $commit = trim `git show --no-patch --pretty=format:%h`;
+# check if the tag already exists
+if (not $o{force}) {
+    `git rev-parse --quiet --verify '$tag'`;         # ignore stdout
+    die "$0: tag '$tag' exists already\n" if $? == 0;
+}
 
 my ($fh, $filename) = tempfile();
 print $fh <<XXX;
@@ -66,12 +70,12 @@ system(
     : defined $ENV{EDITOR} ? $ENV{EDITOR}
     :                        'vi' => $filename
   ) == 0
-  or exit $?
+  or exit $? >> 8
   if $o{edit};
 
 if (not $o{auto}) {
     print "Commit tag `$tag` to local repository? [Y/n] ";
-    exit 0 unless <STDIN> =~ /^y(?:es)?|$/i;
+    exit 0 unless <STDIN> =~ /^(:?y(?:es)?|)$/i;
 }
 
 system(
@@ -80,29 +84,33 @@ system(
     -a => $tag,
     -F => $filename,
   ) == 0
-  or exit $?;
+  or exit $? >> 8;
+
+system(
+    git        => 'tag',
+    '--verify' => $tag
+  ) == 0
+  or exit $? >> 8;
 
-exec git => 'tag',
-  '--verify'    => $tag;
+say "\nDo not forget to `git push --follow-tags ...` now."
 
 __END__
 
 =head1 NAME
 
- micro-release - tag the current commit on a +fixes branch
+ tag-fixes-release - tag the current commit on a +fixes branch
 
 =head1 SYNOPSIS
 
- micro-release [--[no]-edit] [--[no]-dotted] [--force] [TAG]
+ tag-fixes-release [--[no]-edit] [--[no]-dotted] [--force]
 
 =head1 DESCRIPTION
 
-This tools is for internal use only. It tags the current commit
-of a F<+fixes> branch as a "micro release".
+This B<tag-fixes-release> tools is for internal use only. It tags the
+current commit of a F<+fixes> branch as a "micro release".
 
-The tag is taken from the command line parameter I<TAG> or is calculated from the
-branch name and the distance to the release tag. The branch name needs
-to match the branch tag.
+The tag is calculated from the branch name and the distance to the
+branch tag. The branch name needs to match the branch tag.
 
     branch tag:    exim-4_90
     branch name:   exim-4_90+fixes

commit e5fc5d4ba779be4c57bd08ad2da70b6e1a85a549
Author: Jeremy Harris <jgh146exb@wizmail.org>
Date:   Wed Feb 7 23:09:55 2018 +0000

    DKIM: fix buffer overflow in verify
    
    Caused crash in free() by corrupting malloc metadata.
    
    Reported-by: University of Cambridge
    Broken-by: 80a47a2c96

diff --git a/src/src/pdkim/pdkim.c b/src/src/pdkim/pdkim.c
index 186258a6..62934927 100644
--- a/src/src/pdkim/pdkim.c
+++ b/src/src/pdkim/pdkim.c
@@ -701,7 +701,7 @@ if (sig->canon_body == PDKIM_CANON_RELAXED)
   if (!relaxed_data)
     {
     BOOL seen_wsp = FALSE;
-    const uschar * p;
+    const uschar * p, * r;
     int q = 0;
 
     /* We want to be able to free this else we allocate
@@ -712,7 +712,7 @@ if (sig->canon_body == PDKIM_CANON_RELAXED)
     relaxed_data = store_malloc(sizeof(blob) + orig_data->len+1);
     relaxed_data->data = US (relaxed_data+1);
 
-    for (p = orig_data->data; *p; p++)
+    for (p = orig_data->data, r = p + orig_data->len; p < r; p++)
       {
       char c = *p;
       if (c == '\r')
@@ -838,6 +838,7 @@ ctx->linebuf_offset = 0;
 
 /* -------------------------------------------------------------------------- */
 /* Call from pdkim_feed below for processing complete body lines */
+/* NOTE: the line is not NUL-terminated; but we have a count */
 
 static void
 pdkim_bodyline_complete(pdkim_ctx * ctx)

commit 1d602879523c878458f81b9d9d00df49fdcea9ef
Author: Phil Pennock <pdp@exim.org>
Date:   Wed Feb 7 22:59:03 2018 -0500

    Repair Heimdal GSSAPI authenticator init
    
    Broken-by: f2ed27cf5f
    (cherry picked from commit 7be145827de6464f18601325df0091b7c7ab908e)

diff --git a/src/src/drtables.c b/src/src/drtables.c
index 1d815579..5ed8d01c 100644
--- a/src/src/drtables.c
+++ b/src/src/drtables.c
@@ -119,7 +119,7 @@ auth_info auths_available[] = {
   {
   .driver_name =	US"heimdal_gssapi",
   .options =		auth_heimdal_gssapi_options,
-  .options_count 	 &auth_heimdal_gssapi_options_count,
+  .options_count =	&auth_heimdal_gssapi_options_count,
   .options_block =	&auth_heimdal_gssapi_option_defaults,
   .options_len =	sizeof(auth_heimdal_gssapi_options_block),
   .init =		auth_heimdal_gssapi_init,

commit 00cddd57ad37a7eb9543c3ea3d34714c7425f720
Author: Phil Pennock <pdp@exim.org>
Date:   Wed Feb 7 23:15:37 2018 -0500

    Repair Heimdal GSSAPI authenticator init part 2
    
    Broken-by: d185889f4
    (cherry picked from commit aab9a84358906493bde0efc6aa13b00e59096396)

diff --git a/src/src/auths/heimdal_gssapi.c b/src/src/auths/heimdal_gssapi.c
index b583a73f..631b9790 100644
--- a/src/src/auths/heimdal_gssapi.c
+++ b/src/src/auths/heimdal_gssapi.c
@@ -80,9 +80,9 @@ auth_heimdal_gssapi_options_block auth_heimdal_gssapi_option_defaults = {
 #ifdef MACRO_PREDEF
 
 /* Dummy values */
-void auth_heimdal_init(auth_instance *ablock) {}
-int auth_heimdal_server(auth_instance *ablock, uschar *data) {return 0;}
-int auth_heimdal_client(auth_instance *ablock, smtp_inblock *inblock,
+void auth_heimdal_gssapi_init(auth_instance *ablock) {}
+int auth_heimdal_gssapi_server(auth_instance *ablock, uschar *data) {return 0;}
+int auth_heimdal_gssapi_client(auth_instance *ablock, smtp_inblock *inblock,
   smtp_outblock *outblock, int timeout, uschar *buffer, int buffsize) {return 0;}
 void auth_heimdal_gssapi_version_report(FILE *f) {}
 
