Testsuite: add delays for predictable log sequencing in 2x36, log-sorting for 2x38
[exim.git] / src / src / readconf.c
index 81e3bc267c7bfe75e552dae4be441fc7b34ddbee..95abaf5be8d25bf5513ceb10dd2fe1930f28b9ac 100644 (file)
@@ -2,7 +2,7 @@
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
-/* Copyright (c) University of Cambridge 1995 - 2013 */
+/* 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
@@ -11,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 {
@@ -40,6 +61,8 @@ typedef struct syslog_fac_item {
   int    value;
 } syslog_fac_item;
 
+/* constants */
+static const char * const hidden = "<value not displayable>";
 
 /* Static variables */
 
@@ -140,7 +163,7 @@ 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 },
-#ifdef EXPERIMENTAL_PRDR
+#ifndef DISABLE_PRDR
   { "acl_smtp_data_prdr",       opt_stringptr,   &acl_smtp_data_prdr },
 #endif
 #ifndef DISABLE_DKIM
@@ -162,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 },
@@ -178,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 },
@@ -191,6 +216,8 @@ 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 },
@@ -200,6 +227,7 @@ static optionlist optionlist_config[] = {
   { "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 },
@@ -225,14 +253,19 @@ static optionlist optionlist_config[] = {
   { "dns_ipv4_lookup",          opt_stringptr,   &dns_ipv4_lookup },
   { "dns_retrans",              opt_time,        &dns_retrans },
   { "dns_retry",                opt_int,         &dns_retry },
+  { "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 compability */
+ /* 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 },
@@ -245,10 +278,6 @@ static optionlist optionlist_config[] = {
 #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 */
-  { "gnutls_require_kx",        opt_stringptr,   &gnutls_require_kx },
-  { "gnutls_require_mac",       opt_stringptr,   &gnutls_require_mac },
-  { "gnutls_require_protocols", opt_stringptr,   &gnutls_require_proto },
 #endif
   { "header_line_maxsize",      opt_int,         &header_line_maxsize },
   { "header_maxsize",           opt_int,         &header_maxsize },
@@ -263,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 },
@@ -270,6 +302,7 @@ 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 },
@@ -318,13 +351,14 @@ 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 },
-#ifdef EXPERIMENTAL_PRDR
+#ifndef DISABLE_PRDR
   { "prdr_enable",              opt_bool,        &prdr_enable },
 #endif
   { "preserve_message_logs",    opt_bool,        &preserve_message_logs },
@@ -332,9 +366,6 @@ static optionlist optionlist_config[] = {
   { "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 },
@@ -345,7 +376,7 @@ static optionlist optionlist_config[] = {
   { "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 },
@@ -353,7 +384,7 @@ 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
+#ifdef LOOKUP_REDIS
   { "redis_servers",            opt_stringptr,   &redis_servers },
 #endif
   { "remote_max_parallel",      opt_int,         &remote_max_parallel },
@@ -365,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 },
@@ -387,9 +419,12 @@ 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
@@ -398,6 +433,7 @@ static optionlist optionlist_config[] = {
 #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
@@ -415,6 +451,7 @@ static optionlist optionlist_config[] = {
   { "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 },
@@ -430,13 +467,14 @@ static optionlist optionlist_config[] = {
 #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 },
-# if defined(EXPERIMENTAL_OCSP) && !defined(USE_GNUTLS)
+  { "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 },
@@ -458,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);
 
 
 
@@ -484,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))
@@ -498,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;
     }
   }
 
@@ -519,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
@@ -538,7 +619,6 @@ uschar name[64];
 int namelen = 0;
 BOOL redef = FALSE;
 macro_item *m;
-macro_item *mlast = NULL;
 
 while (isalnum(*s) || *s == '_')
   {
@@ -564,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)
@@ -579,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);
@@ -587,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
+
+#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
+}
 
-/* Set the value of the new or redefined macro */
 
-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;
+}
 
 
 /*************************************************
@@ -676,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;
       }
 
@@ -695,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. */
@@ -736,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;
@@ -748,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);
@@ -771,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;
@@ -877,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;
     }
@@ -939,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;
@@ -1017,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 (;;)
@@ -1026,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)
@@ -1040,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);
@@ -1072,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;
@@ -1117,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;
 }
@@ -1178,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)";
@@ -1214,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));
 
@@ -1320,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);
 
@@ -1340,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                  *
 *************************************************/
@@ -1398,7 +1732,6 @@ int intbase = 0;
 uschar *inttype = US"";
 uschar *sptr;
 uschar *s = buffer;
-uschar *saved_condition, *strtemp;
 uschar **str_target;
 uschar name[64];
 uschar name2[64];
@@ -1445,9 +1778,7 @@ 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);
@@ -1476,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 != '='))
@@ -1535,19 +1866,18 @@ switch (type)
     control block and flags word. */
 
     case opt_stringptr:
-    if (data_block == NULL)
-      str_target = (uschar **)(ol->value);
-    else
-      str_target = (uschar **)((uschar *)data_block + (long int)(ol->value));
+    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. */
-      saved_condition = *str_target;
-      strtemp = string_sprintf("${if and{{bool_lax{%s}}{bool_lax{%s}}}}",
-          saved_condition, sptr);
-      *str_target = string_copy_malloc(strtemp);
+      *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
@@ -1564,18 +1894,16 @@ switch (type)
       }
     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);
+      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
       {
@@ -1585,10 +1913,10 @@ switch (type)
     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)
       {
@@ -1618,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;
@@ -1743,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",
@@ -1784,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",
@@ -1924,7 +2251,7 @@ 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:
@@ -1940,7 +2267,6 @@ switch (type)
         inttype, name);
 
     if (errno != ERANGE)
