Bail out if a configuration file starts with a byte order mark
[exim.git] / src / src / readconf.c
index d1911a7b634289e69c31162613b89e9c8a496208..fd9657e0e763d0d3035fd22aa2bee32730fa57a5 100644 (file)
@@ -1,10 +1,8 @@
-/* $Cambridge: exim/src/src/readconf.c,v 1.22 2006/03/09 15:10:16 ph10 Exp $ */
-
 /*************************************************
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
-/* Copyright (c) University of Cambridge 1995 - 2006 */
+/* Copyright (c) University of Cambridge 1995 - 2016 */
 /* See the file NOTICE for conditions of use and distribution. */
 
 /* Functions for reading the configuration file, and for displaying
@@ -13,18 +11,39 @@ implementation of the conditional .ifdef etc. */
 
 #include "exim.h"
 
+extern char **environ;
+
+static void fn_smtp_receive_timeout(const uschar * name, const uschar * str);
+static void save_config_line(const uschar* line);
+static void save_config_position(const uschar *file, int line);
+static void print_config(BOOL admin, BOOL terse);
+static void readconf_options_auths(void);
+
+
 #define CSTATE_STACK_SIZE 10
 
+const uschar *config_directory = NULL;
+
 
 /* Structure for chain (stack) of .included files */
 
 typedef struct config_file_item {
   struct config_file_item *next;
-  uschar *filename;
+  const uschar *filename;
+  const uschar *directory;
   FILE *file;
   int lineno;
 } config_file_item;
 
