readconf: clarify a retry rule parsing error message
[users/jgh/exim.git] / src / src / readconf.c
index 2b02ddf640b30924ab83f17a32ecb16d3941c42f..7f42bb7a97179814cdbbe02713459c52b721557d 100644 (file)
@@ -1,10 +1,8 @@
-/* $Cambridge: exim/src/src/readconf.c,v 1.36 2009/06/10 07:34:04 tom Exp $ */
-
 /*************************************************
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
 /*************************************************
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
-/* Copyright (c) University of Cambridge 1995 - 2007 */
+/* 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
 /* See the file NOTICE for conditions of use and distribution. */
 
 /* Functions for reading the configuration file, and for displaying
@@ -142,6 +140,9 @@ static optionlist optionlist_config[] = {
   { "acl_smtp_auth",            opt_stringptr,   &acl_smtp_auth },
   { "acl_smtp_connect",         opt_stringptr,   &acl_smtp_connect },
   { "acl_smtp_data",            opt_stringptr,   &acl_smtp_data },
   { "acl_smtp_auth",            opt_stringptr,   &acl_smtp_auth },
   { "acl_smtp_connect",         opt_stringptr,   &acl_smtp_connect },
   { "acl_smtp_data",            opt_stringptr,   &acl_smtp_data },
+#ifdef EXPERIMENTAL_PRDR
+  { "acl_smtp_data_prdr",       opt_stringptr,   &acl_smtp_data_prdr },
+#endif
 #ifndef DISABLE_DKIM
   { "acl_smtp_dkim",            opt_stringptr,   &acl_smtp_dkim },
 #endif
 #ifndef DISABLE_DKIM
   { "acl_smtp_dkim",            opt_stringptr,   &acl_smtp_dkim },
 #endif
@@ -210,14 +211,21 @@ static optionlist optionlist_config[] = {
   { "disable_ipv6",             opt_bool,        &disable_ipv6 },
 #ifndef DISABLE_DKIM
   { "dkim_verify_signers",      opt_stringptr,   &dkim_verify_signers },
   { "disable_ipv6",             opt_bool,        &disable_ipv6 },
 #ifndef DISABLE_DKIM
   { "dkim_verify_signers",      opt_stringptr,   &dkim_verify_signers },
+#endif
+#ifdef EXPERIMENTAL_DMARC
+  { "dmarc_forensic_sender",    opt_stringptr,   &dmarc_forensic_sender },
+  { "dmarc_history_file",       opt_stringptr,   &dmarc_history_file },
+  { "dmarc_tld_file",           opt_stringptr,   &dmarc_tld_file },
 #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 },
 #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_dnssec_ok",            opt_int,         &dns_dnssec_ok },
   { "dns_ipv4_lookup",          opt_stringptr,   &dns_ipv4_lookup },
   { "dns_retrans",              opt_time,        &dns_retrans },
   { "dns_retry",                opt_int,         &dns_retry },
   { "dns_ipv4_lookup",          opt_stringptr,   &dns_ipv4_lookup },
   { "dns_retrans",              opt_time,        &dns_retrans },
   { "dns_retry",                opt_int,         &dns_retry },
+  { "dns_use_edns0",            opt_int,         &dns_use_edns0 },
  /* This option is now a no-op, retained for compability */
   { "drop_cr",                  opt_bool,        &drop_cr },
 /*********************************************************/
  /* This option is now a no-op, retained for compability */
   { "drop_cr",                  opt_bool,        &drop_cr },
 /*********************************************************/
