Multiple headers_add/remove options per router/transport - fixes bug 337
[exim.git] / src / src / readconf.c
index 0fe00cf6bcf2df4c7112bff2172c8aa84607e052..ddd81d1d33f54d0c8b369a265057e040c614b474 100644 (file)
@@ -1,10 +1,8 @@
-/* $Cambridge: exim/src/src/readconf.c,v 1.6 2005/04/04 10:33:49 ph10 Exp $ */
-
 /*************************************************
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
-/* Copyright (c) University of Cambridge 1995 - 2005 */
+/* Copyright (c) University of Cambridge 1995 - 2012 */
 /* See the file NOTICE for conditions of use and distribution. */
 
 /* Functions for reading the configuration file, and for displaying
@@ -138,9 +136,13 @@ static optionlist optionlist_config[] = {
 #ifdef WITH_CONTENT_SCAN
   { "acl_not_smtp_mime",        opt_stringptr,   &acl_not_smtp_mime },
 #endif
+  { "acl_not_smtp_start",       opt_stringptr,   &acl_not_smtp_start },
   { "acl_smtp_auth",            opt_stringptr,   &acl_smtp_auth },
   { "acl_smtp_connect",         opt_stringptr,   &acl_smtp_connect },
   { "acl_smtp_data",            opt_stringptr,   &acl_smtp_data },
+#ifndef DISABLE_DKIM
+  { "acl_smtp_dkim",            opt_stringptr,   &acl_smtp_dkim },
+#endif
   { "acl_smtp_etrn",            opt_stringptr,   &acl_smtp_etrn },
   { "acl_smtp_expn",            opt_stringptr,   &acl_smtp_expn },
   { "acl_smtp_helo",            opt_stringptr,   &acl_smtp_helo },
@@ -149,6 +151,7 @@ static optionlist optionlist_config[] = {
 #ifdef WITH_CONTENT_SCAN
   { "acl_smtp_mime",            opt_stringptr,   &acl_smtp_mime },
 #endif
+  { "acl_smtp_notquit",         opt_stringptr,   &acl_smtp_notquit },
   { "acl_smtp_predata",         opt_stringptr,   &acl_smtp_predata },
   { "acl_smtp_quit",            opt_stringptr,   &acl_smtp_quit },
   { "acl_smtp_rcpt",            opt_stringptr,   &acl_smtp_rcpt },
@@ -182,23 +185,43 @@ static optionlist optionlist_config[] = {
   { "callout_random_local_part",opt_stringptr,   &callout_random_local_part },
   { "check_log_inodes",         opt_int,         &check_log_inodes },
   { "check_log_space",          opt_Kint,        &check_log_space },
+  { "check_rfc2047_length",     opt_bool,        &check_rfc2047_length },
   { "check_spool_inodes",       opt_int,         &check_spool_inodes },
   { "check_spool_space",        opt_Kint,        &check_spool_space },
   { "daemon_smtp_port",         opt_stringptr|opt_hidden, &daemon_smtp_port },
   { "daemon_smtp_ports",        opt_stringptr,   &daemon_smtp_port },
+  { "daemon_startup_retries",   opt_int,         &daemon_startup_retries },
+  { "daemon_startup_sleep",     opt_time,        &daemon_startup_sleep },
+#ifdef EXPERIMENTAL_DCC
+  { "dcc_direct_add_header",    opt_bool,        &dcc_direct_add_header },
+  { "dccifd_address",           opt_stringptr,   &dccifd_address },
+  { "dccifd_options",           opt_stringptr,   &dccifd_options },
+#endif
   { "delay_warning",            opt_timelist,    &delay_warning },
   { "delay_warning_condition",  opt_stringptr,   &delay_warning_condition },
   { "deliver_drop_privilege",   opt_bool,        &deliver_drop_privilege },
   { "deliver_queue_load_max",   opt_fixed,       &deliver_queue_load_max },
   { "delivery_date_remove",     opt_bool,        &delivery_date_remove },
+#ifdef ENABLE_DISABLE_FSYNC
+  { "disable_fsync",            opt_bool,        &disable_fsync },
+#endif
+  { "disable_ipv6",             opt_bool,        &disable_ipv6 },
+#ifndef DISABLE_DKIM
+  { "dkim_verify_signers",      opt_stringptr,   &dkim_verify_signers },
+#endif
   { "dns_again_means_nonexist", opt_stringptr,   &dns_again_means_nonexist },
   { "dns_check_names_pattern",  opt_stringptr,   &check_dns_names_pattern },
+  { "dns_csa_search_limit",     opt_int,         &dns_csa_search_limit },
+  { "dns_csa_use_reverse",      opt_bool,        &dns_csa_use_reverse },
   { "dns_ipv4_lookup",          opt_stringptr,   &dns_ipv4_lookup },
   { "dns_retrans",              opt_time,        &dns_retrans },
   { "dns_retry",                opt_int,         &dns_retry },
+  { "dns_use_dnssec",           opt_int,         &dns_use_dnssec },
+  { "dns_use_edns0",            opt_int,         &dns_use_edns0 },
  /* This option is now a no-op, retained for compability */
   { "drop_cr",                  opt_bool,        &drop_cr },
 /*********************************************************/
+  { "dsn_from",                 opt_stringptr,   &dsn_from },
   { "envelope_to_remove",       opt_bool,        &envelope_to_remove },
   { "errors_copy",              opt_stringptr,   &errors_copy },
   { "errors_reply_to",          opt_stringptr,   &errors_reply_to },