+/* Structure for chain of configuration lines (-bP config) */
+
+typedef struct config_line_item {
+  struct config_line_item *next;
+  uschar *line;
+} config_line_item;
+
+static config_line_item* config_lines;
+
 /* Structure of table of conditional words and their state transitions */
 
 typedef struct cond_item {
@@ -42,6 +61,8 @@ typedef struct syslog_fac_item {
   int    value;
 } syslog_fac_item;
 
+/* constants */
+static const char * const hidden = "<value not displayable>";
 
 /* Static variables */
 
@@ -138,9 +159,16 @@ 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_PRDR
+  { "acl_smtp_data_prdr",       opt_stringptr,   &acl_smtp_data_prdr },
+#endif
+#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 +177,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 },
@@ -156,6 +185,7 @@ static optionlist optionlist_config[] = {
   { "acl_smtp_starttls",        opt_stringptr,   &acl_smtp_starttls },
 #endif
   { "acl_smtp_vrfy",            opt_stringptr,   &acl_smtp_vrfy },
+  { "add_environment",          opt_stringptr,   &add_environment },
   { "admin_groups",             opt_gidlist,     &admin_groups },
   { "allow_domain_literals",    opt_bool,        &allow_domain_literals },
   { "allow_mx_to_ip",           opt_bool,        &allow_mx_to_ip },
@@ -172,6 +202,7 @@ static optionlist optionlist_config[] = {
   { "bounce_message_file",      opt_stringptr,   &bounce_message_file },
   { "bounce_message_text",      opt_stringptr,   &bounce_message_text },
   { "bounce_return_body",       opt_bool,        &bounce_return_body },
+  { "bounce_return_linesize_limit", opt_mkint,   &bounce_return_linesize_limit },
   { "bounce_return_message",    opt_bool,        &bounce_return_message },
   { "bounce_return_size_limit", opt_mkint,       &bounce_return_size_limit },
   { "bounce_sender_authentication",opt_stringptr,&bounce_sender_authentication },
@@ -185,29 +216,56 @@ static optionlist optionlist_config[] = {
   { "check_rfc2047_length",     opt_bool,        &check_rfc2047_length },
   { "check_spool_inodes",       opt_int,         &check_spool_inodes },
   { "check_spool_space",        opt_Kint,        &check_spool_space },
+  { "chunking_advertise_hosts", opt_stringptr,  &chunking_advertise_hosts },
+  { "commandline_checks_require_admin", opt_bool,&commandline_checks_require_admin },
   { "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
+  { "debug_store",              opt_bool,        &debug_store },
   { "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
+#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 },
- /* This option is now a no-op, retained for compability */
+  { "dns_trust_aa",             opt_stringptr,   &dns_trust_aa },
+  { "dns_use_edns0",            opt_int,         &dns_use_edns0 },
+ /* This option is now a no-op, retained for compatibility */
   { "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 },
+#ifndef DISABLE_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 },
@@ -217,6 +275,10 @@ 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_allow_auto_pkcs11", opt_bool,        &gnutls_allow_auto_pkcs11 },
+  { "gnutls_compat_mode",       opt_bool,        &gnutls_compat_mode },
+#endif
   { "header_line_maxsize",      opt_int,         &header_line_maxsize },
   { "header_maxsize",           opt_int,         &header_maxsize },
   { "headers_charset",          opt_stringptr,   &headers_charset },
@@ -230,6 +292,9 @@ static optionlist optionlist_config[] = {
   { "host_lookup_order",        opt_stringptr,   &host_lookup_order },
   { "host_reject_connection",   opt_stringptr,   &host_reject_connection },
   { "hosts_connection_nolog",   opt_stringptr,   &hosts_connection_nolog },
+#ifdef SUPPORT_PROXY
+  { "hosts_proxy",              opt_stringptr,   &hosts_proxy },
+#endif
   { "hosts_treat_as_local",     opt_stringptr,   &hosts_treat_as_local },
 #ifdef LOOKUP_IBASE
   { "ibase_servers",            opt_stringptr,   &ibase_servers },
@@ -237,9 +302,17 @@ static optionlist optionlist_config[] = {
   { "ignore_bounce_errors_after", opt_time,      &ignore_bounce_errors_after },
   { "ignore_fromline_hosts",    opt_stringptr,   &ignore_fromline_hosts },
   { "ignore_fromline_local",    opt_bool,        &ignore_fromline_local },
+  { "keep_environment",         opt_stringptr,   &keep_environment },
   { "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 },
@@ -254,6 +327,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 },
@@ -267,6 +341,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
@@ -274,12 +351,16 @@ static optionlist optionlist_config[] = {
 #ifdef EXIM_PERL
   { "perl_at_start",            opt_bool,        &opt_perl_at_start },
   { "perl_startup",             opt_stringptr,   &opt_perl_startup },
+  { "perl_taintmode",           opt_bool,        &opt_perl_taintmode },
 #endif
 #ifdef LOOKUP_PGSQL
   { "pgsql_servers",            opt_stringptr,   &pgsql_servers },
 #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 },
@@ -292,9 +373,10 @@ 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 },
+  { "queue_run_max",            opt_stringptr,   &queue_run_max },
   { "queue_smtp_domains",       opt_stringptr,   &queue_smtp_domains },
   { "receive_timeout",          opt_time,        &receive_timeout },
   { "received_header_text",     opt_stringptr,   &received_header_text },
@@ -302,6 +384,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 LOOKUP_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 },
@@ -311,6 +396,7 @@ static optionlist optionlist_config[] = {
   { "rfc1413_hosts",            opt_stringptr,   &rfc1413_hosts },
   { "rfc1413_query_timeout",    opt_time,        &rfc1413_query_timeout },
   { "sender_unqualified_hosts", opt_stringptr,   &sender_unqualified_hosts },
+  { "slow_lookup_log",          opt_int,         &slow_lookup_log },
   { "smtp_accept_keepalive",    opt_bool,        &smtp_accept_keepalive },
   { "smtp_accept_max",          opt_int,         &smtp_accept_max },
   { "smtp_accept_max_nonmail",  opt_int,         &smtp_accept_max_nonmail },
@@ -333,14 +419,21 @@ static optionlist optionlist_config[] = {
   { "smtp_ratelimit_hosts",     opt_stringptr,   &smtp_ratelimit_hosts },
   { "smtp_ratelimit_mail",      opt_stringptr,   &smtp_ratelimit_mail },
   { "smtp_ratelimit_rcpt",      opt_stringptr,   &smtp_ratelimit_rcpt },
-  { "smtp_receive_timeout",     opt_time,        &smtp_receive_timeout },
+  { "smtp_receive_timeout",     opt_func,        &fn_smtp_receive_timeout },
   { "smtp_reserve_hosts",       opt_stringptr,   &smtp_reserve_hosts },
   { "smtp_return_error_details",opt_bool,        &smtp_return_error_details },
+#ifdef SUPPORT_I18N
+  { "smtputf8_advertise_hosts", opt_stringptr,   &smtputf8_advertise_hosts },
+#endif
 #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 },
+  { "spool_wireformat",         opt_bool,        &spool_wireformat },
 #ifdef LOOKUP_SQLITE
   { "sqlite_lock_timeout",      opt_int,         &sqlite_lock_timeout },
 #endif
@@ -353,10 +446,12 @@ static optionlist optionlist_config[] = {
   { "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 },
   { "syslog_facility",          opt_stringptr,   &syslog_facility_str },
+  { "syslog_pid",               opt_bool,        &syslog_pid },
   { "syslog_processname",       opt_stringptr,   &syslog_processname },
   { "syslog_timestamp",         opt_bool,        &syslog_timestamp },
   { "system_filter",            opt_stringptr,   &system_filter },
@@ -367,14 +462,22 @@ 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 },
+#ifdef SUPPORT_TLS
   { "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 },
+  { "tls_eccurve",              opt_stringptr,   &tls_eccurve },
+# 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 },
@@ -393,8 +496,7 @@ static optionlist optionlist_config[] = {
   { "write_rejectlog",          opt_bool,        &write_rejectlog }
 };
 
-static int optionlist_config_size =
-  sizeof(optionlist_config)/sizeof(optionlist);
+static int optionlist_config_size = nelem(optionlist_config);
 
 
 
@@ -419,13 +521,13 @@ int i;
 router_instance *r;
 transport_instance *t;
 
-for (i = 0; i < optionlist_config_size; i++)
+for (i = 0; i < nelem(optionlist_config); i++)
   if (p == optionlist_config[i].value) return US optionlist_config[i].name;
 
-for (r = routers; r != NULL; r = r->next)
+for (r = routers; r; 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))
@@ -433,14 +535,19 @@ for (r = routers; r != NULL; r = r->next)
     }
   }
 
-for (t = transports; t != NULL; t = t->next)
+for (t = transports; t; 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;
     }
   }
 
@@ -454,6 +561,45 @@ return US"";
 *       Deal with an assignment to a macro       *
 *************************************************/
 
+/* 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.
+If a builtin macro we place at head of list, else tail.  This lets us lazy-create
+builtins. */
+
+macro_item *
+macro_create(const uschar * name, const uschar * val,
+  BOOL command_line, BOOL builtin)
+{
+unsigned namelen = Ustrlen(name);
+macro_item * m = store_get(sizeof(macro_item) + namelen);
+
+/* fprintf(stderr, "%s: '%s' '%s'\n", __FUNCTION__, name, val) */
+if (!macros)
+  {
+  macros = m;
+  mlast = m;
+  m->next = NULL;
+  }
+else if (builtin)
+  {
+  m->next = macros;
+  macros = m;
+  }
+else
+  {
+  mlast->next = m;
+  mlast = m;
+  m->next = NULL;
+  }
+m->command_line = command_line;
+m->namelen = namelen;
+m->replacement = string_copy(val);
+Ustrcpy(m->name, name);
+return m;
+}
+
+
 /* 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
@@ -473,13 +619,12 @@ 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 %d characters)", sizeof(name) - 1);
+      "macro name too long (maximum is " SIZE_T_FMT " characters)", sizeof(name) - 1);
   name[namelen++] = *s++;
   }
 name[namelen] = 0;
@@ -499,13 +644,13 @@ while (isspace(*s)) s++;
 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. */
+previously defined macro.  This is the requirement that make using a tree
+for macros hard; we must check all macros for the substring.  Perhaps a
+sorted list, and a bsearch, would work?
+Note: it is documented that the other way round works. */
 
-for (m = macros; m != NULL; m = m->next)
+for (m = macros; m; m = m->next)
   {
-  int len = Ustrlen(m->name);
-
   if (Ustrcmp(m->name, name) == 0)
     {
     if (!m->command_line && !redef)
@@ -514,7 +659,7 @@ for (m = macros; m != NULL; m = m->next)
     break;
     }
 
-  if (len < namelen && Ustrstr(name, m->name) != NULL)
+  if (m->namelen < 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);
@@ -522,49 +667,243 @@ for (m = macros; m != NULL; m = m->next)
   /* 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)
+  * if (m->namelen > 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;
+if (m && m->command_line) return;
 
 /* Redefinition must refer to an existing macro. */
 
 if (redef)
-  {
-  if (m == NULL)
+  if (m)
+    m->replacement = string_copy(s);
+  else
     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. */
 
+/* We have a new definition. */
 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;
-  }
+  (void) macro_create(name, s, FALSE, FALSE);
+}
+
+
+
+
+
+/*************************************************/
+/* Create compile-time feature macros */
+static void
+readconf_features(void)
+{
+/* Probably we could work out a static initialiser for wherever
+macros are stored, but this will do for now. Some names are awkward
+due to conflicts with other common macros. */
+
+#ifdef SUPPORT_CRYPTEQ
+  macro_create(US"_HAVE_CRYPTEQ", US"y", FALSE, TRUE);
+#endif
+#if HAVE_ICONV
+  macro_create(US"_HAVE_ICONV", US"y", FALSE, TRUE);
+#endif
+#if HAVE_IPV6
+  macro_create(US"_HAVE_IPV6", US"y", FALSE, TRUE);
+#endif
+#ifdef HAVE_SETCLASSRESOURCES
+  macro_create(US"_HAVE_SETCLASSRESOURCES", US"y", FALSE, TRUE);
+#endif
+#ifdef SUPPORT_PAM
+  macro_create(US"_HAVE_PAM", US"y", FALSE, TRUE);
+#endif
+#ifdef EXIM_PERL
+  macro_create(US"_HAVE_PERL", US"y", FALSE, TRUE);
+#endif
+#ifdef EXPAND_DLFUNC
+  macro_create(US"_HAVE_DLFUNC", US"y", FALSE, TRUE);
+#endif
+#ifdef USE_TCP_WRAPPERS
+  macro_create(US"_HAVE_TCPWRAPPERS", US"y", FALSE, TRUE);
+#endif
+#ifdef SUPPORT_TLS
+  macro_create(US"_HAVE_TLS", US"y", FALSE, TRUE);
+# ifdef USE_GNUTLS
+  macro_create(US"_HAVE_GNUTLS", US"y", FALSE, TRUE);
+# else
+  macro_create(US"_HAVE_OPENSSL", US"y", FALSE, TRUE);
+# endif
+#endif
+#ifdef SUPPORT_TRANSLATE_IP_ADDRESS
+  macro_create(US"_HAVE_TRANSLATE_IP_ADDRESS", US"y", FALSE, TRUE);
+#endif
+#ifdef SUPPORT_MOVE_FROZEN_MESSAGES
+  macro_create(US"_HAVE_MOVE_FROZEN_MESSAGES", US"y", FALSE, TRUE);
+#endif
+#ifdef WITH_CONTENT_SCAN
+  macro_create(US"_HAVE_CONTENT_SCANNING", US"y", FALSE, TRUE);
+#endif
+#ifndef DISABLE_DKIM
+  macro_create(US"_HAVE_DKIM", US"y", FALSE, TRUE);
+#endif
+#ifndef DISABLE_DNSSEC
+  macro_create(US"_HAVE_DNSSEC", US"y", FALSE, TRUE);
+#endif
+#ifndef DISABLE_EVENT
+  macro_create(US"_HAVE_EVENT", US"y", FALSE, TRUE);
+#endif
+#ifdef SUPPORT_I18N
+  macro_create(US"_HAVE_I18N", US"y", FALSE, TRUE);
+#endif
+#ifndef DISABLE_OCSP
+  macro_create(US"_HAVE_OCSP", US"y", FALSE, TRUE);
+#endif
+#ifndef DISABLE_PRDR
+  macro_create(US"_HAVE_PRDR", US"y", FALSE, TRUE);
+#endif
+#ifdef SUPPORT_PROXY
+  macro_create(US"_HAVE_PROXY", US"y", FALSE, TRUE);
+#endif
+#ifdef SUPPORT_SOCKS
+  macro_create(US"_HAVE_SOCKS", US"y", FALSE, TRUE);
+#endif
+#ifdef TCP_FASTOPEN
+  macro_create(US"_HAVE_TCP_FASTOPEN", US"y", FALSE, TRUE);
+#endif
+#ifdef EXPERIMENTAL_LMDB
+  macro_create(US"_HAVE_LMDB", US"y", FALSE, TRUE);
+#endif
+#ifdef EXPERIMENTAL_SPF
+  macro_create(US"_HAVE_SPF", US"y", FALSE, TRUE);
+#endif
+#ifdef EXPERIMENTAL_SRS
+  macro_create(US"_HAVE_SRS", US"y", FALSE, TRUE);
+#endif
+#ifdef EXPERIMENTAL_BRIGHTMAIL
+  macro_create(US"_HAVE_BRIGHTMAIL", US"y", FALSE, TRUE);
+#endif
+#ifdef EXPERIMENTAL_DANE
+  macro_create(US"_HAVE_DANE", US"y", FALSE, TRUE);
+#endif
+#ifdef EXPERIMENTAL_DCC
+  macro_create(US"_HAVE_DCC", US"y", FALSE, TRUE);
+#endif
+#ifdef EXPERIMENTAL_DMARC
+  macro_create(US"_HAVE_DMARC", US"y", FALSE, TRUE);
+#endif
+#ifdef EXPERIMENTAL_DSN_INFO
+  macro_create(US"_HAVE_DSN_INFO", US"y", FALSE, TRUE);
+#endif
+
+#ifdef LOOKUP_LSEARCH
+  macro_create(US"_HAVE_LOOKUP_LSEARCH", US"y", FALSE, TRUE);
+#endif
+#ifdef LOOKUP_CDB
+  macro_create(US"_HAVE_LOOKUP_CDB", US"y", FALSE, TRUE);
+#endif
+#ifdef LOOKUP_DBM
+  macro_create(US"_HAVE_LOOKUP_DBM", US"y", FALSE, TRUE);
+#endif
+#ifdef LOOKUP_DNSDB
+  macro_create(US"_HAVE_LOOKUP_DNSDB", US"y", FALSE, TRUE);
+#endif
+#ifdef LOOKUP_DSEARCH
+  macro_create(US"_HAVE_LOOKUP_DSEARCH", US"y", FALSE, TRUE);
+#endif
+#ifdef LOOKUP_IBASE
+  macro_create(US"_HAVE_LOOKUP_IBASE", US"y", FALSE, TRUE);
+#endif
+#ifdef LOOKUP_LDAP
+  macro_create(US"_HAVE_LOOKUP_LDAP", US"y", FALSE, TRUE);
+#endif
+#ifdef EXPERIMENTAL_LMDB
+  macro_create(US"_HAVE_LOOKUP_LMDB", US"y", FALSE, TRUE);
+#endif
+#ifdef LOOKUP_MYSQL
+  macro_create(US"_HAVE_LOOKUP_MYSQL", US"y", FALSE, TRUE);
+#endif
+#ifdef LOOKUP_NIS
+  macro_create(US"_HAVE_LOOKUP_NIS", US"y", FALSE, TRUE);
+#endif
+#ifdef LOOKUP_NISPLUS
+  macro_create(US"_HAVE_LOOKUP_NISPLUS", US"y", FALSE, TRUE);
+#endif
+#ifdef LOOKUP_ORACLE
+  macro_create(US"_HAVE_LOOKUP_ORACLE", US"y", FALSE, TRUE);
+#endif
+#ifdef LOOKUP_PASSWD
+  macro_create(US"_HAVE_LOOKUP_PASSWD", US"y", FALSE, TRUE);
+#endif
+#ifdef LOOKUP_PGSQL
+  macro_create(US"_HAVE_LOOKUP_PGSQL", US"y", FALSE, TRUE);
+#endif
+#ifdef LOOKUP_REDIS
+  macro_create(US"_HAVE_LOOKUP_REDIS", US"y", FALSE, TRUE);
+#endif
+#ifdef LOOKUP_SQLITE
+  macro_create(US"_HAVE_LOOKUP_SQLITE", US"y", FALSE, TRUE);
+#endif
+#ifdef LOOKUP_TESTDB
+  macro_create(US"_HAVE_LOOKUP_TESTDB", US"y", FALSE, TRUE);
+#endif
+#ifdef LOOKUP_WHOSON
+  macro_create(US"_HAVE_LOOKUP_WHOSON", US"y", FALSE, TRUE);
+#endif
 
-/* Set the value of the new or redefined macro */
+#ifdef TRANSPORT_APPENDFILE
+# ifdef SUPPORT_MAILDIR
+  macro_create(US"_HAVE_TRANSPORT_APPEND_MAILDIR", US"y", FALSE, TRUE);
+# endif
+# ifdef SUPPORT_MAILSTORE
+  macro_create(US"_HAVE_TRANSPORT_APPEND_MAILSTORE", US"y", FALSE, TRUE);
+# endif
+# ifdef SUPPORT_MBX
+  macro_create(US"_HAVE_TRANSPORT_APPEND_MBX", US"y", FALSE, TRUE);
+# endif
+#endif
+}
 
-m->replacement = string_copy(s);
+
+void
+readconf_options_from_list(optionlist * opts, unsigned nopt, const uschar * section, uschar * group)
+{
+int i;
+const uschar * s;
+
+/* The 'previously-defined-substring' rule for macros in config file
+lines is done so for these builtin macros: we know that the table
+we source from is in strict alpha order, hence the builtins portion
+of the macros list is in reverse-alpha (we prepend them) - so longer
+macros that have substrings are always discovered first during
+expansion. */
+
+for (i = 0; i < nopt; i++)  if (*(s = US opts[i].name) && *s != '*')
+  if (group)
+    macro_create(string_sprintf("_OPT_%T_%T_%T", section, group, s), US"y", FALSE, TRUE);
+  else
+    macro_create(string_sprintf("_OPT_%T_%T", section, s), US"y", FALSE, TRUE);
 }
 
 
+static void
+readconf_options(void)
+{
+readconf_options_from_list(optionlist_config, nelem(optionlist_config), US"MAIN", NULL);
+readconf_options_routers();
+readconf_options_transports();
+readconf_options_auths();
+}
 
+static void
+macros_create_builtin(void)
+{
+readconf_features();
+readconf_options();
+macros_builtin_created = TRUE;
+}
 
 
 /*************************************************
@@ -611,8 +950,11 @@ for (;;)
       (void)fclose(config_file);
       config_file = config_file_stack->file;
       config_filename = config_file_stack->filename;
+      config_directory = config_file_stack->directory;
       config_lineno = config_file_stack->lineno;
       config_file_stack = config_file_stack->next;
+      if (config_lines)
+        save_config_position(config_filename, config_lineno);
       continue;
       }
 
@@ -630,6 +972,9 @@ for (;;)
   config_lineno++;
   newlen = len + Ustrlen(big_buffer + len);
 
+  if (config_lines && config_lineno == 1)
+    save_config_position(config_filename, config_lineno);
+
   /* Handle pathologically long physical lines - yes, it did happen - by
   extending big_buffer at this point. The code also copes with very long
   logical lines. */
@@ -671,11 +1016,29 @@ for (;;)
     if (*s != '=') s = ss;          /* Not a macro definition */
     }
 
+  /* If the builtin macros are not yet defined, and the line contains an
+  underscrore followed by an one of the three possible chars used by
+  builtins, create them. */
+
+  if (!macros_builtin_created)
+    {
+    const uschar * t, * p;
+    uschar c;
+    for (t = s; (p = CUstrchr(t, '_')); t = p+1)
+      if (c = p[1], c == 'O' || c == 'D' || c == 'H')
+       {
+/* fprintf(stderr, "%s: builtins create triggered by '%s'\n", __FUNCTION__, s); */
+       builtin_macros_create_trigger = string_copy(s);
+       macros_create_builtin();
+       break;
+       }
+    }
+
   /* For each defined macro, scan the line (from after XXX= if present),
   replacing all occurrences of the macro. */
 
   macro_found = FALSE;
-  for (m = macros; m != NULL; m = m->next)
+  for (m = macros; m; m = m->next)
     {
     uschar *p, *pp;
     uschar *t = s;
@@ -683,12 +1046,12 @@ for (;;)
     while ((p = Ustrstr(t, m->name)) != NULL)
       {
       int moveby;
-      int namelen = Ustrlen(m->name);
       int replen = Ustrlen(m->replacement);
 
+/* fprintf(stderr, "%s: matched '%s' in '%s'\n", __FUNCTION__, m->name, t) */
       /* Expand the buffer if necessary */
 
-      while (newlen - namelen + replen + 1 > big_buffer_size)
+      while (newlen - m->namelen + replen + 1 > big_buffer_size)
         {
         int newsize = big_buffer_size + BIG_BUFFER_SIZE;
         uschar *newbuffer = store_malloc(newsize);
@@ -706,9 +1069,8 @@ for (;;)
       copying in the replacement text. Don't rescan the replacement for this
       same macro. */
 
-      pp = p + namelen;
-      moveby = replen - namelen;
-      if (moveby != 0)
+      pp = p + m->namelen;
+      if ((moveby = replen - m->namelen) != 0)
         {
         memmove(p + replen, pp, (big_buffer + newlen) - pp + 1);
         newlen += moveby;
@@ -812,24 +1174,38 @@ for (;;)
       }
     *t = 0;
 
+    /* We allow relative file names. For security reasons currently
+    relative names not allowed with .include_if_exists. For .include_if_exists
+    we need to check the permissions/ownership of the containing folder */
     if (*ss != '/')
-      log_write(0, LOG_PANIC_DIE|LOG_CONFIG_IN, ".include specifies a non-"
-        "absolute path \"%s\"", ss);
+      if (include_if_exists) log_write(0, LOG_PANIC_DIE|LOG_CONFIG_IN, ".include specifies a non-"
+          "absolute path \"%s\"", ss);
+      else
+        {
+        int offset = 0;
+        int size = 0;
+        ss = string_append(NULL, &size, &offset, 3, config_directory, "/", ss);
+        ss[offset] = '\0';  /* string_append() does not zero terminate the string! */
+        }
 
     if (include_if_exists != 0 && (Ustat(ss, &statbuf) != 0)) continue;
 
+    if (config_lines)
+      save_config_position(config_filename, config_lineno);
     save = store_get(sizeof(config_file_item));
     save->next = config_file_stack;
     config_file_stack = save;
     save->file = config_file;
     save->filename = config_filename;
+    save->directory = config_directory;
     save->lineno = config_lineno;
 
-    config_file = Ufopen(ss, "rb");
-    if (config_file == NULL)
+    if (!(config_file = Ufopen(ss, "rb")))
       log_write(0, LOG_PANIC_DIE|LOG_CONFIG_IN, "failed to open included "
         "configuration file %s", ss);
+
     config_filename = string_copy(ss);
+    config_directory = string_copyn(ss, CUstrrchr(ss, '/') - ss);
     config_lineno = 0;
     continue;
     }
@@ -874,6 +1250,10 @@ next_section, truncate it. It will be unrecognized later, because all the known
 section names do fit. Leave space for pluralizing. */
 
 s = big_buffer + startoffset;            /* First non-space character */
+
+if (config_lines)
+  save_config_line(s);
+
 if (strncmpic(s, US"begin ", 6) == 0)
   {
   s += 6;
@@ -952,7 +1332,7 @@ Returns:        the time value, or -1 on syntax error
 */
 
 int
-readconf_readtime(uschar *s, int terminator, BOOL return_msec)
+readconf_readtime(const uschar *s, int terminator, BOOL return_msec)
 {
 int yield = 0;
 for (;;)
@@ -961,7 +1341,7 @@ for (;;)
   double fraction;
 
   if (!isdigit(*s)) return -1;
-  (void)sscanf(CS s, "%d%n", &value, &count);
+  (void)sscanf(CCS s, "%d%n", &value, &count);
   s += count;
 
   switch (*s)
@@ -975,7 +1355,7 @@ for (;;)
 
     case '.':
     if (!return_msec) return -1;
-    (void)sscanf(CS s, "%lf%n", &fraction, &count);
+    (void)sscanf(CCS s, "%lf%n", &fraction, &count);
     s += count;
     if (*s++ != 's') return -1;
     yield += (int)(fraction * 1000.0);
@@ -1007,7 +1387,7 @@ Returns:      the value, or -1 on error
 */
 
 static int
-readconf_readfixed(uschar *s, int terminator)
+readconf_readfixed(const uschar *s, int terminator)
 {
 int yield = 0;
 int value, count;
@@ -1052,9 +1432,10 @@ while (last > first)
   {
   int middle = (first + last)/2;
   int c = Ustrcmp(name, ol[middle].name);
+
   if (c == 0) return ol + middle;
-    else if (c > 0) first = middle + 1;
-      else last = middle;
+  else if (c > 0) first = middle + 1;
+  else last = middle;
   }
 return NULL;
 }
@@ -1113,7 +1494,7 @@ Returns:     doesn't return; dies
 */
 
 static void
-extra_chars_error(uschar *s, uschar *t1, uschar *t2, uschar *t3)
+extra_chars_error(const uschar *s, const uschar *t1, const uschar *t2, const uschar *t3)
 {
 uschar *comment = US"";
 if (*s == '#') comment = US" (# is comment only at line start)";
@@ -1149,7 +1530,7 @@ Returns:      the control block for the parsed rule.
 */
 
 static rewrite_rule *
-readconf_one_rewrite(uschar *p, int *existflags, BOOL isglobal)
+readconf_one_rewrite(const uschar *p, int *existflags, BOOL isglobal)
 {
 rewrite_rule *next = store_get(sizeof(rewrite_rule));
 
@@ -1255,10 +1636,10 @@ Returns:    pointer to the string
 */
 
 static uschar *
-read_string(uschar *s, uschar *name)
+read_string(const uschar *s, const uschar *name)
 {
 uschar *yield;
-uschar *ss;
+const uschar *ss;
 
 if (*s != '\"') return string_copy(s);
 
@@ -1275,6 +1656,24 @@ return yield;
 }
 
 
+/*************************************************
+*            Custom-handler options              *
+*************************************************/
+static void
+fn_smtp_receive_timeout(const uschar * name, const uschar * str)
+{
+if (*str == '$')
+  smtp_receive_timeout_s = string_copy(str);
+else
+  {
+  /* "smtp_receive_timeout",     opt_time,        &smtp_receive_timeout */
+  smtp_receive_timeout = readconf_readtime(str, 0, FALSE);
+  if (smtp_receive_timeout < 0)
+    log_write(0, LOG_PANIC_DIE|LOG_CONFIG_IN, "invalid time value for %s",
+      name);
+  }
+}
+
 /*************************************************
 *            Handle option line                  *
 *************************************************/
@@ -1333,6 +1732,7 @@ int intbase = 0;
 uschar *inttype = US"";
 uschar *sptr;
 uschar *s = buffer;
+uschar **str_target;
 uschar name[64];
 uschar name2[64];
 
@@ -1378,21 +1778,15 @@ if (Ustrncmp(name, "not_", 4) == 0)
 /* Search the list for the given name. A non-existent name, or an option that
 is set twice, is a disaster. */
 
-ol = find_option(name + offset, oltop, last);
-
-if (ol == NULL)
+if (!(ol = find_option(name + offset, oltop, last)))
   {
   if (unknown_txt == NULL) return FALSE;
   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;
@@ -1413,7 +1807,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 != '='))
@@ -1472,11 +1866,57 @@ switch (type)
     control block and flags word. */
 
     case opt_stringptr:
+    str_target = data_block ? USS (US data_block + (long int)(ol->value))
+                           : USS (ol->value);
+    if (ol->type & opt_rep_con)
+      {
+      uschar * saved_condition;
+      /* 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. */
+      *str_target = string_copy_malloc( (saved_condition = *str_target)
+       ? string_sprintf("${if and{{bool_lax{%s}}{bool_lax{%s}}}}",
+           saved_condition, sptr)
+       : sptr);
+      /* 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 (ol->type & opt_rep_str)
+      {
+      uschar sep_o = Ustrncmp(name, "headers_add", 11)==0 ? '\n' : ':';
+      int    sep_i = -(int)sep_o;
+      const uschar * list = sptr;
+      uschar * s;
+      uschar * list_o = *str_target;
+
+      while ((s = string_nextinlist(&list, &sep_i, NULL, 0)))
+       list_o = string_append_listele(list_o, sep_o, s);
+      if (list_o)
+       *str_target = string_copy_malloc(list_o);
+      }
+    else
+      {
+      *str_target = sptr;
+      freesptr = FALSE;
+      }
+    break;
+
     case opt_rewrite:
-    if (data_block == NULL)
-      *((uschar **)(ol->value)) = sptr;
+    if (data_block)
+      *USS (US data_block + (long int)(ol->value)) = sptr;
     else
-      *((uschar **)((uschar *)data_block + (long int)(ol->value))) = sptr;
+      *USS (ol->value) = sptr;
     freesptr = FALSE;
     if (type == opt_rewrite)
       {
@@ -1506,8 +1946,7 @@ switch (type)
         flagptr = (int *)((uschar *)data_block + (long int)(ol3->value));
         }
 
-      while ((p = string_nextinlist(&sptr, &sep, big_buffer, BIG_BUFFER_SIZE))
-              != NULL)
+      while ((p = string_nextinlist(CUSS &sptr, &sep, big_buffer, BIG_BUFFER_SIZE)))
         {
         rewrite_rule *next = readconf_one_rewrite(p, flagptr, FALSE);
         *chain = next;
@@ -1631,8 +2070,8 @@ switch (type)
       int count = 1;
       uid_t *list;
       int ptr = 0;
-      uschar *p;
-      uschar *op = expand_string (sptr);
+      const uschar *p;
+      const uschar *op = expand_string (sptr);
 
       if (op == NULL)
         log_write(0, LOG_PANIC_DIE|LOG_CONFIG_IN, "failed to expand %s: %s",
@@ -1672,8 +2111,8 @@ switch (type)
       int count = 1;
       gid_t *list;
       int ptr = 0;
-      uschar *p;
-      uschar *op = expand_string (sptr);
+      const uschar *p;
+      const uschar *op = expand_string (sptr);
 
       if (op == NULL)
         log_write(0, LOG_PANIC_DIE|LOG_CONFIG_IN, "failed to expand %s: %s",
@@ -1812,42 +2251,52 @@ switch (type)
   inttype = US"octal ";
 
   /*  Integer: a simple(ish) case; allow octal and hex formats, and
-  suffixes K and M. The different types affect output, not input. */
+  suffixes K, M and G. The different types affect output, not input. */
 
   case opt_mkint:
   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",
         inttype, name);
 
     if (errno != ERANGE)
-      {
       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++;
+        }
+      else if (tolower(*endptr) == 'g')
+        {
+        if (lvalue > INT_MAX/(1024*1024*1024) || lvalue < INT_MIN/(1024*1024*1024))
+          errno = ERANGE;
+        else lvalue *= 1024*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)
@@ -1856,8 +2305,9 @@ switch (type)
     *((int *)((uschar *)data_block + (long int)(ol->value))) = value;
   break;
 
-  /*  Integer held in K: again, allow octal and hex formats, and suffixes K and
-  M. */
+  /*  Integer held in K: again, allow octal and hex formats, and suffixes K, M
+  and G. */
+  /*XXX consider moving to int_eximarith_t (but mind the overflow test 0415) */
 
   case opt_Kint:
     {
@@ -1870,22 +2320,26 @@ switch (type)
         inttype, name);
 
     if (errno != ERANGE)
-      {
-      if (tolower(*endptr) == 'm')
+      if (tolower(*endptr) == 'g')
         {
-        if (value > INT_MAX/1024 || value < INT_MIN/1024) errno = ERANGE;
-          else value *= 1024;
+        if (value > INT_MAX/(1024*1024) || value < INT_MIN/(1024*1024))
+         errno = ERANGE;
+       else
+         value *= 1024*1024;
         endptr++;
         }
-      else if (tolower(*endptr) == 'k')
+      else if (tolower(*endptr) == 'm')
         {
+        if (value > INT_MAX/1024 || value < INT_MIN/1024)
+         errno = ERANGE;
+       else
+         value *= 1024;
         endptr++;
         }
+      else if (tolower(*endptr) == 'k')
+        endptr++;
       else
-        {
         value = (value + 512)/1024;
-        }
-      }
 
     if (errno == ERANGE) log_write(0, LOG_PANIC_DIE|LOG_CONFIG_IN,
       "absolute value of integer \"%s\" is too large (overflow)", s);
@@ -1916,6 +2370,11 @@ switch (type)
   if (value < 0) log_write(0, LOG_PANIC_DIE|LOG_CONFIG_IN,
     "integer \"%s\" is too large (overflow)", s);
 
+  /* We get a coverity error here for using count, as it derived
+  from the tainted buffer pointed to by s, as parsed by sscanf().
+  By the definition of sscanf we must be accessing between start
+  and end of s (assuming it is nul-terminated...) so ignore the error.  */
+  /* coverity[tainted_data] */
   if (s[count] == '.')
     {
     int d = 100;
@@ -1989,9 +2448,15 @@ switch (type)
         name);
     if (count > 0 && list[2] == 0) count = 0;
     list[1] = count;
+    break;
     }
 
-  break;
+  case opt_func:
+    {
+    void (*fn)() = ol->value;
+    fn(name, s);
+    break;
+    }
   }
 
 return TRUE;
@@ -2060,13 +2525,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;
@@ -2088,7 +2554,10 @@ if (ol == NULL)
 
 if (!admin_user && (ol->type & opt_secure) != 0)
   {
-  printf("%s = <value not displayable>\n", name);
+  if (no_labels)
+    printf("%s\n", hidden);
+  else
+    printf("%s = %s\n", name, hidden);
   return;
   }
 
@@ -2107,11 +2576,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:
@@ -2126,23 +2597,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 */
@@ -2154,7 +2632,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);
@@ -2180,7 +2659,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;
       }
     }
@@ -2188,14 +2668,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;
 
@@ -2212,7 +2693,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;
       }
     }
@@ -2220,31 +2702,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 = ':';
       }
     }
@@ -2253,18 +2738,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 = ':';
       }
     }
@@ -2272,14 +2759,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");
@@ -2302,7 +2790,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 */
@@ -2329,34 +2818,42 @@ causes the value of any main configuration variable to be output if the
 second argument is NULL. There are some special values:
 
   all                print all main configuration options
-  configure_file     print the name of the configuration file
+  config_file        print the name of the configuration file
+                     (configure_file will still work, for backward
+                     compatibility)
   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
+  config             print the configuration as it is parsed
+  environment        print the used execution environment
 
-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)
@@ -2377,8 +2874,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);
         }
       }
 
