Add cmdline option to append a log message. Bug 418
[exim.git] / src / src / readconf.c
index 33f10690b8670da900c0e65fd32d000db5f80063..687b35223f0d7c6c6f8fbbe78debaeddd765faec 100644 (file)
@@ -1,10 +1,8 @@
-/* $Cambridge: exim/src/src/readconf.c,v 1.44 2010/06/12 15:21:26 jetmore Exp $ */
-
 /*************************************************
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
-/* Copyright (c) University of Cambridge 1995 - 2009 */
+/* Copyright (c) University of Cambridge 1995 - 2014 */
 /* 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 },
+#ifndef DISABLE_PRDR
+  { "acl_smtp_data_prdr",       opt_stringptr,   &acl_smtp_data_prdr },
+#endif
 #ifndef DISABLE_DKIM
   { "acl_smtp_dkim",            opt_stringptr,   &acl_smtp_dkim },
 #endif
@@ -210,21 +211,32 @@ static optionlist optionlist_config[] = {
   { "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 },
+  { "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_use_edns0",            opt_int,         &dns_use_edns0 },
  /* This option is now a no-op, retained for compability */
   { "drop_cr",                  opt_bool,        &drop_cr },
 /*********************************************************/
+  { "dsn_advertise_hosts",      opt_stringptr,   &dsn_advertise_hosts },
   { "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 },
+#ifdef EXPERIMENTAL_EVENT
+  { "event_action",             opt_stringptr,   &event_action },
+#endif
   { "exim_group",               opt_gid,         &exim_gid },
   { "exim_path",                opt_stringptr,   &exim_path },
   { "exim_user",                opt_uid,         &exim_uid },
@@ -235,7 +247,10 @@ static optionlist optionlist_config[] = {
   { "gecos_name",               opt_stringptr,   &gecos_name },
   { "gecos_pattern",            opt_stringptr,   &gecos_pattern },
 #ifdef SUPPORT_TLS
+  { "gnutls_allow_auto_pkcs11", opt_bool,        &gnutls_allow_auto_pkcs11 },
   { "gnutls_compat_mode",       opt_bool,        &gnutls_compat_mode },
+  /* These three gnutls_require_* options stopped working in Exim 4.80 */
+  /* From 4.83 we log a warning; a future relase will remove them */
   { "gnutls_require_kx",        opt_stringptr,   &gnutls_require_kx },
   { "gnutls_require_mac",       opt_stringptr,   &gnutls_require_mac },
   { "gnutls_require_protocols", opt_stringptr,   &gnutls_require_proto },
@@ -262,7 +277,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 },
@@ -307,11 +329,17 @@ static optionlist optionlist_config[] = {
 #endif
   { "pid_file_path",            opt_stringptr,   &pid_file_path },
   { "pipelining_advertise_hosts", opt_stringptr, &pipelining_advertise_hosts },
+#ifndef DISABLE_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 },
   { "process_log_path",         opt_stringptr,   &process_log_path },
   { "prod_requires_admin",      opt_bool,        &prod_requires_admin },
+#ifdef EXPERIMENTAL_PROXY
+  { "proxy_required_hosts",     opt_stringptr,   &proxy_required_hosts },
+#endif
   { "qualify_domain",           opt_stringptr,   &qualify_domain_sender },
   { "qualify_recipient",        opt_stringptr,   &qualify_domain_recipient },
   { "queue_domains",            opt_stringptr,   &queue_domains },
@@ -330,6 +358,9 @@ static optionlist optionlist_config[] = {
   { "recipient_unqualified_hosts", opt_stringptr, &recipient_unqualified_hosts },
   { "recipients_max",           opt_int,         &recipients_max },
   { "recipients_max_reject",    opt_bool,        &recipients_max_reject },
+#ifdef EXPERIMENTAL_REDIS
+  { "redis_servers",            opt_stringptr,   &redis_servers },
+#endif
   { "remote_max_parallel",      opt_int,         &remote_max_parallel },
   { "remote_sort_domains",      opt_stringptr,   &remote_sort_domains },
   { "retry_data_expire",        opt_time,        &retry_data_expire },
@@ -408,8 +439,12 @@ static optionlist optionlist_config[] = {
   { "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 },
+# ifndef DISABLE_OCSP
+  { "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 },
@@ -460,7 +495,7 @@ for (i = 0; i < optionlist_config_size; i++)
 for (r = routers; r != NULL; r = r->next)
   {
   router_info *ri = r->info;
-  for (i = 0; i < ri->options_count[0]; i++)
+  for (i = 0; i < *ri->options_count; i++)
     {
     if ((ri->options[i].type & opt_mask) != opt_stringptr) continue;
     if (p == (char *)(r->options_block) + (long int)(ri->options[i].value))
@@ -471,11 +506,16 @@ for (r = routers; r != NULL; r = r->next)
 for (t = transports; t != NULL; t = t->next)
   {
   transport_info *ti = t->info;
-  for (i = 0; i < ti->options_count[0]; i++)
+  for (i = 0; i < *ti->options_count; i++)
     {
-    if ((ti->options[i].type & opt_mask) != opt_stringptr) continue;
-    if (p == (char *)(t->options_block) + (long int)(ti->options[i].value))
-      return US ti->options[i].name;
+    optionlist * op = &ti->options[i];
+    if ((op->type & opt_mask) != opt_stringptr) continue;
+    if (p == (  op->type & opt_public
+            ? (char *)t
+            : (char *)t->options_block
+            )
+            + (long int)op->value)
+       return US op->name;
     }
   }
 
@@ -514,7 +554,7 @@ 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);
+      "macro name too long (maximum is " SIZE_T_FMT " characters)", sizeof(name) - 1);
   name[namelen++] = *s++;
   }
 name[namelen] = 0;
@@ -1361,7 +1401,6 @@ uid_t uid;
 gid_t gid;
 BOOL boolvalue = TRUE;
 BOOL freesptr = TRUE;
-BOOL extra_condition = FALSE;
 optionlist *ol, *ol2;
 struct passwd *pw;
 void *reset_point;
@@ -1424,16 +1463,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 (Ustrcmp(mname, "condition") == 0)
-    extra_condition = TRUE;
-  else
-    log_write(0, LOG_PANIC_DIE|LOG_CONFIG_IN,
-      "\"%s\" option set for the second time", mname);
-  }
+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", name);
 
 ol->type |= opt_set | issecure;
 type = ol->type & opt_mask;
@@ -1454,7 +1486,7 @@ if (type < opt_bool || type > opt_bool_last)
   }
 
 /* If a boolean wasn't preceded by "no[t]_" it can be followed by = and
-true/false/yes/no, or, in the case of opt_expanded_bool, a general string that
+true/false/yes/no, or, in the case of opt_expand_bool, a general string that
 ultimately expands to one of those values. */
 
 else if (*s != 0 && (offset != 0 || *s != '='))
@@ -1517,7 +1549,7 @@ switch (type)
       str_target = (uschar **)(ol->value);
     else
       str_target = (uschar **)((uschar *)data_block + (long int)(ol->value));
-    if (extra_condition)
+    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
@@ -1526,9 +1558,10 @@ switch (type)
       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 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.
+      /* 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
@@ -1539,6 +1572,21 @@ switch (type)
       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 (ol->type & opt_rep_str)
+      {
+      uschar sep = Ustrncmp(name, "headers_add", 11)==0 ? '\n' : ':';
+      uschar * cp;
+
+      /* Strip trailing whitespace and seperators */
+      for (cp = sptr + Ustrlen(sptr) - 1;
+         cp >= sptr && (*cp == '\n' || *cp == '\t' || *cp == ' ' || *cp == sep);
+         cp--) *cp = '\0';
+
+      if (cp >= sptr)
+       *str_target = string_copy_malloc(
+                     *str_target ? string_sprintf("%s%c%s", *str_target, sep, sptr)
+                                 : sptr);
+      }
     else
       {
       *str_target = sptr;
@@ -2139,13 +2187,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;
@@ -2167,7 +2216,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;
   }
 
@@ -2186,11 +2239,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:
@@ -2205,23 +2260,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 */
@@ -2233,7 +2295,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);
@@ -2259,7 +2322,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;
       }
     }