@@ -211,6 +234,14 @@ static optionlist optionlist_config[] = {
   { "freeze_tell",              opt_stringptr,   &freeze_tell },
   { "gecos_name",               opt_stringptr,   &gecos_name },
   { "gecos_pattern",            opt_stringptr,   &gecos_pattern },
+#ifdef SUPPORT_TLS
+  { "gnutls_compat_mode",       opt_bool,        &gnutls_compat_mode },
+  { "gnutls_enable_pkcs11",     opt_bool,        &gnutls_enable_pkcs11 },
+  /* These three gnutls_require_* options stopped working in Exim 4.80 */
+  { "gnutls_require_kx",        opt_stringptr,   &gnutls_require_kx },
+  { "gnutls_require_mac",       opt_stringptr,   &gnutls_require_mac },
+  { "gnutls_require_protocols", opt_stringptr,   &gnutls_require_proto },
+#endif
   { "header_line_maxsize",      opt_int,         &header_line_maxsize },
   { "header_maxsize",           opt_int,         &header_maxsize },
   { "headers_charset",          opt_stringptr,   &headers_charset },
@@ -233,7 +264,14 @@ static optionlist optionlist_config[] = {
   { "ignore_fromline_local",    opt_bool,        &ignore_fromline_local },
   { "keep_malformed",           opt_time,        &keep_malformed },
 #ifdef LOOKUP_LDAP
+  { "ldap_ca_cert_dir",         opt_stringptr,   &eldap_ca_cert_dir },
+  { "ldap_ca_cert_file",        opt_stringptr,   &eldap_ca_cert_file },
+  { "ldap_cert_file",           opt_stringptr,   &eldap_cert_file },
+  { "ldap_cert_key",            opt_stringptr,   &eldap_cert_key },
+  { "ldap_cipher_suite",        opt_stringptr,   &eldap_cipher_suite },
   { "ldap_default_servers",     opt_stringptr,   &eldap_default_servers },
+  { "ldap_require_cert",        opt_stringptr,   &eldap_require_cert },
+  { "ldap_start_tls",           opt_bool,        &eldap_start_tls },
   { "ldap_version",             opt_int,         &eldap_version },
 #endif
   { "local_from_check",         opt_bool,        &local_from_check },
@@ -248,6 +286,7 @@ static optionlist optionlist_config[] = {
   { "log_timezone",             opt_bool,        &log_timezone },
   { "lookup_open_max",          opt_int,         &lookup_open_max },
   { "max_username_length",      opt_int,         &max_username_length },
+  { "message_body_newlines",    opt_bool,        &message_body_newlines },
   { "message_body_visible",     opt_mkint,       &message_body_visible },
   { "message_id_header_domain", opt_stringptr,   &message_id_domain },
   { "message_id_header_text",   opt_stringptr,   &message_id_text },
@@ -261,6 +300,9 @@ static optionlist optionlist_config[] = {
   { "mysql_servers",            opt_stringptr,   &mysql_servers },
 #endif
   { "never_users",              opt_uidlist,     &never_users },
+#ifdef SUPPORT_TLS
+  { "openssl_options",          opt_stringptr,   &openssl_options },
+#endif
 #ifdef LOOKUP_ORACLE
   { "oracle_servers",           opt_stringptr,   &oracle_servers },
 #endif
@@ -286,6 +328,7 @@ static optionlist optionlist_config[] = {
   { "queue_only",               opt_bool,        &queue_only },
   { "queue_only_file",          opt_stringptr,   &queue_only_file },
   { "queue_only_load",          opt_fixed,       &queue_only_load },
+  { "queue_only_load_latch",    opt_bool,        &queue_only_load_latch },
   { "queue_only_override",      opt_bool,        &queue_only_override },
   { "queue_run_in_order",       opt_bool,        &queue_run_in_order },
   { "queue_run_max",            opt_int,         &queue_run_max },
@@ -332,12 +375,25 @@ static optionlist optionlist_config[] = {
   { "smtp_return_error_details",opt_bool,        &smtp_return_error_details },
 #ifdef WITH_CONTENT_SCAN
   { "spamd_address",            opt_stringptr,   &spamd_address },
+#endif
+#ifdef EXPERIMENTAL_SPF
+  { "spf_guess",                opt_stringptr,   &spf_guess },
 #endif
   { "split_spool_directory",    opt_bool,        &split_spool_directory },
   { "spool_directory",          opt_stringptr,   &spool_directory },
+#ifdef LOOKUP_SQLITE
+  { "sqlite_lock_timeout",      opt_int,         &sqlite_lock_timeout },
+#endif
 #ifdef EXPERIMENTAL_SRS
   { "srs_config",               opt_stringptr,   &srs_config },
+  { "srs_hashlength",           opt_int,         &srs_hashlength },
+  { "srs_hashmin",              opt_int,         &srs_hashmin },
+  { "srs_maxage",               opt_int,         &srs_maxage },
+  { "srs_secrets",              opt_stringptr,   &srs_secrets },
+  { "srs_usehash",              opt_bool,        &srs_usehash },
+  { "srs_usetimestamp",         opt_bool,        &srs_usetimestamp },
 #endif
+  { "strict_acl_vars",          opt_bool,        &strict_acl_vars },
   { "strip_excess_angle_brackets", opt_bool,     &strip_excess_angle_brackets },
   { "strip_trailing_dot",       opt_bool,        &strip_trailing_dot },
   { "syslog_duplication",       opt_bool,        &syslog_duplication },
@@ -352,14 +408,21 @@ static optionlist optionlist_config[] = {
   { "system_filter_reply_transport",opt_stringptr,&system_filter_reply_transport },
   { "system_filter_user",       opt_uid,         &system_filter_uid },
   { "tcp_nodelay",              opt_bool,        &tcp_nodelay },
+#ifdef USE_TCP_WRAPPERS
+  { "tcp_wrappers_daemon_name", opt_stringptr,   &tcp_wrappers_daemon_name },
+#endif
   { "timeout_frozen_after",     opt_time,        &timeout_frozen_after },
   { "timezone",                 opt_stringptr,   &timezone_string },
 #ifdef SUPPORT_TLS
   { "tls_advertise_hosts",      opt_stringptr,   &tls_advertise_hosts },
   { "tls_certificate",          opt_stringptr,   &tls_certificate },
   { "tls_crl",                  opt_stringptr,   &tls_crl },
+  { "tls_dh_max_bits",          opt_int,         &tls_dh_max_bits },
   { "tls_dhparam",              opt_stringptr,   &tls_dhparam },
-  { "tls_on_connect_ports",     opt_stringptr,   &tls_on_connect_ports },
+# if defined(EXPERIMENTAL_OCSP) && !defined(USE_GNUTLS)
+  { "tls_ocsp_file",            opt_stringptr,   &tls_ocsp_file },
+# endif
+  { "tls_on_connect_ports",     opt_stringptr,   &tls_in.on_connect_ports },
   { "tls_privatekey",           opt_stringptr,   &tls_privatekey },
   { "tls_remember_esmtp",       opt_bool,        &tls_remember_esmtp },
   { "tls_require_ciphers",      opt_stringptr,   &tls_require_ciphers },
@@ -435,6 +498,122 @@ return US"";
 
 
 
+/*************************************************
+*       Deal with an assignment to a macro       *
+*************************************************/
+
+/* This function is called when a line that starts with an upper case letter is
+encountered. The argument "line" should contain a complete logical line, and
+start with the first letter of the macro name. The macro name and the
+replacement text are extracted and stored. Redefinition of existing,
+non-command line, macros is permitted using '==' instead of '='.
+
+Arguments:
+  s            points to the start of the logical line
+
+Returns:       nothing
+*/
+
+static void
+read_macro_assignment(uschar *s)
+{
+uschar name[64];
+int namelen = 0;
+BOOL redef = FALSE;
+macro_item *m;
+macro_item *mlast = NULL;
+
+while (isalnum(*s) || *s == '_')
+  {
+  if (namelen >= sizeof(name) - 1)
+    log_write(0, LOG_PANIC_DIE|LOG_CONFIG_IN,
+      "macro name too long (maximum is " SIZE_T_FMT " characters)", sizeof(name) - 1);
+  name[namelen++] = *s++;
+  }
+name[namelen] = 0;
+
+while (isspace(*s)) s++;
+if (*s++ != '=')
+  log_write(0, LOG_PANIC_DIE|LOG_CONFIG_IN, "malformed macro definition");
+
+if (*s == '=')
+  {
+  redef = TRUE;
+  s++;
+  }
+while (isspace(*s)) s++;
+
+/* If an existing macro of the same name was defined on the command line, we
+just skip this definition. It's an error to attempt to redefine a macro without
+redef set to TRUE, or to redefine a macro when it hasn't been defined earlier.
+It is also an error to define a macro whose name begins with the name of a
+previously defined macro. Note: it is documented that the other way round
+works. */
+
+for (m = macros; m != NULL; m = m->next)
+  {
+  int len = Ustrlen(m->name);
+
+  if (Ustrcmp(m->name, name) == 0)
+    {
+    if (!m->command_line && !redef)
+      log_write(0, LOG_CONFIG|LOG_PANIC_DIE, "macro \"%s\" is already "
+       "defined (use \"==\" if you want to redefine it", name);
+    break;
+    }
+
+  if (len < namelen && Ustrstr(name, m->name) != NULL)
+    log_write(0, LOG_CONFIG|LOG_PANIC_DIE, "\"%s\" cannot be defined as "
+      "a macro because previously defined macro \"%s\" is a substring",
+      name, m->name);
+
+  /* We cannot have this test, because it is documented that a substring
+  macro is permitted (there is even an example).
+  *
+  * if (len > namelen && Ustrstr(m->name, name) != NULL)
+  *   log_write(0, LOG_CONFIG|LOG_PANIC_DIE, "\"%s\" cannot be defined as "
+  *     "a macro because it is a substring of previously defined macro \"%s\"",
+  *     name, m->name);
+  */
+
+  mlast = m;
+  }
+
+/* Check for an overriding command-line definition. */
+
+if (m != NULL && m->command_line) return;
+
+/* Redefinition must refer to an existing macro. */
+
+if (redef)
+  {
+  if (m == NULL)
+    log_write(0, LOG_CONFIG|LOG_PANIC_DIE, "can't redefine an undefined macro "
+      "\"%s\"", name);
+  }
+
+/* We have a new definition. The macro_item structure includes a final vector
+called "name" which is one byte long. Thus, adding "namelen" gives us enough
+room to store the "name" string. */
+
+else
+  {
+  m = store_get(sizeof(macro_item) + namelen);
+  if (macros == NULL) macros = m; else mlast->next = m;
+  Ustrncpy(m->name, name, namelen);
+  m->name[namelen] = 0;
+  m->next = NULL;
+  m->command_line = FALSE;
+  }
+
+/* Set the value of the new or redefined macro */
+
+m->replacement = string_copy(s);
+}
+
+
+
+
 
 /*************************************************
 *            Read configuration line             *
@@ -477,7 +656,7 @@ for (;;)
     {
     if (config_file_stack != NULL)    /* EOF inside .include */
       {
-      fclose(config_file);
+      (void)fclose(config_file);
       config_file = config_file_stack->file;
       config_filename = config_file_stack->filename;
       config_lineno = config_file_stack->lineno;
@@ -681,6 +860,10 @@ for (;;)
       }
     *t = 0;
 
+    if (*ss != '/')
+      log_write(0, LOG_PANIC_DIE|LOG_CONFIG_IN, ".include specifies a non-"
+        "absolute path \"%s\"", ss);
+
     if (include_if_exists != 0 && (Ustat(ss, &statbuf) != 0)) continue;
 
     save = store_get(sizeof(config_file_item));
@@ -1198,6 +1381,8 @@ int intbase = 0;
 uschar *inttype = US"";
 uschar *sptr;
 uschar *s = buffer;
+uschar *saved_condition, *strtemp;
+uschar **str_target;
 uschar name[64];
 uschar name2[64];
 
@@ -1251,13 +1436,9 @@ if (ol == NULL)
   log_write(0, LOG_PANIC_DIE|LOG_CONFIG_IN, CS unknown_txt, name);
   }
 
-if ((ol->type & opt_set) != 0)
-  {
-  uschar *mname = name;
-  if (Ustrncmp(mname, "no_", 3) == 0) mname += 3;
+if ((ol->type & opt_set)  && !(ol->type & (opt_rep_con | opt_rep_str)))
   log_write(0, LOG_PANIC_DIE|LOG_CONFIG_IN,
-    "\"%s\" option set for the second time", mname);
-  }
+    "\"%s\" option set for the second time", name);
 
 ol->type |= opt_set | issecure;
 type = ol->type & opt_mask;
@@ -1337,6 +1518,49 @@ switch (type)
     control block and flags word. */
 
     case opt_stringptr:
+    if (data_block == NULL)
+      str_target = (uschar **)(ol->value);
+    else
+      str_target = (uschar **)((uschar *)data_block + (long int)(ol->value));
+    if (ol->type & opt_rep_con)
+      {
+      /* We already have a condition, we're conducting a crude hack to let
+      multiple condition rules be chained together, despite storing them in
+      text form. */
+      saved_condition = *str_target;
+      strtemp = string_sprintf("${if and{{bool_lax{%s}}{bool_lax{%s}}}}",
+          saved_condition, sptr);
+      *str_target = string_copy_malloc(strtemp);
+      /* TODO(pdp): there is a memory leak here and just below
+      when we set 3 or more conditions; I still don't
+      understand the store mechanism enough to know
+      what's the safe way to free content from an earlier store.
+      AFAICT, stores stack, so freeing an early stored item also stores
+      all data alloc'd after it.  If we knew conditions were adjacent,
+      we could survive that, but we don't.  So I *think* we need to take
+      another bit from opt_type to indicate "malloced"; this seems like
+      quite a hack, especially for this one case.  It also means that
+      we can't ever reclaim the store from the *first* condition.
+
+      Because we only do this once, near process start-up, I'm prepared to
+      let this slide for the time being, even though it rankles.  */
+      }
+    else if (*str_target && (ol->type & opt_rep_str))
+     {
+      uschar sep = Ustrncmp(name, "headers_add", 11)==0 ? '\n' : ':';
+      saved_condition = *str_target;
+      strtemp = saved_condition + strlen(saved_condition)-1;
+      if (*strtemp == sep) *strtemp = 0;       /* eliminate trailing list-sep */
+      strtemp = string_sprintf("%s%c%s", saved_condition, sep, sptr);
+      *str_target = string_copy_malloc(strtemp);
+     }
+    else
+      {
+      *str_target = sptr;
+      freesptr = FALSE;
+      }
+    break;
+
     case opt_rewrite:
     if (data_block == NULL)
       *((uschar **)(ol->value)) = sptr;
@@ -1496,10 +1720,16 @@ switch (type)
       int count = 1;
       uid_t *list;
       int ptr = 0;
-      uschar *p = sptr;
+      uschar *p;
+      uschar *op = expand_string (sptr);
+
+      if (op == NULL)
+        log_write(0, LOG_PANIC_DIE|LOG_CONFIG_IN, "failed to expand %s: %s",
+          name, expand_string_message);
 
+      p = op;
       if (*p != 0) count++;
-      while (*p != 0) if (*p++ == ':') count++;
+      while (*p != 0) if (*p++ == ':' && *p != 0) count++;
       list = store_malloc(count*sizeof(uid_t));
       list[ptr++] = (uid_t)(count - 1);
 
@@ -1508,7 +1738,7 @@ switch (type)
       else
         *((uid_t **)((uschar *)data_block + (long int)(ol->value))) = list;
 
-      p = sptr;
+      p = op;
       while (count-- > 1)
         {
         int sep = 0;
@@ -1531,10 +1761,16 @@ switch (type)
       int count = 1;
       gid_t *list;
       int ptr = 0;
-      uschar *p = sptr;
+      uschar *p;
+      uschar *op = expand_string (sptr);
+
+      if (op == NULL)
+        log_write(0, LOG_PANIC_DIE|LOG_CONFIG_IN, "failed to expand %s: %s",
+          name, expand_string_message);
 
+      p = op;
       if (*p != 0) count++;
-      while (*p != 0) if (*p++ == ':') count++;
+      while (*p != 0) if (*p++ == ':' && *p != 0) count++;
       list = store_malloc(count*sizeof(gid_t));
       list[ptr++] = (gid_t)(count - 1);
 
@@ -1543,7 +1779,7 @@ switch (type)
       else
         *((gid_t **)((uschar *)data_block + (long int)(ol->value))) = list;
 
-      p = sptr;
+      p = op;
       while (count-- > 1)
         {
         int sep = 0;
@@ -1671,8 +1907,10 @@ switch (type)
   case opt_int:
     {
     uschar *endptr;
+    long int lvalue;
+
     errno = 0;
-    value = strtol(CS s, CSS &endptr, intbase);
+    lvalue = strtol(CS s, CSS &endptr, intbase);
 
     if (endptr == s)
       log_write(0, LOG_PANIC_DIE|LOG_CONFIG_IN, "%sinteger expected for %s",
@@ -1682,25 +1920,28 @@ switch (type)
       {
       if (tolower(*endptr) == 'k')
         {
-        if (value > INT_MAX/1024 || value < INT_MIN/1024) errno = ERANGE;
-          else value *= 1024;
+        if (lvalue > INT_MAX/1024 || lvalue < INT_MIN/1024) errno = ERANGE;
+          else lvalue *= 1024;
         endptr++;
         }
       else if (tolower(*endptr) == 'm')
         {
-        if (value > INT_MAX/(1024*1024) || value < INT_MIN/(1024*1024))
+        if (lvalue > INT_MAX/(1024*1024) || lvalue < INT_MIN/(1024*1024))
           errno = ERANGE;
-        else value *= 1024*1024;
+        else lvalue *= 1024*1024;
         endptr++;
         }
       }
 
-    if (errno == ERANGE) log_write(0, LOG_PANIC_DIE|LOG_CONFIG_IN,
-      "absolute value of integer \"%s\" is too large (overflow)", s);
+    if (errno == ERANGE || lvalue > INT_MAX || lvalue < INT_MIN)
+      log_write(0, LOG_PANIC_DIE|LOG_CONFIG_IN,
+        "absolute value of integer \"%s\" is too large (overflow)", s);
 
     while (isspace(*endptr)) endptr++;
     if (*endptr != 0)
       extra_chars_error(endptr, inttype, US"integer value for ", name);
+
+    value = (int)lvalue;
     }
 
   if (data_block == NULL)
@@ -1868,6 +2109,12 @@ readconf_printtime(int t)
 int s, m, h, d, w;
 uschar *p = time_buffer;
 
+if (t < 0)
+  {
+  *p++ = '-';
+  t = -t;
+  }
+
 s = t % 60;
 t /= 60;
 m = t % 60;
@@ -1907,13 +2154,14 @@ Arguments:
                    resides.
   oltop          points to the option list in which ol exists
   last           one more than the offset of the last entry in optop
+  no_labels      do not show "foo = " at the start.
 
 Returns:         nothing
 */
 
 static void
 print_ol(optionlist *ol, uschar *name, void *options_block,
-  optionlist *oltop, int last)
+  optionlist *oltop, int last, BOOL no_labels)
 {
 struct passwd *pw;
 struct group *gr;
@@ -1935,7 +2183,11 @@ if (ol == NULL)
 
 if (!admin_user && (ol->type & opt_secure) != 0)
   {
-  printf("%s = <value not displayable>\n", name);
+  const char * const hidden = "<value not displayable>";
+  if (no_labels)
+    printf("%s\n", hidden);
+  else
+    printf("%s = %s\n", name, hidden);
   return;
   }
 
@@ -1954,11 +2206,13 @@ switch(ol->type & opt_mask)
   case opt_stringptr:
   case opt_rewrite:        /* Show the text value */
   s = *((uschar **)value);
-  printf("%s = %s\n", name, (s == NULL)? US"" : string_printing2(s, FALSE));
+  if (!no_labels) printf("%s = ", name);
+  printf("%s\n", (s == NULL)? US"" : string_printing2(s, FALSE));
   break;
 
   case opt_int:
-  printf("%s = %d\n", name, *((int *)value));
+  if (!no_labels) printf("%s = ", name);
+  printf("%d\n", *((int *)value));
   break;
 
   case opt_mkint:
@@ -1973,23 +2227,30 @@ switch(ol->type & opt_mask)
         c = 'M';
         x >>= 10;
         }
-      printf("%s = %d%c\n", name, x, c);
+      if (!no_labels) printf("%s = ", name);
+      printf("%d%c\n", x, c);
+      }
+    else
+      {
+      if (!no_labels) printf("%s = ", name);
+      printf("%d\n", x);
       }
-    else printf("%s = %d\n", name, x);
     }
   break;
 
   case opt_Kint:
     {
     int x = *((int *)value);
-    if (x == 0) printf("%s = 0\n", name);
-      else if ((x & 1023) == 0) printf("%s = %dM\n", name, x >> 10);
-        else printf("%s = %dK\n", name, x);
+    if (!no_labels) printf("%s = ", name);
+    if (x == 0) printf("0\n");
+      else if ((x & 1023) == 0) printf("%dM\n", x >> 10);
+        else printf("%dK\n", x);
     }
   break;
 
   case opt_octint:
-  printf("%s = %#o\n", name, *((int *)value));
+  if (!no_labels) printf("%s = ", name);
+  printf("%#o\n", *((int *)value));
   break;
 
   /* Can be negative only when "unset", in which case integer */
@@ -2001,7 +2262,8 @@ switch(ol->type & opt_mask)
     int d = 100;
     if (x < 0) printf("%s =\n", name); else
       {
-      printf("%s = %d.", name, x/1000);
+      if (!no_labels) printf("%s = ", name);
+      printf("%d.", x/1000);
       do
         {
         printf("%d", f/d);
@@ -2027,7 +2289,8 @@ switch(ol->type & opt_mask)
       if (options_block != NULL)
         value2 = (void *)((uschar *)options_block + (long int)value2);
       s = *((uschar **)value2);
-      printf("%s = %s\n", name, (s == NULL)? US"" : string_printing(s));
+      if (!no_labels) printf("%s = ", name);
+      printf("%s\n", (s == NULL)? US"" : string_printing(s));
       break;
       }
     }
@@ -2035,14 +2298,15 @@ switch(ol->type & opt_mask)
   /* Else fall through */
 
   case opt_uid:
+  if (!no_labels) printf("%s = ", name);
   if (! *get_set_flag(name, oltop, last, options_block))
-    printf("%s =\n", name);
+    printf("\n");
   else
     {
     pw = getpwuid(*((uid_t *)value));
     if (pw == NULL)
-      printf("%s = %ld\n", name, (long int)(*((uid_t *)value)));
-    else printf("%s = %s\n", name, pw->pw_name);
+      printf("%ld\n", (long int)(*((uid_t *)value)));
+    else printf("%s\n", pw->pw_name);
     }
   break;
 
@@ -2059,7 +2323,8 @@ switch(ol->type & opt_mask)
       if (options_block != NULL)
         value2 = (void *)((uschar *)options_block + (long int)value2);
       s = *((uschar **)value2);
-      printf("%s = %s\n", name, (s == NULL)? US"" : string_printing(s));
+      if (!no_labels) printf("%s = ", name);
+      printf("%s\n", (s == NULL)? US"" : string_printing(s));
       break;
       }
     }
@@ -2067,31 +2332,34 @@ switch(ol->type & opt_mask)
   /* Else fall through */
 
   case opt_gid:
+  if (!no_labels) printf("%s = ", name);
   if (! *get_set_flag(name, oltop, last, options_block))
-    printf("%s =\n", name);
+    printf("\n");
   else
     {
     gr = getgrgid(*((int *)value));
     if (gr == NULL)
-       printf("%s = %ld\n", name, (long int)(*((int *)value)));
-    else printf("%s = %s\n", name, gr->gr_name);
+       printf("%ld\n", (long int)(*((int *)value)));
+    else printf("%s\n", gr->gr_name);
     }
   break;
 
   case opt_uidlist:
   uidlist = *((uid_t **)value);
-  printf("%s =", name);
+  if (!no_labels) printf("%s =", name);
   if (uidlist != NULL)
     {
     int i;
     uschar sep = ' ';
+    if (no_labels) sep = '\0';
     for (i = 1; i <= (int)(uidlist[0]); i++)
       {
       uschar *name = NULL;
       pw = getpwuid(uidlist[i]);
       if (pw != NULL) name = US pw->pw_name;
-      if (name != NULL) printf("%c%s", sep, name);
-        else printf("%c%ld", sep, (long int)(uidlist[i]));
+      if (sep != '\0') printf("%c", sep);
+      if (name != NULL) printf("%s", name);
+        else printf("%ld", (long int)(uidlist[i]));
       sep = ':';
       }
     }
@@ -2100,18 +2368,20 @@ switch(ol->type & opt_mask)
 
   case opt_gidlist:
   gidlist = *((gid_t **)value);
-  printf("%s =", name);
+  if (!no_labels) printf("%s =", name);
   if (gidlist != NULL)
     {
     int i;
     uschar sep = ' ';
+    if (no_labels) sep = '\0';
     for (i = 1; i <= (int)(gidlist[0]); i++)
       {
       uschar *name = NULL;
       gr = getgrgid(gidlist[i]);
       if (gr != NULL) name = US gr->gr_name;
-      if (name != NULL) printf("%c%s", sep, name);
-        else printf("%c%ld", sep, (long int)(gidlist[i]));
+      if (sep != '\0') printf("%c", sep);
+      if (name != NULL) printf("%s", name);
+        else printf("%ld", (long int)(gidlist[i]));
       sep = ':';
       }
     }
@@ -2119,14 +2389,15 @@ switch(ol->type & opt_mask)
   break;
 
   case opt_time:
-  printf("%s = %s\n", name, readconf_printtime(*((int *)value)));
+  if (!no_labels) printf("%s = ", name);
+  printf("%s\n", readconf_printtime(*((int *)value)));
   break;
 
   case opt_timelist:
     {
     int i;
     int *list = (int *)value;
-    printf("%s = ", name);
+    if (!no_labels) printf("%s = ", name);
     for (i = 0; i < list[1]; i++)
       printf("%s%s", (i == 0)? "" : ":", readconf_printtime(list[i+2]));
     printf("\n");
@@ -2149,7 +2420,8 @@ switch(ol->type & opt_mask)
     s = *((uschar **)value2);
     if (s != NULL)
       {
-      printf("%s = %s\n", name, string_printing(s));
+      if (!no_labels) printf("%s = ", name);
+      printf("%s\n", string_printing(s));
       break;
       }
     /* s == NULL => string not set; fall through */
@@ -2180,30 +2452,34 @@ second argument is NULL. There are some special values:
   routers            print the routers' configurations
   transports         print the transports' configuration
   authenticators     print the authenticators' configuration
+  macros             print the macros' configuration
   router_list        print a list of router names
   transport_list     print a list of transport names
   authenticator_list print a list of authentication mechanism names
+  macro_list         print a list of macro names
   +name              print a named list item
   local_scan         print the local_scan options
 
-If the second argument is not NULL, it must be one of "router", "transport", or
-"authenticator" in which case the first argument identifies the driver whose
-options are to be printed.
+If the second argument is not NULL, it must be one of "router", "transport",
+"authenticator" or "macro" in which case the first argument identifies the
+driver whose options are to be printed.
 
 Arguments:
   name        option name if type == NULL; else driver name
   type        NULL or driver type name, as described above
+  no_labels   avoid the "foo = " at the start of an item
 
 Returns:      nothing
 */
 
 void
-readconf_print(uschar *name, uschar *type)
+readconf_print(uschar *name, uschar *type, BOOL no_labels)
 {
 BOOL names_only = FALSE;
 optionlist *ol;
 optionlist *ol2 = NULL;
 driver_instance *d = NULL;
+macro_item *m;
 int size = 0;
 
 if (type == NULL)
@@ -2224,8 +2500,11 @@ if (type == NULL)
       if (t != NULL)
         {
         found = TRUE;
-        printf("%slist %s = %s\n", types[i], name+1,
-          ((namedlist_block *)(t->data.ptr))->string);
+        if (no_labels)
+          printf("%s\n", ((namedlist_block *)(t->data.ptr))->string);
+        else
+          printf("%slist %s = %s\n", types[i], name+1,
+            ((namedlist_block *)(t->data.ptr))->string);
         }
       }
 
@@ -2248,7 +2527,9 @@ if (type == NULL)
          ol < optionlist_config + optionlist_config_size; ol++)
       {
       if ((ol->type & opt_hidden) == 0)
-        print_ol(ol, US ol->name, NULL, optionlist_config, optionlist_config_size);
+        print_ol(ol, US ol->name, NULL,
+            optionlist_config, optionlist_config_size,
+            no_labels);
       }
     return;
     }
@@ -2262,7 +2543,7 @@ if (type == NULL)
          ol < local_scan_options + local_scan_options_count; ol++)
       {
       print_ol(ol, US ol->name, NULL, local_scan_options,
-        local_scan_options_count);
+        local_scan_options_count, no_labels);
       }
     #endif
     return;