@@ -2389,7 +2889,8 @@ if (type == NULL)
     return;
     }
 
-  if (Ustrcmp(name, "configure_file") == 0)
+  if (  Ustrcmp(name, "configure_file") == 0
+     || Ustrcmp(name, "config_file") == 0)
     {
     printf("%s\n", CS config_main_filename);
     return;
@@ -2398,10 +2899,12 @@ if (type == NULL)
   if (Ustrcmp(name, "all") == 0)
     {
     for (ol = optionlist_config;
-         ol < optionlist_config + optionlist_config_size; ol++)
+         ol < optionlist_config + nelem(optionlist_config); 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, nelem(optionlist_config),
+            no_labels);
       }
     return;
     }
@@ -2415,12 +2918,18 @@ 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;
     }
 
+  if (Ustrcmp(name, "config") == 0)
+    {
+    print_config(admin_user, no_labels);
+    return;
+    }
+
   if (Ustrcmp(name, "routers") == 0)
     {
     type = US"router";
@@ -2438,11 +2947,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)
@@ -2451,16 +2959,50 @@ 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 if (Ustrcmp(name, "environment") == 0)
+    {
+    if (environ)
+      {
+      uschar ** p;
+      for (p = USS environ; *p; p++) ;
+      qsort(environ, p - USS environ, sizeof(*p), string_compare_by_pointer);
+
+      for (p = USS environ; *p; p++)
+        {
+       uschar * q;
+        if (no_labels && (q = Ustrchr(*p, '='))) *q  = '\0';
+        puts(CS *p);
+        }
+      }
+    return;
+    }
+
   else
     {
-    print_ol(find_option(name, optionlist_config, optionlist_config_size),
-      name, NULL, optionlist_config, optionlist_config_size);
+    print_ol(find_option(name, optionlist_config, nelem(optionlist_config)),
+      name, NULL, optionlist_config, nelem(optionlist_config), no_labels);
     return;
     }
   }