-      {
       if (tolower(*endptr) == 'k')
         {
         if (lvalue > INT_MAX/1024 || lvalue < INT_MIN/1024) errno = ERANGE;
@@ -1954,7 +2280,13 @@ switch (type)
         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 || lvalue > INT_MAX || lvalue < INT_MIN)
       log_write(0, LOG_PANIC_DIE|LOG_CONFIG_IN,
@@ -1973,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:
     {
@@ -1987,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);
@@ -2033,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;
@@ -2106,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;
@@ -2206,7 +2554,6 @@ if (ol == NULL)
 
 if (!admin_user && (ol->type & opt_secure) != 0)
   {
-  const char * const hidden = "<value not displayable>";
   if (no_labels)
     printf("%s\n", hidden);
   else
@@ -2471,7 +2818,9 @@ 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
@@ -2482,6 +2831,8 @@ second argument is NULL. There are some special values:
   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",
 "authenticator" or "macro" in which case the first argument identifies the
@@ -2538,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;
@@ -2547,11 +2899,11 @@ 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,
+            optionlist_config, nelem(optionlist_config),
             no_labels);
       }
     return;
@@ -2572,6 +2924,12 @@ if (type == NULL)
     return;
     }
 
+  if (Ustrcmp(name, "config") == 0)
+    {
+    print_config(admin_user, no_labels);
+    return;
+    }
+
   if (Ustrcmp(name, "routers") == 0)
     {
     type = US"router";
@@ -2623,10 +2981,28 @@ if (type == 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, no_labels);
+    print_ol(find_option(name, optionlist_config, nelem(optionlist_config)),
+      name, NULL, optionlist_config, nelem(optionlist_config), no_labels);
     return;
     }
   }
@@ -2665,19 +3041,18 @@ else if (Ustrcmp(type, "macro") == 0)
     fprintf(stderr, "exim: permission denied\n");
     exit(EXIT_FAILURE);
     }
-  for (m = macros; m != NULL; m = m->next)
-    {
-    if (name == NULL || Ustrcmp(name, m->name) == 0)
+  if (!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 != NULL)
+      if (name)
         return;
       }
-    }
-  if (name != NULL)
+  if (name)
     printf("%s %s not found\n", type, name);
   return;
   }
@@ -2848,13 +3223,25 @@ Returns:  bool for "okay"; false will cause caller to immediately exit.
 
 #ifdef SUPPORT_TLS
 static BOOL
-tls_dropprivs_validate_require_cipher(void)
+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);
@@ -2868,12 +3255,9 @@ if (pid == 0)
     exim_setugid(exim_uid, exim_gid, FALSE,
         US"calling tls_validate_require_cipher");
 
-  errmsg = tls_validate_require_cipher();
-  if (errmsg)
-    {
+  if ((errmsg = tls_validate_require_cipher()))
     log_write(0, LOG_PANIC_DIE|LOG_CONFIG,
         "tls_require_ciphers invalid: %s", errmsg);
-    }
   fflush(NULL);
   _exit(0);
   }
@@ -2924,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)
@@ -2994,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
   {
@@ -3008,6 +3428,15 @@ else
       "configuration file %s", filename));
   }
 
+/* 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). */
 
@@ -3096,7 +3525,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");
@@ -3109,8 +3538,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;
@@ -3170,7 +3599,7 @@ 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)
@@ -3347,7 +3776,7 @@ if ((tls_verify_hosts != NULL || tls_try_verify_hosts != NULL) &&
 
 /* 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())
+if (!tls_dropprivs_validate_require_cipher(nowarn))
   exit(1);
 
 /* Magic number: at time of writing, 1024 has been the long-standing value
@@ -3358,19 +3787,24 @@ if (tls_dh_max_bits < 1024)
       "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)
+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)))
+  if (!tls_openssl_options_parse(openssl_options, &dummy))
     log_write(0, LOG_PANIC_DIE|LOG_CONFIG,
       "openssl_options parse error: %s", openssl_options);
 # endif
   }
-#endif
+#endif /*SUPPORT_TLS*/
+
+if (!nowarn && !keep_environment && environ && *environ)
+  log_write(0, LOG_MAIN,
+      "Warning: purging the environment.\n"
+      " Suggested action: use keep_environment.");
 }
 
 
@@ -3483,9 +3917,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);
@@ -3505,9 +3939,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);
@@ -3515,7 +3949,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);
@@ -3526,7 +3960,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 */
@@ -3544,8 +3978,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
@@ -3554,7 +3988,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);
     }
 
@@ -3562,11 +3996,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. */
 
@@ -3576,9 +4008,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);
@@ -3652,10 +4084,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;
 
@@ -3684,7 +4117,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" };
@@ -3692,24 +4125,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\"";
     }
   }
 
@@ -3736,8 +4164,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;
   }
 
@@ -3751,6 +4179,9 @@ else if (strncmpic(pp, US"lost_connection", p - pp) == 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\"", (int) (p-pp), pp);
 
@@ -3788,10 +4219,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");
 
@@ -3805,10 +4236,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 */
 }
@@ -3820,12 +4249,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;
@@ -3845,8 +4275,8 @@ while ((p = get_config_line()) != NULL)
 
   /* 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
@@ -3883,18 +4313,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 ||
@@ -3920,6 +4350,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
@@ -3930,6 +4375,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 */
@@ -3939,22 +4385,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);
-      }
-    }
   }
 }
 
@@ -4137,6 +4580,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 */