@@ -235,6 +243,9 @@ static optionlist optionlist_config[] = {
   { "gecos_name",               opt_stringptr,   &gecos_name },
   { "gecos_pattern",            opt_stringptr,   &gecos_pattern },
 #ifdef SUPPORT_TLS
   { "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 },
   { "gnutls_require_kx",        opt_stringptr,   &gnutls_require_kx },
   { "gnutls_require_mac",       opt_stringptr,   &gnutls_require_mac },
   { "gnutls_require_protocols", opt_stringptr,   &gnutls_require_proto },
@@ -261,7 +272,14 @@ static optionlist optionlist_config[] = {
   { "ignore_fromline_local",    opt_bool,        &ignore_fromline_local },
   { "keep_malformed",           opt_time,        &keep_malformed },
 #ifdef LOOKUP_LDAP
   { "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_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 },
   { "ldap_version",             opt_int,         &eldap_version },
 #endif
   { "local_from_check",         opt_bool,        &local_from_check },
@@ -290,6 +308,9 @@ static optionlist optionlist_config[] = {
   { "mysql_servers",            opt_stringptr,   &mysql_servers },
 #endif
   { "never_users",              opt_uidlist,     &never_users },
   { "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
 #ifdef LOOKUP_ORACLE
   { "oracle_servers",           opt_stringptr,   &oracle_servers },
 #endif
@@ -303,6 +324,9 @@ static optionlist optionlist_config[] = {
 #endif
   { "pid_file_path",            opt_stringptr,   &pid_file_path },
   { "pipelining_advertise_hosts", opt_stringptr, &pipelining_advertise_hosts },
 #endif
   { "pid_file_path",            opt_stringptr,   &pid_file_path },
   { "pipelining_advertise_hosts", opt_stringptr, &pipelining_advertise_hosts },
+#ifdef EXPERIMENTAL_PRDR
+  { "prdr_enable",              opt_bool,        &prdr_enable },
+#endif
   { "preserve_message_logs",    opt_bool,        &preserve_message_logs },
   { "primary_hostname",         opt_stringptr,   &primary_hostname },
   { "print_topbitchars",        opt_bool,        &print_topbitchars },
   { "preserve_message_logs",    opt_bool,        &preserve_message_logs },
   { "primary_hostname",         opt_stringptr,   &primary_hostname },
   { "print_topbitchars",        opt_bool,        &print_topbitchars },
@@ -395,14 +419,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 },
   { "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 },
   { "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_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 },
   { "tls_privatekey",           opt_stringptr,   &tls_privatekey },
   { "tls_remember_esmtp",       opt_bool,        &tls_remember_esmtp },
   { "tls_require_ciphers",      opt_stringptr,   &tls_require_ciphers },
@@ -507,7 +538,7 @@ while (isalnum(*s) || *s == '_')
   {
   if (namelen >= sizeof(name) - 1)
     log_write(0, LOG_PANIC_DIE|LOG_CONFIG_IN,
   {
   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);
+      "macro name too long (maximum is " SIZE_T_FMT " characters)", sizeof(name) - 1);
   name[namelen++] = *s++;
   }
 name[namelen] = 0;
   name[namelen++] = *s++;
   }
 name[namelen] = 0;
@@ -1361,6 +1392,8 @@ int intbase = 0;
 uschar *inttype = US"";
 uschar *sptr;
 uschar *s = buffer;
 uschar *inttype = US"";
 uschar *sptr;
 uschar *s = buffer;
+uschar *saved_condition, *strtemp;
+uschar **str_target;
 uschar name[64];
 uschar name2[64];
 
 uschar name[64];
 uschar name2[64];
 
@@ -1414,13 +1447,9 @@ if (ol == NULL)
   log_write(0, LOG_PANIC_DIE|LOG_CONFIG_IN, CS unknown_txt, name);
   }
 
   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,
   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;
 
 ol->type |= opt_set | issecure;
 type = ol->type & opt_mask;
@@ -1500,6 +1529,49 @@ switch (type)
     control block and flags word. */
 
     case opt_stringptr:
     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 + Ustrlen(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;
     case opt_rewrite:
     if (data_block == NULL)
       *((uschar **)(ol->value)) = sptr;
@@ -2093,13 +2165,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
                    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,
 
 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;
 {
 struct passwd *pw;
 struct group *gr;
@@ -2121,7 +2194,11 @@ if (ol == NULL)
 
 if (!admin_user && (ol->type & opt_secure) != 0)
   {
 
 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;
   }
 
   return;
   }
 
@@ -2140,11 +2217,13 @@ switch(ol->type & opt_mask)
   case opt_stringptr:
   case opt_rewrite:        /* Show the text value */
   s = *((uschar **)value);
   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:
   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:
   break;
 
   case opt_mkint:
@@ -2159,23 +2238,30 @@ switch(ol->type & opt_mask)
         c = 'M';
         x >>= 10;
         }
         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);
     }
   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:
     }
   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 */
   break;
 
   /* Can be negative only when "unset", in which case integer */
@@ -2187,7 +2273,8 @@ switch(ol->type & opt_mask)
     int d = 100;
     if (x < 0) printf("%s =\n", name); else
       {
     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);
       do
         {
         printf("%d", f/d);
@@ -2213,7 +2300,8 @@ switch(ol->type & opt_mask)
       if (options_block != NULL)
         value2 = (void *)((uschar *)options_block + (long int)value2);
       s = *((uschar **)value2);
       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;
       }
     }
       break;
       }
     }