@@ -2490,6 +3032,31 @@ 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);
+    }
+  if (!macros_builtin_created) macros_create_builtin();
+  for (m = macros; m; m = m->next)
+    if (!name || 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)
+        return;
+      }
+  if (name)
+    printf("%s %s not found\n", type, name);
+  return;
+  }
+
 if (names_only)
   {
   for (; d != NULL; d = d->next) printf("%s\n", CS d->name);
@@ -2507,14 +3074,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;
   }
@@ -2638,6 +3205,80 @@ 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(BOOL nowarn)
+{
+const uschar *errmsg;
+pid_t pid;
+int rc, status;
+void (*oldsignal)(int);
+
+/* If TLS will never be used, no point checking ciphers */
+
+if (  !tls_advertise_hosts
+   || !*tls_advertise_hosts
+   || Ustrcmp(tls_advertise_hosts, ":") == 0
+   )
+  return TRUE;
+else if (!nowarn && !tls_certificate)
+  log_write(0, LOG_MAIN,
+    "Warning: No server certificate defined; will use a selfsigned one.\n"
+    " Suggested action: either install a certificate or change tls_advertise_hosts option");
+
+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");
+
+  if ((errmsg = tls_validate_require_cipher()))
+    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        *
 *************************************************/
@@ -2667,18 +3308,18 @@ systems. Therefore they are available only when requested by compile-time
 options. */
 
 void
-readconf_main(void)
+readconf_main(BOOL nowarn)
 {
 int sep = 0;
 struct stat statbuf;
 uschar *s, *filename;
-uschar *list = config_main_filelist;
+const uschar *list = config_main_filelist;
 
 /* Loop through the possible file names */
 
-while((filename = string_nextinlist(&list, &sep, big_buffer, big_buffer_size))
-       != NULL)
+while((filename = string_nextinlist(&list, &sep, big_buffer, big_buffer_size)))
   {
+
   /* Cut out all the fancy processing unless specifically wanted */
 
   #if defined(CONFIGURE_FILE_USE_NODE) || defined(CONFIGURE_FILE_USE_EUID)
@@ -2737,9 +3378,45 @@ logging configuration errors (it changes for .included files) whereas
 config_main_filename is the name shown by -bP. Failure to open a configuration
 file is a serious disaster. */
 
-if (config_file != NULL)
+if (config_file)
   {
+  uschar *last_slash = Ustrrchr(filename, '/');
   config_filename = config_main_filename = string_copy(filename);
+
+  /* The config_main_directory we need for the $config_dir expansion.
+  config_main_filename we need for $config_file expansion.
+  And config_dir is the directory of the current configuration, used for
+  relative .includes. We do need to know it's name, as we change our working
+  directory later. */
+
+  if (filename[0] == '/')
+    config_main_directory = last_slash == filename ? US"/" : string_copyn(filename, last_slash - filename);
+  else
+    {
+      /* relative configuration file name: working dir + / + basename(filename) */
+
+      uschar buf[PATH_MAX];
+      int offset = 0;
+      int size = 0;
+
+      if (os_getcwd(buf, PATH_MAX) == NULL)
+        {
+        perror("exim: getcwd");
+        exit(EXIT_FAILURE);
+        }
+      config_main_directory = string_cat(NULL, &size, &offset, buf);
+
+      /* If the dir does not end with a "/", append one */
+      if (config_main_directory[offset-1] != '/')
+        config_main_directory = string_catn(config_main_directory, &size, &offset, US"/", 1);
+
+      /* If the config file contains a "/", extract the directory part */
+      if (last_slash)
+        config_main_directory = string_catn(config_main_directory, &size, &offset, filename, last_slash - filename);
+
+      config_main_directory[offset] = '\0';
+    }
+  config_directory = config_main_directory;
   }
 else
   {
@@ -2751,22 +3428,30 @@ 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. */
+/* Now, once we found and opened our configuration file, we change the directory
+to a safe place. Later we change to $spool_directory. */
+
+if (Uchdir("/") < 0)
+  {
+  perror("exim: chdir `/': ");
+  exit(EXIT_FAILURE);
+  }
+
+/* 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
@@ -2784,6 +3469,11 @@ a macro definition. */
 
 while ((s = get_config_line()) != NULL)
   {
+
+  if (config_lineno == 1 && Ustrstr(s, "\xef\xbb\xbf") == s)
+    log_write(0, LOG_PANIC_DIE|LOG_CONFIG_IN,
+      "found unexpected BOM (Byte Order Mark)");
+
   if (isupper(s[0])) read_macro_assignment(s);
 
   else if (Ustrncmp(s, "domainlist", 10) == 0)
@@ -2840,7 +3530,7 @@ don't force the case. */
 
 if (primary_hostname == NULL)
   {
-  uschar *hostname;
+  const uschar *hostname;
   struct utsname uts;
   if (uname(&uts) < 0)
     log_write(0, LOG_MAIN|LOG_PANIC_DIE, "uname() failed to yield host name");
@@ -2853,8 +3543,8 @@ if (primary_hostname == NULL)
 
     #if HAVE_IPV6
     if (!disable_ipv6 && (dns_ipv4_lookup == NULL ||
-         match_isinlist(hostname, &dns_ipv4_lookup, 0, NULL, NULL, MCL_DOMAIN,
-           TRUE, NULL) != OK))
+         match_isinlist(hostname, CUSS &dns_ipv4_lookup, 0, NULL, NULL,
+           MCL_DOMAIN, TRUE, NULL) != OK))
       af = AF_INET6;
     #else
     af = AF_INET;
@@ -2909,12 +3599,12 @@ 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)
   {
-  uschar *ss, *sss;
+  const uschar *ss, *sss;
   int sep = ':';                       /* Fixed for log file path */
   s = expand_string(log_file_path);
   if (s == NULL)
@@ -2934,7 +3624,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);
       }
@@ -2983,6 +3673,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. */
 
@@ -3056,9 +3751,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,
@@ -3078,7 +3778,38 @@ 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_");
-#endif
+
+/* 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(nowarn))
+  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)
+  {
+# 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 /*SUPPORT_TLS*/
+
+if (!nowarn && !keep_environment && environ && *environ)
+  log_write(0, LOG_MAIN,
+      "Warning: purging the environment.\n"
+      " Suggested action: use keep_environment.");
 }
 
 
@@ -3191,9 +3922,9 @@ while ((buffer = get_config_line()) != NULL)
 
   if (isupper(*name) && *s == '=')
     {
-    if (d != NULL)
+    if (d)
       {
-      if (d->driver_name == NULL)
+      if (!d->driver_name)
         log_write(0, LOG_PANIC_DIE|LOG_CONFIG,
           "no driver defined for %s \"%s\"", class, d->name);
       (d->info->init)(d);
@@ -3213,9 +3944,9 @@ while ((buffer = get_config_line()) != NULL)
 
     /* Finish off initializing the previous driver. */
 
-    if (d != NULL)
+    if (d)
       {
-      if (d->driver_name == NULL)
+      if (!d->driver_name)
         log_write(0, LOG_PANIC_DIE|LOG_CONFIG,
           "no driver defined for %s \"%s\"", class, d->name);
       (d->info->init)(d);
@@ -3223,7 +3954,7 @@ while ((buffer = get_config_line()) != NULL)
 
     /* Check that we haven't already got a driver of this name */
 
-    for (d = *anchor; d != NULL; d = d->next)
+    for (d = *anchor; d; d = d->next)
       if (Ustrcmp(name, d->name) == 0)
         log_write(0, LOG_PANIC_DIE|LOG_CONFIG,
           "there are two %ss called \"%s\"", class, name);
@@ -3234,7 +3965,7 @@ while ((buffer = get_config_line()) != NULL)
     d = store_get(instance_size);
     memcpy(d, instance_default, instance_size);
     *p = d;
-    p = &(d->next);
+    p = &d->next;
     d->name = string_copy(name);
 
     /* Clear out the "set" bits in the generic options */
@@ -3252,8 +3983,8 @@ while ((buffer = get_config_line()) != NULL)
   /* 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);
+  if (!d)
+    log_write(0, LOG_PANIC_DIE|LOG_CONFIG_IN, "%s name missing", class);
 
   /* First look to see if this is a generic option; if it is "driver",
   initialize the driver. If is it not a generic option, we can look for a
@@ -3262,7 +3993,7 @@ while ((buffer = get_config_line()) != NULL)
   if (readconf_handle_option(buffer, driver_optionlist,
         driver_optionlist_count, d, NULL))
     {
-    if (d->info == NULL && d->driver_name != NULL)
+    if (!d->info && d->driver_name)
       init_driver(d, drivers_available, size_of_info, class);
     }
 
@@ -3270,11 +4001,9 @@ while ((buffer = get_config_line()) != NULL)
   live therein. A flag with each option indicates if it is in the public
   block. */
 
-  else if (d->info != NULL)
-    {
+  else if (d->info)
     readconf_handle_option(buffer, d->info->options,
       *(d->info->options_count), d, US"option \"%s\" unknown");
-    }
 
   /* The option is not generic and the driver name has not yet been given. */
 
@@ -3284,9 +4013,9 @@ while ((buffer = get_config_line()) != NULL)
 
 /* Run the initialization function for the final driver. */
 
-if (d != NULL)
+if (d)
   {
-  if (d->driver_name == NULL)
+  if (!d->driver_name)
     log_write(0, LOG_PANIC_DIE|LOG_CONFIG,
       "no driver defined for %s \"%s\"", class, d->name);
   (d->info->init)(d);
@@ -3360,10 +4089,11 @@ Returns:       NULL if decoded correctly; else points to error text
 */
 
 uschar *
-readconf_retry_error(uschar *pp, uschar *p, int *basic_errno, int *more_errno)
+readconf_retry_error(const uschar *pp, const uschar *p,
+  int *basic_errno, int *more_errno)
 {
 int len;
-uschar *q = pp;
+const uschar *q = pp;
 while (q < p && *q != '_') q++;
 len = q - pp;
 
@@ -3392,7 +4122,7 @@ else if (len == 7 && strncmpic(pp, US"timeout", len) == 0)
     {
     int i;
     int xlen = p - q - 1;
-    uschar *x = q + 1;
+    const uschar *x = q + 1;
 
     static uschar *extras[] =
       { US"A", US"MX", US"connect", US"connect_A",  US"connect_MX" };
@@ -3400,24 +4130,19 @@ else if (len == 7 && strncmpic(pp, US"timeout", len) == 0)
       { 'A',   'M',    RTEF_CTOUT,  RTEF_CTOUT|'A', RTEF_CTOUT|'M' };
 
     for (i = 0; i < sizeof(extras)/sizeof(uschar *); i++)
-      {
       if (strncmpic(x, extras[i], xlen) == 0)
         {
         *more_errno = values[i];
         break;
         }
-      }
 
     if (i >= sizeof(extras)/sizeof(uschar *))
-      {
       if (strncmpic(x, US"DNS", xlen) == 0)
-        {
         log_write(0, LOG_MAIN|LOG_PANIC, "\"timeout_dns\" is no longer "
           "available in retry rules (it has never worked) - treated as "
           "\"timeout\"");
-        }
-      else return US"\"A\", \"MX\", or \"connect\" expected after \"timeout\"";
-      }
+      else
+        return US"\"A\", \"MX\", or \"connect\" expected after \"timeout\"";
     }
   }
 
@@ -3444,8 +4169,8 @@ else if (strncmpic(pp, US"mail_4", 6) == 0 ||
     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 = (*pp == 'm')? ERRNO_MAIL4XX :
-                 (*pp == 'r')? ERRNO_RCPT4XX : ERRNO_DATA4XX;
+  *basic_errno = *pp == 'm' ? ERRNO_MAIL4XX :
+                 *pp == 'r' ? ERRNO_RCPT4XX : ERRNO_DATA4XX;
   *more_errno = x << 8;
   }
 
@@ -3453,14 +4178,17 @@ 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 (strcmpic(pp, US"lost_connection") == 0)
+else if (strncmpic(pp, US"lost_connection", p - pp) == 0)
   *basic_errno = ERRNO_SMTPCLOSED;
 
-else if (strcmpic(pp, US"tls_required") == 0)
+else if (strncmpic(pp, US"tls_required", p - pp) == 0)
   *basic_errno = ERRNO_TLSREQUIRED;
 
+else if (strncmpic(pp, US"lookup", p - pp) == 0)
+  *basic_errno = ERRNO_UNKNOWNHOST;
+
 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;
 }
@@ -3496,10 +4224,10 @@ Returns:    time in seconds or fixed point number * 1000
 */
 
 static int
-retry_arg(uschar **paddr, int type)
+retry_arg(const uschar **paddr, int type)
 {
-uschar *p = *paddr;
-uschar *pp;
+const uschar *p = *paddr;
+const uschar *pp;
 
 if (*p++ != ',') log_write(0, LOG_PANIC_DIE|LOG_CONFIG_IN, "comma expected");
 
@@ -3513,10 +4241,8 @@ if (*p != 0 && !isspace(*p) && *p != ',' && *p != ';')
 *paddr = p;
 switch (type)
   {
-  case 0:
-  return readconf_readtime(pp, *p, FALSE);
-  case 1:
-  return readconf_readfixed(pp, *p);
+  case 0: return readconf_readtime(pp, *p, FALSE);
+  case 1: return readconf_readfixed(pp, *p);
   }
 return 0;    /* Keep picky compilers happy */
 }
@@ -3528,12 +4254,13 @@ readconf_retries(void)
 {
 retry_config **chain = &retries;
 retry_config *next;
-uschar *p;
+const uschar *p;
 
-while ((p = get_config_line()) != NULL)
+while ((p = get_config_line()))
   {
   retry_rule **rchain;
-  uschar *pp, *error;
+  const uschar *pp;
+  uschar *error;
 
   next = store_get(sizeof(retry_config));
   next->next = NULL;
@@ -3549,12 +4276,12 @@ 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. */
 
-  if ((error = readconf_retry_error(pp, p, &(next->basic_errno),
-       &(next->more_errno))) != NULL)
+  if ((error = readconf_retry_error(pp, p, &next->basic_errno,
+       &next->more_errno)))
     log_write(0, LOG_PANIC_DIE|LOG_CONFIG_IN, "%s", error);
 
   /* There may be an optional address list of senders to be used as another
@@ -3591,18 +4318,18 @@ while ((p = get_config_line()) != NULL)
     switch (rule->rule)
       {
       case 'F':   /* Fixed interval */
-      rule->p1 = retry_arg(&p, 0);
-      break;
+       rule->p1 = retry_arg(&p, 0);
+       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;
+       rule->p1 = retry_arg(&p, 0);
+       rule->p2 = retry_arg(&p, 1);
+       break;
 
       default:
-      log_write(0, LOG_PANIC_DIE|LOG_CONFIG_IN, "unknown retry rule letter");
-      break;
+       log_write(0, LOG_PANIC_DIE|LOG_CONFIG_IN, "unknown retry rule letter");
+       break;
       }
 
     if (rule->timeout <= 0 || rule->p1 <= 0 ||
@@ -3628,6 +4355,21 @@ while ((p = get_config_line()) != NULL)
 *         Initialize authenticators              *
 *************************************************/
 
+static void
+readconf_options_auths(void)
+{
+struct auth_info * ai;
+
+readconf_options_from_list(optionlist_auths, optionlist_auths_size, US"AUTHENTICATORS", NULL);
+
+for (ai = auths_available; ai->driver_name[0]; ai++)
+  {
+  macro_create(string_sprintf("_DRIVER_AUTHENTICATOR_%T", ai->driver_name), US"y", FALSE, TRUE);
+  readconf_options_from_list(ai->options, (unsigned)*ai->options_count, US"AUTHENTICATOR", ai->driver_name);
+  }
+}
+
+
 /* Read the authenticators section of the configuration file.
 
 Arguments:   none
@@ -3638,6 +4380,7 @@ static void
 auths_init(void)
 {
 auth_instance *au, *bu;
+
 readconf_driver_init(US"authenticator",
   (driver_instance **)(&auths),      /* chain anchor */
   (driver_info *)auths_available,    /* available drivers */
@@ -3647,22 +4390,19 @@ readconf_driver_init(US"authenticator",
   optionlist_auths,                  /* generic options */
   optionlist_auths_size);
 
-for (au = auths; au != NULL; au = au->next)
+for (au = auths; au; au = au->next)
   {
-  if (au->public_name == NULL)
+  if (!au->public_name)
     log_write(0, LOG_PANIC_DIE|LOG_CONFIG, "no public name specified for "
       "the %s authenticator", au->name);
-  for (bu = au->next; bu != NULL; bu = bu->next)
-    {
+
+  for (bu = au->next; bu; bu = bu->next)
     if (strcmpic(au->public_name, bu->public_name) == 0)
-      {
       if ((au->client && bu->client) || (au->server && bu->server))
         log_write(0, LOG_PANIC_DIE|LOG_CONFIG, "two %s authenticators "
           "(%s and %s) have the same public name (%s)",
-          (au->client)? US"client" : US"server", au->name, bu->name,
+          au->client ? US"client" : US"server", au->name, bu->name,
           au->public_name);
-      }
-    }
   }
 }
 
@@ -3692,27 +4432,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. */
 
@@ -3797,9 +4525,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
 */
 
@@ -3813,7 +4539,7 @@ static uschar *section_list[] = {
   US"transports"};
 
 void
-readconf_rest(BOOL skip_acl)
+readconf_rest(void)
 {
 int had = 0;
 
@@ -3846,7 +4572,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;
@@ -3859,4 +4585,122 @@ while(next_section[0] != 0)
 (void)fclose(config_file);
 }
 
+/* Init the storage for the pre-parsed config lines */
+void
+readconf_save_config(const uschar *s)
+{
+  save_config_line(string_sprintf("# Exim Configuration (%s)",
+    running_in_test_harness ? US"X" : s));
+}
+
+static void
+save_config_position(const uschar *file, int line)
+{
+  save_config_line(string_sprintf("# %d \"%s\"", line, file));
+}
+
+/* Append a pre-parsed logical line to the config lines store,
+this operates on a global (static) list that holds all the pre-parsed
+config lines, we do no further processing here, output formatting and
+honouring of <hide> or macros will be done during output */
+static void
+save_config_line(const uschar* line)
+{
+static config_line_item *current;
+config_line_item *next;
+
+next = (config_line_item*) store_get(sizeof(config_line_item));
+next->line = string_copy(line);
+next->next = NULL;
+
+if (!config_lines) config_lines = next;
+else current->next = next;
+
+current = next;
+}
+
+/* List the parsed config lines, care about nice formatting and
+hide the <hide> values unless we're the admin user */
+void
+print_config(BOOL admin, BOOL terse)
+{
+config_line_item *i;
+const int TS = terse ? 0 : 2;
+int indent = 0;
+
+for (i = config_lines; i; i = i->next)
+  {
+  uschar *current;
+  uschar *p;
+
+  /* skip over to the first non-space */
+  for (current = i->line; *current && isspace(*current); ++current)
+    ;
+
+  if (*current == '\0')
+    continue;
+
+  /* Collapse runs of spaces. We stop this if we encounter one of the
+   * following characters: "'$, as this may indicate careful formatting */
+  for (p = current; *p; ++p)
+    {
+    uschar *next;
+    if (!isspace(*p)) continue;
+    if (*p != ' ') *p = ' ';
+
+    for (next = p; isspace(*next); ++next)
+      ;
+
+    if (next - p > 1)
+      memmove(p+1, next, Ustrlen(next)+1);
+
+    if (*next == '"' || *next == '\'' || *next == '$')
+      break;
+    }
+
+  /* # lines */
+  if (current[0] == '#')
+    puts(CCS current);
+
+  /* begin lines are left aligned */
+  else if (Ustrncmp(current, "begin", 5) == 0 && isspace(current[5]))
+    {
+    if (!terse) puts("");
+    puts(CCS current);
+    indent = TS;
+    }
+
+  /* router/acl/transport block names */
+  else if (current[Ustrlen(current)-1] == ':' && !Ustrchr(current, '='))
+    {
+    if (!terse) puts("");
+    printf("%*s%s\n", TS, "", current);
+    indent = 2 * TS;
+    }
+
+  /* hidden lines (all MACROS or lines prefixed with "hide") */
+  else if (  !admin
+         && (  isupper(*current)
+            || Ustrncmp(current, "hide", 4) == 0 && isspace(current[4])
+            )
+         )
+    {
+    if ((p = Ustrchr(current, '=')))
+      {
+      *p = '\0';
+      printf("%*s%s= %s\n", indent, "", current, hidden);
+      }
+    /* e.g.: hide split_spool_directory */
+    else
+      printf("%*s\n", indent, hidden);
+    }
+
+  else
+    /* rest is public */
+    printf("%*s%s\n", indent, "", current);
+  }
+}
+
+/* vi: aw ai sw=2
+*/
 /* End of readconf.c */