@@ -2285,11 +2566,10 @@ if (type == NULL)
     name = NULL;
     }
 
-  else if (Ustrcmp(name, "authenticator_list") == 0)
+  else if (Ustrcmp(name, "macros") == 0)
     {
-    type = US"authenticator";
+    type = US"macro";
     name = NULL;
-    names_only = TRUE;
     }
 
   else if (Ustrcmp(name, "router_list") == 0)
@@ -2298,16 +2578,32 @@ if (type == NULL)
     name = NULL;
     names_only = TRUE;
     }
+
   else if (Ustrcmp(name, "transport_list") == 0)
     {
     type = US"transport";
     name = NULL;
     names_only = TRUE;
     }
+
+  else if (Ustrcmp(name, "authenticator_list") == 0)
+    {
+    type = US"authenticator";
+    name = NULL;
+    names_only = TRUE;
+    }
+
+  else if (Ustrcmp(name, "macro_list") == 0)
+    {
+    type = US"macro";
+    name = NULL;
+    names_only = TRUE;
+    }
+
   else
     {
     print_ol(find_option(name, optionlist_config, optionlist_config_size),
-      name, NULL, optionlist_config, optionlist_config_size);
+      name, NULL, optionlist_config, optionlist_config_size, no_labels);
     return;
     }
   }