@@ -2221,14 +2309,15 @@ switch(ol->type & opt_mask)
   /* Else fall through */
 
   case opt_uid:
   /* Else fall through */
 
   case opt_uid:
+  if (!no_labels) printf("%s = ", name);
   if (! *get_set_flag(name, oltop, last, options_block))
   if (! *get_set_flag(name, oltop, last, options_block))
-    printf("%s =\n", name);
+    printf("\n");
   else
     {
     pw = getpwuid(*((uid_t *)value));
     if (pw == NULL)
   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;
 
     }
   break;
 
@@ -2245,7 +2334,8 @@ switch(ol->type & opt_mask)
       if (options_block != NULL)
         value2 = (void *)((uschar *)options_block + (long int)value2);
       s = *((uschar **)value2);
       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;
       }
     }
       break;
       }
     }
@@ -2253,31 +2343,34 @@ switch(ol->type & opt_mask)
   /* Else fall through */
 
   case opt_gid:
   /* Else fall through */
 
   case opt_gid:
+  if (!no_labels) printf("%s = ", name);
   if (! *get_set_flag(name, oltop, last, options_block))
   if (! *get_set_flag(name, oltop, last, options_block))
-    printf("%s =\n", name);
+    printf("\n");
   else
     {
     gr = getgrgid(*((int *)value));
     if (gr == NULL)
   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);
     }
   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 (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;
     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 = ':';
       }
     }
       sep = ':';
       }
     }
@@ -2286,18 +2379,20 @@ switch(ol->type & opt_mask)
 
   case opt_gidlist:
   gidlist = *((gid_t **)value);
 
   case opt_gidlist:
   gidlist = *((gid_t **)value);
-  printf("%s =", name);
+  if (!no_labels) printf("%s =", name);
   if (gidlist != NULL)
     {
     int i;
     uschar sep = ' ';
   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;
     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 = ':';
       }
     }
       sep = ':';
       }
     }
@@ -2305,14 +2400,15 @@ switch(ol->type & opt_mask)
   break;
 
   case opt_time:
   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;
   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");
     for (i = 0; i < list[1]; i++)
       printf("%s%s", (i == 0)? "" : ":", readconf_printtime(list[i+2]));
     printf("\n");
@@ -2335,7 +2431,8 @@ switch(ol->type & opt_mask)
     s = *((uschar **)value2);
     if (s != NULL)
       {
     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 */
       break;
       }
     /* s == NULL => string not set; fall through */
@@ -2366,30 +2463,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
   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
   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
 
   +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
 
 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
 
 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;
 {
 BOOL names_only = FALSE;
 optionlist *ol;
 optionlist *ol2 = NULL;
 driver_instance *d = NULL;
+macro_item *m;
 int size = 0;
 
 if (type == NULL)
 int size = 0;
 
 if (type == NULL)
@@ -2410,8 +2511,11 @@ if (type == NULL)
       if (t != NULL)
         {
         found = TRUE;
       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);
         }
       }
 
         }
       }
 
@@ -2434,7 +2538,9 @@ if (type == NULL)
          ol < optionlist_config + optionlist_config_size; ol++)
       {
       if ((ol->type & opt_hidden) == 0)
          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;
     }
       }
     return;
     }