@@ -2267,14 +2331,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;
 
@@ -2291,7 +2356,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;
       }
     }
@@ -2299,31 +2365,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 = ':';
       }
     }
@@ -2332,18 +2401,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 = ':';
       }
     }
@@ -2351,14 +2422,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");
@@ -2381,7 +2453,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 */
@@ -2427,12 +2500,13 @@ 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;
@@ -2459,8 +2533,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);
         }
       }
 
@@ -2483,7 +2560,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;
     }
@@ -2497,7 +2576,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;
@@ -2557,7 +2636,7 @@ if (type == NULL)
   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;
     }
   }
@@ -2630,14 +2709,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;
   }
@@ -2761,6 +2840,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        *
 *************************************************/
@@ -2862,7 +3006,12 @@ file is a serious disaster. */
 
 if (config_file != NULL)
   {
+  uschar *p;
   config_filename = config_main_filename = string_copy(filename);
+
+  p = Ustrrchr(filename, '/');
+  config_main_directory = p ? string_copyn(filename, p - filename) 
+                            : string_copy(US".");
   }
 else
   {
@@ -2874,22 +3023,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
@@ -3032,8 +3180,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)
   {
@@ -3057,7 +3205,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);
       }
@@ -3106,6 +3254,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. */
 
@@ -3179,9 +3332,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,
@@ -3202,20 +3360,37 @@ if ((tls_verify_hosts != NULL || tls_try_verify_hosts != NULL) &&
     "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\n");