@@ -2337,6 +2633,32 @@ else if (Ustrcmp(type, "authenticator") == 0)
   size = optionlist_auths_size;
   }
 
+else if (Ustrcmp(type, "macro") == 0)
+  {
+  /* People store passwords in macros and they were previously not available
+  for printing.  So we have an admin_users restriction. */
+  if (!admin_user)
+    {
+    fprintf(stderr, "exim: permission denied\n");
+    exit(EXIT_FAILURE);
+    }
+  for (m = macros; m != NULL; m = m->next)
+    {
+    if (name == NULL || Ustrcmp(name, m->name) == 0)
+      {
+      if (names_only)
+        printf("%s\n", CS m->name);
+      else
+        printf("%s=%s\n", CS m->name, CS m->replacement);
+      if (name != NULL)
+        return;
+      }
+    }
+  if (name != NULL)
+    printf("%s %s not found\n", type, name);
+  return;
+  }
+
 if (names_only)
   {
   for (; d != NULL; d = d->next) printf("%s\n", CS d->name);
@@ -2354,14 +2676,14 @@ for (; d != NULL; d = d->next)
   for (ol = ol2; ol < ol2 + size; ol++)
     {
     if ((ol->type & opt_hidden) == 0)
-      print_ol(ol, US ol->name, d, ol2, size);
+      print_ol(ol, US ol->name, d, ol2, size, no_labels);
     }
 
   for (ol = d->info->options;
        ol < d->info->options + *(d->info->options_count); ol++)
     {
     if ((ol->type & opt_hidden) == 0)
-      print_ol(ol, US ol->name, d, d->info->options, *(d->info->options_count));
+      print_ol(ol, US ol->name, d, d->info->options, *(d->info->options_count), no_labels);
     }
   if (name != NULL) return;
   }