@@ -2448,7 +2554,7 @@ if (type == NULL)
          ol < local_scan_options + local_scan_options_count; ol++)
       {
       print_ol(ol, US ol->name, NULL, local_scan_options,
          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;
       }
     #endif
     return;
@@ -2471,11 +2577,10 @@ if (type == NULL)
     name = NULL;
     }
 
     name = NULL;
     }
 
-  else if (Ustrcmp(name, "authenticator_list") == 0)
+  else if (Ustrcmp(name, "macros") == 0)
     {
     {
-    type = US"authenticator";
+    type = US"macro";
     name = NULL;
     name = NULL;
-    names_only = TRUE;
     }
 
   else if (Ustrcmp(name, "router_list") == 0)
     }
 
   else if (Ustrcmp(name, "router_list") == 0)
@@ -2484,16 +2589,32 @@ if (type == NULL)
     name = NULL;
     names_only = TRUE;
     }
     name = NULL;
     names_only = TRUE;
     }
+
   else if (Ustrcmp(name, "transport_list") == 0)
     {
     type = US"transport";
     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),
   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;
     }
   }
     return;
     }
   }
@@ -2523,6 +2644,32 @@ else if (Ustrcmp(type, "authenticator") == 0)
   size = optionlist_auths_size;
   }
 
   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);
 if (names_only)
   {
   for (; d != NULL; d = d->next) printf("%s\n", CS d->name);
@@ -2540,14 +2687,14 @@ for (; d != NULL; d = d->next)
   for (ol = ol2; ol < ol2 + size; ol++)
     {
     if ((ol->type & opt_hidden) == 0)
   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)
     }
 
   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;
   }
     }
   if (name != NULL) return;
   }
@@ -2671,6 +2818,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        *
 *************************************************/
 /*************************************************
 *         Read main configuration options        *
 *************************************************/
@@ -2784,22 +2996,21 @@ else
       "configuration file %s", filename));
   }
 
       "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 (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 */
        #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
        #ifdef CONFIGURE_GROUP
        && statbuf.st_gid != config_gid           /* group not the special one */
        #endif
@@ -2942,8 +3153,8 @@ if (s == NULL)
 spool_directory = s;
 
 /* Expand log_file_path, which must contain "%s" in any component that isn't
 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)
   {
 
 if (*log_file_path != 0)
   {
@@ -2967,7 +3178,7 @@ if (*log_file_path != 0)
     t = Ustrchr(sss, '%');
     if (t != NULL)
       {
     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);
       }
         log_write(0, LOG_MAIN|LOG_PANIC_DIE, "log_file_path \"%s\" contains "
           "unexpected \"%%\" character", s);
       }
@@ -3016,6 +3227,11 @@ if (*pid_file_path != 0)
   pid_file_path = s;
   }
 
   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. */
 
 /* Compile the regex for matching a UUCP-style "From_" line in an incoming
 message. */
 
@@ -3089,9 +3305,14 @@ so as to ensure that everything else is set up before the expansion. */
 
 if (host_number_string != NULL)
   {
 
 if (host_number_string != NULL)
   {
+  long int n;
   uschar *end;
   uschar *s = expand_string(host_number_string);
   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,
   while (isspace(*end)) end++;
   if (*end != 0)
     log_write(0, LOG_PANIC_DIE|LOG_CONFIG,
@@ -3111,6 +3332,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_");
   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
 }
 
 #endif
 }
 
@@ -3493,7 +3740,7 @@ else if (strncmpic(pp, US"tls_required", p - pp) == 0)
   *basic_errno = ERRNO_TLSREQUIRED;
 
 else if (len != 1 || Ustrncmp(pp, "*", 1) != 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;
 }
 
 return NULL;
 }
@@ -3582,7 +3829,7 @@ while ((p = get_config_line()) != NULL)
   pp = p;
   while (mac_isgraph(*p)) p++;
   if (p - pp <= 0) log_write(0, LOG_PANIC_DIE|LOG_CONFIG_IN,
   pp = p;
   while (mac_isgraph(*p)) p++;
   if (p - pp <= 0) log_write(0, LOG_PANIC_DIE|LOG_CONFIG_IN,
-    "missing error type");
+    "missing error type in retry rule");
 
   /* Test error names for things we understand. */
 
 
   /* Test error names for things we understand. */