+    "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\n", openssl_options);
+      "openssl_options parse error: %s", openssl_options);
 # endif
   }
-#endif
+
+if (gnutls_require_kx || gnutls_require_mac || gnutls_require_proto)
+  log_write(0, LOG_MAIN, "WARNING: main options"
+      " gnutls_require_kx, gnutls_require_mac and gnutls_require_protocols"
+      " are obsolete\n");
+#endif /*SUPPORT_TLS*/
 }
 
 
@@ -3597,7 +3772,7 @@ 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;
 }
@@ -3686,7 +3861,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,
-    "missing error type");
+    "missing error type in retry rule");
 
   /* Test error names for things we understand. */
 
@@ -3829,27 +4004,15 @@ return acl_line;
 
 /* Now the main function:
 
-Arguments:
-  skip        TRUE when this Exim process is doing something that will
-              not need the ACL data
-
+Arguments:    none
 Returns:      nothing
 */
 
 static void
-readconf_acl(BOOL skip)
+readconf_acl(void)
 {
 uschar *p;
 
-/* Not receiving messages, don't need to parse the ACL data */
-
-if (skip)
-  {
-  DEBUG(D_acl) debug_printf("skipping ACL configuration - not needed\n");
-  while ((p = get_config_line()) != NULL);
-  return;
-  }
-
 /* Read each ACL and add it into the tree. Macro (re)definitions are allowed
 between ACLs. */
 
@@ -3934,9 +4097,7 @@ Because it may confuse people as to whether the names are singular or plural,
 we add "s" if it's missing. There is always enough room in next_section for
 this. This function is basically just a switch.
 
-Arguments:
-  skip_acl   TRUE if ACL information is not needed
-
+Arguments:   none
 Returns:     nothing
 */
 
@@ -3950,7 +4111,7 @@ static uschar *section_list[] = {
   US"transports"};
 
 void
-readconf_rest(BOOL skip_acl)
+readconf_rest(void)
 {
 int had = 0;
 
@@ -3983,7 +4144,7 @@ while(next_section[0] != 0)
 
   switch(mid)
     {
-    case 0: readconf_acl(skip_acl); break;
+    case 0: readconf_acl(); break;
     case 1: auths_init(); break;
     case 2: local_scan_init(); break;
     case 3: readconf_retries(); break;
@@ -3996,4 +4157,6 @@ while(next_section[0] != 0)
 (void)fclose(config_file);
 }
 
+/* vi: aw ai sw=2
+*/
 /* End of readconf.c */