@@ -2485,6 +2807,71 @@ log_write(0, LOG_MAIN|LOG_PANIC_DIE, "malformed ratelimit data: %s", s);
 
 
 
+/*************************************************
+*       Drop privs for checking TLS config      *
+*************************************************/
+
+/* We want to validate TLS options during readconf, but do not want to be
+root when we call into the TLS library, in case of library linkage errors
+which cause segfaults; before this check, those were always done as the Exim
+runtime user and it makes sense to continue with that.
+
+Assumes:  tls_require_ciphers has been set, if it will be
+          exim_user has been set, if it will be
+          exim_group has been set, if it will be
+
+Returns:  bool for "okay"; false will cause caller to immediately exit.
+*/
+
+#ifdef SUPPORT_TLS
+static BOOL
+tls_dropprivs_validate_require_cipher(void)
+{
+const uschar *errmsg;
+pid_t pid;
+int rc, status;
+void (*oldsignal)(int);
+
+oldsignal = signal(SIGCHLD, SIG_DFL);
+
+fflush(NULL);
+if ((pid = fork()) < 0)
+  log_write(0, LOG_MAIN|LOG_PANIC_DIE, "fork failed for TLS check");
+
+if (pid == 0)
+  {
+  /* in some modes, will have dropped privilege already */
+  if (!geteuid())
+    exim_setugid(exim_uid, exim_gid, FALSE,
+        US"calling tls_validate_require_cipher");
+
+  errmsg = tls_validate_require_cipher();
+  if (errmsg)
+    {
+    log_write(0, LOG_PANIC_DIE|LOG_CONFIG,
+        "tls_require_ciphers invalid: %s", errmsg);
+    }
+  fflush(NULL);
+  _exit(0);
+  }
+
+do {
+  rc = waitpid(pid, &status, 0);
+} while (rc < 0 && errno == EINTR);
+
+DEBUG(D_tls)
+  debug_printf("tls_validate_require_cipher child %d ended: status=0x%x\n",
+      (int)pid, status);
+
+signal(SIGCHLD, oldsignal);
+
+return status == 0;
+}
+#endif /* SUPPORT_TLS */
+
+
+
+
 /*************************************************
 *         Read main configuration options        *
 *************************************************/
@@ -2598,22 +2985,21 @@ else
       "configuration file %s", filename));
   }
 
-/* Check the status of the file we have opened, unless it was specified on
-the command line, in which case privilege was given away at the start. */
+/* Check the status of the file we have opened, if we have retained root
+privileges and the file isn't /dev/null (which *should* be 0666). */
 
-if (!config_changed)
+if (trusted_config && Ustrcmp(filename, US"/dev/null"))
   {
   if (fstat(fileno(config_file), &statbuf) != 0)
     log_write(0, LOG_MAIN|LOG_PANIC_DIE, "failed to stat configuration file %s",
       big_buffer);
 
-  if ((statbuf.st_uid != root_uid &&             /* owner not root */
-       statbuf.st_uid != exim_uid                /* owner not exim */
+  if ((statbuf.st_uid != root_uid                /* owner not root */
        #ifdef CONFIGURE_OWNER
        && statbuf.st_uid != config_uid           /* owner not the special one */
        #endif
          ) ||                                    /* or */
-      (statbuf.st_gid != exim_gid                /* group not exim & */
+      (statbuf.st_gid != root_gid                /* group not root & */
        #ifdef CONFIGURE_GROUP
        && statbuf.st_gid != config_gid           /* group not the special one */
        #endif
@@ -2631,65 +3017,7 @@ a macro definition. */
 
 while ((s = get_config_line()) != NULL)
   {
-  if (isupper(s[0]))
-    {
-    macro_item *m;
-    macro_item *mlast = NULL;
-    uschar name[64];
-    int namelen = 0;
-
-    while (isalnum(*s) || *s == '_')
-      {
-      if (namelen >= sizeof(name) - 1)
-        log_write(0, LOG_PANIC_DIE|LOG_CONFIG_IN,
-          "macro name too long (maximum is %d characters)", sizeof(name) - 1);
-      name[namelen++] = *s++;
-      }
-    name[namelen] = 0;
-    while (isspace(*s)) s++;
-    if (*s++ != '=') log_write(0, LOG_PANIC_DIE|LOG_CONFIG_IN,
-      "malformed macro definition");
-    while (isspace(*s)) s++;
-
-    /* If an existing macro of the same name was defined on the command line,
-    we just skip this definition. Otherwise it's an error to attempt to
-    redefine a macro. It is also an error to define a macro whose name begins
-    with the name of a previously-defined macro. */
-
-    for (m = macros; m != NULL; m = m->next)
-      {
-      int len = Ustrlen(m->name);
-
-      if (Ustrcmp(m->name, name) == 0)
-        {
-        if (m->command_line) break;
-        log_write(0, LOG_CONFIG|LOG_PANIC_DIE, "macro \"%s\" is already "
-          "defined", name);
-        }
-
-      if (len < namelen && Ustrstr(name, m->name) != NULL)
-        log_write(0, LOG_CONFIG|LOG_PANIC_DIE, "\"%s\" cannot be defined as "
-          "a macro because previously defined macro \"%s\" is a substring",
-          name, m->name);
-
-      mlast = m;
-      }
-    if (m != NULL) continue;   /* Found an overriding command-line definition */
-
-    m = store_get(sizeof(macro_item) + namelen);
-    m->next = NULL;
-    m->command_line = FALSE;
-    if (mlast == NULL) macros = m; else mlast->next = m;
-
-    /* This use of strcpy() is OK because we have obtained a block of store
-    whose size is based on the length of "name". The definition of the
-    macro_item structure includes a final vector called "name" which is one
-    byte long. Thus, adding "namelen" gives us enough room to store the "name"
-    string. */
-
-    Ustrcpy(m->name, name);
-    m->replacement = string_copy(s);
-    }
+  if (isupper(s[0])) read_macro_assignment(s);
 
   else if (Ustrncmp(s, "domainlist", 10) == 0)
     read_named_list(&domainlist_anchor, &domainlist_count,
@@ -2724,10 +3052,19 @@ wanted. */
 
 if (timezone_string != NULL && *timezone_string == 0) timezone_string = NULL;
 
+/* The max retry interval must not be greater than 24 hours. */
+
+if (retry_interval_max > 24*60*60) retry_interval_max = 24*60*60;
+
 /* remote_max_parallel must be > 0 */
 
 if (remote_max_parallel <= 0) remote_max_parallel = 1;
 
+/* Save the configured setting of freeze_tell, so we can re-instate it at the
+start of a new SMTP message. */
+
+freeze_tell_config = freeze_tell;
+
 /* The primary host name may be required for expansion of spool_directory
 and log_file_path, so make sure it is set asap. It is obtained from uname(),
 but if that yields an unqualified value, make a FQDN by using gethostbyname to
@@ -2748,9 +3085,9 @@ if (primary_hostname == NULL)
     struct hostent *hostdata;
 
     #if HAVE_IPV6
-    if (dns_ipv4_lookup == NULL ||
+    if (!disable_ipv6 && (dns_ipv4_lookup == NULL ||
          match_isinlist(hostname, &dns_ipv4_lookup, 0, NULL, NULL, MCL_DOMAIN,
-           TRUE, NULL) != OK)
+           TRUE, NULL) != OK))
       af = AF_INET6;
     #else
     af = AF_INET;
@@ -2805,8 +3142,8 @@ if (s == NULL)
 spool_directory = s;
 
 /* Expand log_file_path, which must contain "%s" in any component that isn't
-the null string or "syslog". It is also allowed to contain one instance of %D.
-However, it must NOT contain % followed by anything else. */
+the null string or "syslog". It is also allowed to contain one instance of %D
+or %M. However, it must NOT contain % followed by anything else. */
 
 if (*log_file_path != 0)
   {
@@ -2830,7 +3167,7 @@ if (*log_file_path != 0)
     t = Ustrchr(sss, '%');
     if (t != NULL)
       {
-      if (t[1] != 'D' || Ustrchr(t+2, '%') != NULL)
+      if ((t[1] != 'D' && t[1] != 'M') || Ustrchr(t+2, '%') != NULL)
         log_write(0, LOG_MAIN|LOG_PANIC_DIE, "log_file_path \"%s\" contains "
           "unexpected \"%%\" character", s);
       }
@@ -2879,6 +3216,11 @@ if (*pid_file_path != 0)
   pid_file_path = s;
   }
 
+/* Set default value of process_log_path */
+
+if (process_log_path == NULL || *process_log_path =='\0')
+  process_log_path = string_sprintf("%s/exim-process.info", spool_directory);
+
 /* Compile the regex for matching a UUCP-style "From_" line in an incoming
 message. */
 
@@ -2952,9 +3294,14 @@ so as to ensure that everything else is set up before the expansion. */
 
 if (host_number_string != NULL)
   {
+  long int n;
   uschar *end;
   uschar *s = expand_string(host_number_string);
-  long int n = Ustrtol(s, &end, 0);
+  if (s == NULL)
+    log_write(0, LOG_MAIN|LOG_PANIC_DIE,
+        "failed to expand localhost_number \"%s\": %s",
+        host_number_string, expand_string_message);
+  n = Ustrtol(s, &end, 0);
   while (isspace(*end)) end++;
   if (*end != 0)
     log_write(0, LOG_PANIC_DIE|LOG_CONFIG,
@@ -2974,6 +3321,32 @@ if ((tls_verify_hosts != NULL || tls_try_verify_hosts != NULL) &&
   log_write(0, LOG_PANIC_DIE|LOG_CONFIG,
     "tls_%sverify_hosts is set, but tls_verify_certificates is not set",
     (tls_verify_hosts != NULL)? "" : "try_");
+
+/* This also checks that the library linkage is working and we can call
+routines in it, so call even if tls_require_ciphers is unset */
+if (!tls_dropprivs_validate_require_cipher())
+  exit(1);
+
+/* Magic number: at time of writing, 1024 has been the long-standing value
+used by so many clients, and what Exim used to use always, that it makes
+sense to just min-clamp this max-clamp at that. */
+if (tls_dh_max_bits < 1024)
+  log_write(0, LOG_PANIC_DIE|LOG_CONFIG,
+      "tls_dh_max_bits is too small, must be at least 1024 for interop");
+
+/* If openssl_options is set, validate it */
+if (openssl_options != NULL)
+  {
+# ifdef USE_GNUTLS
+  log_write(0, LOG_PANIC_DIE|LOG_CONFIG,
+    "openssl_options is set but we're using GnuTLS");
+# else
+  long dummy;
+  if (!(tls_openssl_options_parse(openssl_options, &dummy)))
+    log_write(0, LOG_PANIC_DIE|LOG_CONFIG,
+      "openssl_options parse error: %s", openssl_options);
+# endif
+  }
 #endif
 }
 
@@ -3071,16 +3444,33 @@ driver_instance **p = anchor;
 driver_instance *d = NULL;
 uschar *buffer;
 
-/* Now process the configuration lines */
-
 while ((buffer = get_config_line()) != NULL)
   {
   uschar name[64];
+  uschar *s;
+
+  /* Read the first name on the line and test for the start of a new driver. A
+  macro definition indicates the end of the previous driver. If this isn't the
+  start of a new driver, the line will be re-read. */
 
-  /* Read the first name on the line and test for the start of a new driver.
-  If this isn't the start of a new driver, the line will be re-read. */
+  s = readconf_readname(name, sizeof(name), buffer);
 
-  uschar *s = readconf_readname(name, sizeof(name), buffer);
+  /* Handle macro definition, first finishing off the initialization of the
+  previous driver, if any. */
+
+  if (isupper(*name) && *s == '=')
+    {
+    if (d != NULL)
+      {
+      if (d->driver_name == NULL)
+        log_write(0, LOG_PANIC_DIE|LOG_CONFIG,
+          "no driver defined for %s \"%s\"", class, d->name);
+      (d->info->init)(d);
+      d = NULL;
+      }
+    read_macro_assignment(buffer);
+    continue;
+    }
 
   /* If the line starts with a name terminated by a colon, we are at the
   start of the definition of a new driver. The rest of the line must be
@@ -3128,7 +3518,8 @@ while ((buffer = get_config_line()) != NULL)
     continue;
     }
 
-  /* Give an error if we have not set up a current driver yet. */
+  /* Not the start of a new driver. Give an error if we have not set up a
+  current driver yet. */
 
   if (d == NULL) log_write(0, LOG_PANIC_DIE|LOG_CONFIG_IN,
     "%s name missing", class);
@@ -3299,7 +3690,9 @@ else if (len == 7 && strncmpic(pp, US"timeout", len) == 0)
     }
   }
 
-else if (strncmpic(pp, US"rcpt_4", 6) == 0)
+else if (strncmpic(pp, US"mail_4", 6) == 0 ||
+         strncmpic(pp, US"rcpt_4", 6) == 0 ||
+         strncmpic(pp, US"data_4", 6) == 0)
   {
   BOOL bad = FALSE;
   int x = 255;                           /* means "any 4xx code" */
@@ -3316,21 +3709,27 @@ else if (strncmpic(pp, US"rcpt_4", 6) == 0)
     else if (a != 'x' || b != 'x') bad = TRUE;
     }
 
-  if (bad) return US"rcpt_4 must be followed by xx, dx, or dd, where "
-    "x is literal and d is any digit";
+  if (bad)
+    return string_sprintf("%.4s_4 must be followed by xx, dx, or dd, where "
+      "x is literal and d is any digit", pp);
 
-  *basic_errno = ERRNO_RCPT4XX;
+  *basic_errno = (*pp == 'm')? ERRNO_MAIL4XX :
+                 (*pp == 'r')? ERRNO_RCPT4XX : ERRNO_DATA4XX;
   *more_errno = x << 8;
   }
 
 else if (len == 4 && strncmpic(pp, US"auth", len) == 0 &&
          strncmpic(q+1, US"failed", p-q-1) == 0)
-  {
   *basic_errno = ERRNO_AUTHFAIL;
-  }
+
+else if (strncmpic(pp, US"lost_connection", p - pp) == 0)
+  *basic_errno = ERRNO_SMTPCLOSED;
+
+else if (strncmpic(pp, US"tls_required", p - pp) == 0)
+  *basic_errno = ERRNO_TLSREQUIRED;
 
 else if (len != 1 || Ustrncmp(pp, "*", 1) != 0)
-  return string_sprintf("unknown or malformed retry error \"%.*s\"", p-pp, pp);
+  return string_sprintf("unknown or malformed retry error \"%.*s\"", (int) (p-pp), pp);
 
 return NULL;
 }
@@ -3465,6 +3864,7 @@ while ((p = get_config_line()) != NULL)
       break;
 
       case 'G':   /* Geometrically increasing intervals */
+      case 'H':   /* Ditto, but with randomness */
       rule->p1 = retry_arg(&p, 0);
       rule->p2 = retry_arg(&p, 1);
       break;
@@ -3475,7 +3875,7 @@ while ((p = get_config_line()) != NULL)
       }
 
     if (rule->timeout <= 0 || rule->p1 <= 0 ||
-          (rule->rule == 'G' && rule->p2 < 1000))
+          (rule->rule != 'F' && rule->p2 < 1000))
       log_write(0, LOG_PANIC_DIE|LOG_CONFIG_IN,
         "bad parameters for retry rule");
 
@@ -3582,7 +3982,8 @@ if (skip)
   return;
   }
 
-/* Read each ACL and add it into the tree */
+/* Read each ACL and add it into the tree. Macro (re)definitions are allowed
+between ACLs. */
 
 acl_line = get_config_line();
 
@@ -3593,8 +3994,15 @@ while(acl_line != NULL)
   uschar *error;
 
   p = readconf_readname(name, sizeof(name), acl_line);
-  if (*p != ':')
-    log_write(0, LOG_PANIC_DIE|LOG_CONFIG_IN, "missing ACL name");
+  if (isupper(*name) && *p == '=')
+    {
+    read_macro_assignment(acl_line);
+    acl_line = get_config_line();
+    continue;
+    }
+
+  if (*p != ':' || name[0] == 0)
+    log_write(0, LOG_PANIC_DIE|LOG_CONFIG_IN, "missing or malformed ACL name");
 
   node = store_get(sizeof(tree_node) + Ustrlen(name));
   Ustrcpy(node->name, name);
@@ -3717,7 +4125,7 @@ while(next_section[0] != 0)
     }
   }
 
-fclose(config_file);
+(void)fclose(config_file);
 }
 
 /* End of readconf.c */