Follow CNAME chains only one step. Bug 2264
[exim.git] / src / src / readconf.c
index 9de064d958f877f83b92dd7dd3b0b1e650b3b6f5..3f307fd5cc5ff37a961c58b155e732b522664768 100644 (file)
@@ -2,7 +2,7 @@
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
-/* Copyright (c) University of Cambridge 1995 - 2015 */
+/* Copyright (c) University of Cambridge 1995 - 2018 */
 /* See the file NOTICE for conditions of use and distribution. */
 
 /* Functions for reading the configuration file, and for displaying
@@ -11,129 +11,12 @@ implementation of the conditional .ifdef etc. */
 
 #include "exim.h"
 
-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);
-
-#define CSTATE_STACK_SIZE 10
-
-
-/* Structure for chain (stack) of .included files */
-
-typedef struct config_file_item {
-  struct config_file_item *next;
-  uschar *filename;
-  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 {
-  uschar *name;
-  int    namelen;
-  int    action1;
-  int    action2;
-  int    pushpop;
-} cond_item;
-
-/* Structure of table of syslog facility names and values */
-
-typedef struct syslog_fac_item {
-  uschar *name;
-  int    value;
-} syslog_fac_item;
-
-/* constants */
-static const char * const hidden = "<value not displayable>";
-
-/* Static variables */
-
-static config_file_item *config_file_stack = NULL;  /* For includes */
-
-static uschar *syslog_facility_str  = NULL;
-static uschar next_section[24];
-static uschar time_buffer[24];
-
-/* State variables for conditional loading (.ifdef / .else / .endif) */
-
-static int cstate = 0;
-static int cstate_stack_ptr = -1;
-static int cstate_stack[CSTATE_STACK_SIZE];
-
-/* Table of state transitions for handling conditional inclusions. There are
-four possible state transitions:
-
-  .ifdef true
-  .ifdef false
-  .elifdef true  (or .else)
-  .elifdef false
-
-.endif just causes the previous cstate to be popped off the stack */
-
-static int next_cstate[3][4] =
-  {
-  /* State 0: reading from file, or reading until next .else or .endif */
-  { 0, 1, 2, 2 },
-  /* State 1: condition failed, skipping until next .else or .endif */
-  { 2, 2, 0, 1 },
-  /* State 2: skipping until .endif */
-  { 2, 2, 2, 2 },
-  };
-
-/* Table of conditionals and the states to set. For each name, there are four
-values: the length of the name (to save computing it each time), the state to
-set if a macro was found in the line, the state to set if a macro was not found
-in the line, and a stack manipulation setting which is:
-
-  -1   pull state value off the stack
-   0   don't alter the stack
-  +1   push value onto stack, before setting new state
-*/
-
-static cond_item cond_list[] = {
-  { US"ifdef",    5, 0, 1,  1 },
-  { US"ifndef",   6, 1, 0,  1 },
-  { US"elifdef",  7, 2, 3,  0 },
-  { US"elifndef", 8, 3, 2,  0 },
-  { US"else",     4, 2, 2,  0 },
-  { US"endif",    5, 0, 0, -1 }
-};
-
-static int cond_list_size = sizeof(cond_list)/sizeof(cond_item);
-
-/* Table of syslog facility names and their values */
-
-static syslog_fac_item syslog_list[] = {
-  { US"mail",   LOG_MAIL },
-  { US"user",   LOG_USER },
-  { US"news",   LOG_NEWS },
-  { US"uucp",   LOG_UUCP },
-  { US"local0", LOG_LOCAL0 },
-  { US"local1", LOG_LOCAL1 },
-  { US"local2", LOG_LOCAL2 },
-  { US"local3", LOG_LOCAL3 },
-  { US"local4", LOG_LOCAL4 },
-  { US"local5", LOG_LOCAL5 },
-  { US"local6", LOG_LOCAL6 },
-  { US"local7", LOG_LOCAL7 },
-  { US"daemon", LOG_DAEMON }
-};
-
-static int syslog_list_size = sizeof(syslog_list)/sizeof(syslog_fac_item);
-
-
+#ifdef MACRO_PREDEF
+# include "macro_predef.h"
+#endif
 
+static uschar * syslog_facility_str;
+static void fn_smtp_receive_timeout(const uschar *, const uschar *);
 
 /*************************************************
 *           Main configuration options           *
@@ -178,6 +61,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 },
@@ -208,6 +92,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 },
@@ -217,6 +103,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 },
@@ -236,6 +123,7 @@ static optionlist optionlist_config[] = {
 #endif
   { "dns_again_means_nonexist", opt_stringptr,   &dns_again_means_nonexist },
   { "dns_check_names_pattern",  opt_stringptr,   &check_dns_names_pattern },
+  { "dns_cname_loops",         opt_int,         &dns_cname_loops },
   { "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 },
@@ -244,7 +132,7 @@ static optionlist optionlist_config[] = {
   { "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 },
@@ -267,11 +155,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 */
-  /* From 4.83 we log a warning; a future relase will remove them */
-  { "gnutls_require_kx",        opt_stringptr,   &gnutls_require_kx },
-  { "gnutls_require_mac",       opt_stringptr,   &gnutls_require_mac },
-  { "gnutls_require_protocols", opt_stringptr,   &gnutls_require_proto },
 #endif
   { "header_line_maxsize",      opt_int,         &header_line_maxsize },
   { "header_maxsize",           opt_int,         &header_maxsize },
@@ -296,6 +179,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 },
@@ -312,7 +196,9 @@ static optionlist optionlist_config[] = {
   { "local_from_prefix",        opt_stringptr,   &local_from_prefix },
   { "local_from_suffix",        opt_stringptr,   &local_from_suffix },
   { "local_interfaces",         opt_stringptr,   &local_interfaces },
+#ifdef HAVE_LOCAL_SCAN
   { "local_scan_timeout",       opt_time,        &local_scan_timeout },
+#endif
   { "local_sender_retain",      opt_bool,        &local_sender_retain },
   { "localhost_number",         opt_stringptr,   &host_number_string },
   { "log_file_path",            opt_stringptr,   &log_file_path },
@@ -344,6 +230,7 @@ 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 },
@@ -368,7 +255,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 },
@@ -420,11 +307,12 @@ static optionlist optionlist_config[] = {
 #ifdef WITH_CONTENT_SCAN
   { "spamd_address",            opt_stringptr,   &spamd_address },
 #endif
-#ifdef EXPERIMENTAL_SPF
+#ifdef SUPPORT_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
@@ -442,6 +330,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 },
@@ -486,8 +375,166 @@ static optionlist optionlist_config[] = {
   { "write_rejectlog",          opt_bool,        &write_rejectlog }
 };
 
-static int optionlist_config_size =
-  sizeof(optionlist_config)/sizeof(optionlist);
+#ifndef MACRO_PREDEF
+static int optionlist_config_size = nelem(optionlist_config);
+#endif
+
+
+#ifdef MACRO_PREDEF
+
+static void fn_smtp_receive_timeout(const uschar * name, const uschar * str) {/*Dummy*/}
+
+void
+options_main(void)
+{
+options_from_list(optionlist_config, nelem(optionlist_config), US"MAIN", NULL);
+}
+
+void
+options_auths(void)
+{
+struct auth_info * ai;
+uschar buf[64];
+
+options_from_list(optionlist_auths, optionlist_auths_size, US"AUTHENTICATORS", NULL);
+
+for (ai = auths_available; ai->driver_name[0]; ai++)
+  {
+  spf(buf, sizeof(buf), US"_DRIVER_AUTHENTICATOR_%T", ai->driver_name);
+  builtin_macro_create(buf);
+  options_from_list(ai->options, (unsigned)*ai->options_count, US"AUTHENTICATOR", ai->driver_name);
+  }
+}
+
+
+#else  /*!MACRO_PREDEF*/
+
+extern char **environ;
+
+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);
+
+
+#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;
+  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 {
+  uschar *name;
+  int    namelen;
+  int    action1;
+  int    action2;
+  int    pushpop;
+} cond_item;
+
+/* Structure of table of syslog facility names and values */
+
+typedef struct syslog_fac_item {
+  uschar *name;
+  int    value;
+} syslog_fac_item;
+
+/* constants */
+static const char * const hidden = "<value not displayable>";
+
+/* Static variables */
+
+static config_file_item *config_file_stack = NULL;  /* For includes */
+
+static uschar *syslog_facility_str  = NULL;
+static uschar next_section[24];
+static uschar time_buffer[24];
+
+/* State variables for conditional loading (.ifdef / .else / .endif) */
+
+static int cstate = 0;
+static int cstate_stack_ptr = -1;
+static int cstate_stack[CSTATE_STACK_SIZE];
+
+/* Table of state transitions for handling conditional inclusions. There are
+four possible state transitions:
+
+  .ifdef true
+  .ifdef false
+  .elifdef true  (or .else)
+  .elifdef false
+
+.endif just causes the previous cstate to be popped off the stack */
+
+static int next_cstate[3][4] =
+  {
+  /* State 0: reading from file, or reading until next .else or .endif */
+  { 0, 1, 2, 2 },
+  /* State 1: condition failed, skipping until next .else or .endif */
+  { 2, 2, 0, 1 },
+  /* State 2: skipping until .endif */
+  { 2, 2, 2, 2 },
+  };
+
+/* Table of conditionals and the states to set. For each name, there are four
+values: the length of the name (to save computing it each time), the state to
+set if a macro was found in the line, the state to set if a macro was not found
+in the line, and a stack manipulation setting which is:
+
+  -1   pull state value off the stack
+   0   don't alter the stack
+  +1   push value onto stack, before setting new state
+*/
+
+static cond_item cond_list[] = {
+  { US"ifdef",    5, 0, 1,  1 },
+  { US"ifndef",   6, 1, 0,  1 },
+  { US"elifdef",  7, 2, 3,  0 },
+  { US"elifndef", 8, 3, 2,  0 },
+  { US"else",     4, 2, 2,  0 },
+  { US"endif",    5, 0, 0, -1 }
+};
+
+static int cond_list_size = sizeof(cond_list)/sizeof(cond_item);
+
+/* Table of syslog facility names and their values */
+
+static syslog_fac_item syslog_list[] = {
+  { US"mail",   LOG_MAIL },
+  { US"user",   LOG_USER },
+  { US"news",   LOG_NEWS },
+  { US"uucp",   LOG_UUCP },
+  { US"local0", LOG_LOCAL0 },
+  { US"local1", LOG_LOCAL1 },
+  { US"local2", LOG_LOCAL2 },
+  { US"local3", LOG_LOCAL3 },
+  { US"local4", LOG_LOCAL4 },
+  { US"local5", LOG_LOCAL5 },
+  { US"local6", LOG_LOCAL6 },
+  { US"local7", LOG_LOCAL7 },
+  { US"daemon", LOG_DAEMON }
+};
+
+static int syslog_list_size = sizeof(syslog_list)/sizeof(syslog_fac_item);
+
 
 
 
@@ -512,21 +559,21 @@ 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; i++)
     {
     if ((ri->options[i].type & opt_mask) != opt_stringptr) continue;
-    if (p == (char *)(r->options_block) + (long int)(ri->options[i].value))
+    if (p == CS (r->options_block) + (long int)(ri->options[i].value))
       return US ri->options[i].name;
     }
   }
 
-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; i++)
@@ -534,8 +581,8 @@ for (t = transports; t != NULL; t = t->next)
     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
+            ? CS t
+            : CS t->options_block
             )
             + (long int)op->value)
        return US op->name;
@@ -552,6 +599,36 @@ return US"";
 *       Deal with an assignment to a macro       *
 *************************************************/
 
+/* We have a new definition; append to the list.
+
+Args:
+ name  Name of the macro; will be copied
+ val   Expansion result for the macro; will be copied
+*/
+
+macro_item *
+macro_create(const uschar * name, const uschar * val, BOOL command_line)
+{
+macro_item * m = store_get(sizeof(macro_item));
+
+/* fprintf(stderr, "%s: '%s' '%s'\n", __FUNCTION__, name, val); */
+m->next = NULL;
+m->command_line = command_line;
+m->namelen = Ustrlen(name);
+m->replen = Ustrlen(val);
+m->name = string_copy(name);
+m->replacement = string_copy(val);
+if (mlast)
+  mlast->next = m;
+else
+  macros = m;
+mlast = m;
+if (!macros_user)
+  macros_user = m;
+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
@@ -561,30 +638,35 @@ non-command line, macros is permitted using '==' instead of '='.
 Arguments:
   s            points to the start of the logical line
 
-Returns:       nothing
+Returns:       FALSE iff fatal error
 */
 
-static void
-read_macro_assignment(uschar *s)
+BOOL
+macro_read_assignment(uschar *s)
 {
 uschar name[64];
 int namelen = 0;
 BOOL redef = FALSE;
 macro_item *m;
-macro_item *mlast = NULL;
 
 while (isalnum(*s) || *s == '_')
   {
   if (namelen >= sizeof(name) - 1)
-    log_write(0, LOG_PANIC_DIE|LOG_CONFIG_IN,
+    {
+    log_write(0, LOG_PANIC|LOG_CONFIG_IN,
       "macro name too long (maximum is " SIZE_T_FMT " characters)", sizeof(name) - 1);
+    return FALSE;
+    }
   name[namelen++] = *s++;
   }
 name[namelen] = 0;
 
 while (isspace(*s)) s++;
 if (*s++ != '=')
-  log_write(0, LOG_PANIC_DIE|LOG_CONFIG_IN, "malformed macro definition");
+  {
+  log_write(0, LOG_PANIC|LOG_CONFIG_IN, "malformed macro definition");
+  return FALSE;
+  }
 
 if (*s == '=')
   {
@@ -597,73 +679,171 @@ 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)
-      log_write(0, LOG_CONFIG|LOG_PANIC_DIE, "macro \"%s\" is already "
-       "defined (use \"==\" if you want to redefine it", name);
+      {
+      log_write(0, LOG_CONFIG|LOG_PANIC, "macro \"%s\" is already "
+       "defined (use \"==\" if you want to redefine it)", name);
+      return FALSE;
+      }
     break;
     }
 
-  if (len < namelen && Ustrstr(name, m->name) != NULL)
-    log_write(0, LOG_CONFIG|LOG_PANIC_DIE, "\"%s\" cannot be defined as "
+  if (m->namelen < namelen && Ustrstr(name, m->name) != NULL)
+    {
+    log_write(0, LOG_CONFIG|LOG_PANIC, "\"%s\" cannot be defined as "
       "a macro because previously defined macro \"%s\" is a substring",
       name, m->name);
+    return FALSE;
+    }
+
+  /* We cannot have this test, because it is documented that a substring
+  macro is permitted (there is even an example).
+  *
+  * 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);
+  */
+  }
+
+/* Check for an overriding command-line definition. */
+
+if (m && m->command_line) return TRUE;
+
+/* Redefinition must refer to an existing macro. */
+
+if (redef)
+  if (m)
+    {
+    m->replen = Ustrlen(s);
+    m->replacement = string_copy(s);
+    }
+  else
+    {
+    log_write(0, LOG_CONFIG|LOG_PANIC, "can't redefine an undefined macro "
+      "\"%s\"", name);
+    return FALSE;
+    }
+
+/* We have a new definition. */
+else
+  (void) macro_create(name, s, FALSE);
+return TRUE;
+}
+
+
+
+
+
+/* Process line for macros. The line is in big_buffer starting at offset len.
+Expand big_buffer if needed.  Handle definitions of new macros, and
+macro expansions, rewriting the line in the buffer.
+
+Arguments:
+ len           Offset in buffer of start of line
+ newlen                Pointer to offset of end of line, updated on return
+ macro_found   Pointer to return that a macro was expanded
 
-  /* We cannot have this test, because it is documented that a substring
-  macro is permitted (there is even an example).
-  *
-  * if (len > namelen && Ustrstr(m->name, name) != NULL)
-  *   log_write(0, LOG_CONFIG|LOG_PANIC_DIE, "\"%s\" cannot be defined as "
-  *     "a macro because it is a substring of previously defined macro \"%s\"",
-  *     name, m->name);
-  */
+Return: pointer to first nonblank char in line
+*/
 
-  mlast = m;
-  }
+uschar *
+macros_expand(int len, int * newlen, BOOL * macro_found)
+{
+uschar * ss = big_buffer + len;
+uschar * s;
+macro_item * m;
 
-/* Check for an overriding command-line definition. */
+/* Find the true start of the physical line - leading spaces are always
+ignored. */
 
-if (m != NULL && m->command_line) return;
+while (isspace(*ss)) ss++;
 
-/* Redefinition must refer to an existing macro. */
+/* Process the physical line for macros. If this is the start of the logical
+line, skip over initial text at the start of the line if it starts with an
+upper case character followed by a sequence of name characters and an equals
+sign, because that is the definition of a new macro, and we don't do
+replacement therein. */
 
-if (redef)
+s = ss;
+if (len == 0 && isupper(*s))
   {
-  if (m == NULL)
-    log_write(0, LOG_CONFIG|LOG_PANIC_DIE, "can't redefine an undefined macro "
-      "\"%s\"", name);
+  while (isalnum(*s) || *s == '_') s++;
+  while (isspace(*s)) s++;
+  if (*s != '=') s = ss;          /* Not a macro definition */
   }
 
-/* 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. */
+/* Skip leading chars which cannot start a macro name, to avoid multiple
+pointless rescans in Ustrstr calls. */
 
-else
+while (*s && !isupper(*s) && !(*s == '_' && isupper(s[1]))) s++;
+
+/* For each defined macro, scan the line (from after XXX= if present),
+replacing all occurrences of the macro. */
+
+*macro_found = FALSE;
+if (*s) for (m = *s == '_' ? macros : macros_user; m; m = m->next)
   {
-  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;
-  }
+  uschar * p, *pp;
+  uschar * t;
 
-/* Set the value of the new or redefined macro */
+  while (*s && !isupper(*s) && !(*s == '_' && isupper(s[1]))) s++;
+  if (!*s) break;
 
-m->replacement = string_copy(s);
-}
+  t = s;
+  while ((p = Ustrstr(t, m->name)) != NULL)
+    {
+    int moveby;
+
+/* fprintf(stderr, "%s: matched '%s' in '%s'\n", __FUNCTION__, m->name, ss); */
+    /* Expand the buffer if necessary */
+
+    while (*newlen - m->namelen + m->replen + 1 > big_buffer_size)
+      {
+      int newsize = big_buffer_size + BIG_BUFFER_SIZE;
+      uschar *newbuffer = store_malloc(newsize);
+      memcpy(newbuffer, big_buffer, *newlen + 1);
+      p = newbuffer  + (p - big_buffer);
+      s = newbuffer  + (s - big_buffer);
+      ss = newbuffer + (ss - big_buffer);
+      t = newbuffer  + (t - big_buffer);
+      big_buffer_size = newsize;
+      store_free(big_buffer);
+      big_buffer = newbuffer;
+      }
 
+    /* Shuffle the remaining characters up or down in the buffer before
+    copying in the replacement text. Don't rescan the replacement for this
+    same macro. */
 
+    pp = p + m->namelen;
+    if ((moveby = m->replen - m->namelen) != 0)
+      {
+      memmove(p + m->replen, pp, (big_buffer + *newlen) - pp + 1);
+      *newlen += moveby;
+      }
+    Ustrncpy(p, m->replacement, m->replen);
+    t = p + m->replen;
+    while (*t && !isupper(*t) && !(*t == '_' && isupper(t[1]))) t++;
+    *macro_found = TRUE;
+    }
+  }
 
+/* An empty macro replacement at the start of a line could mean that ss no
+longer points to the first non-blank character. */
 
+while (isspace(*ss)) ss++;
+return ss;
+}
 
 /*************************************************
 *            Read configuration line             *
@@ -694,7 +874,6 @@ int startoffset = 0;         /* To first non-blank char in logical line */
 int len = 0;                 /* Of logical line so far */
 int newlen;
 uschar *s, *ss;
-macro_item *m;
 BOOL macro_found;
 
 /* Loop for handling continuation lines, skipping comments, and dealing with
@@ -709,6 +888,7 @@ 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)
@@ -754,78 +934,7 @@ for (;;)
     newlen += Ustrlen(big_buffer + newlen);
     }
 
-  /* Find the true start of the physical line - leading spaces are always
-  ignored. */
-
-  ss = big_buffer + len;
-  while (isspace(*ss)) ss++;
-
-  /* Process the physical line for macros. If this is the start of the logical
-  line, skip over initial text at the start of the line if it starts with an
-  upper case character followed by a sequence of name characters and an equals
-  sign, because that is the definition of a new macro, and we don't do
-  replacement therein. */
-
-  s = ss;
-  if (len == 0 && isupper(*s))
-    {
-    while (isalnum(*s) || *s == '_') s++;
-    while (isspace(*s)) s++;
-    if (*s != '=') s = ss;          /* Not a macro definition */
-    }
-
-  /* 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)
-    {
-    uschar *p, *pp;
-    uschar *t = s;
-
-    while ((p = Ustrstr(t, m->name)) != NULL)
-      {
-      int moveby;
-      int namelen = Ustrlen(m->name);
-      int replen = Ustrlen(m->replacement);
-
-      /* Expand the buffer if necessary */
-
-      while (newlen - namelen + replen + 1 > big_buffer_size)
-        {
-        int newsize = big_buffer_size + BIG_BUFFER_SIZE;
-        uschar *newbuffer = store_malloc(newsize);
-        memcpy(newbuffer, big_buffer, newlen + 1);
-        p = newbuffer  + (p - big_buffer);
-        s = newbuffer  + (s - big_buffer);
-        ss = newbuffer + (ss - big_buffer);
-        t = newbuffer  + (t - big_buffer);
-        big_buffer_size = newsize;
-        store_free(big_buffer);
-        big_buffer = newbuffer;
-        }
-
-      /* Shuffle the remaining characters up or down in the buffer before
-      copying in the replacement text. Don't rescan the replacement for this
-      same macro. */
-
-      pp = p + namelen;
-      moveby = replen - namelen;
-      if (moveby != 0)
-        {
-        memmove(p + replen, pp, (big_buffer + newlen) - pp + 1);
-        newlen += moveby;
-        }
-      Ustrncpy(p, m->replacement, replen);
-      t = p + replen;
-      macro_found = TRUE;
-      }
-    }
-
-  /* An empty macro replacement at the start of a line could mean that ss no
-  longer points to the first non-blank character. */
-
-  while (isspace(*ss)) ss++;
+  ss = macros_expand(len, &newlen, &macro_found);
 
   /* Check for comment lines - these are physical lines. */
 
@@ -915,9 +1024,17 @@ 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
+        {
+       gstring * g = string_append(NULL, 3, config_directory, "/", ss);
+       ss = string_from_gstring(g);
+        }
 
     if (include_if_exists != 0 && (Ustat(ss, &statbuf) != 0)) continue;
 
@@ -928,13 +1045,15 @@ for (;;)
     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;
     }
@@ -1161,9 +1280,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;
 }
@@ -1201,7 +1321,7 @@ ol = find_option(name2, oltop, last);
 if (ol == NULL) log_write(0, LOG_MAIN|LOG_PANIC_DIE,
   "Exim internal error: missing set flag for %s", name);
 return (data_block == NULL)? (BOOL *)(ol->value) :
-  (BOOL *)((uschar *)data_block + (long int)(ol->value));
+  (BOOL *)(US data_block + (long int)(ol->value));
 }
 
 
@@ -1460,7 +1580,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];
@@ -1507,9 +1626,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);
@@ -1597,19 +1714,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
@@ -1630,12 +1746,19 @@ switch (type)
       int    sep_i = -(int)sep_o;
       const uschar * list = sptr;
       uschar * s;
-      uschar * list_o = *str_target;
+      gstring * list_o = NULL;
+
+      if (*str_target)
+       {
+       list_o = string_get(Ustrlen(*str_target) + Ustrlen(sptr));
+       list_o = string_cat(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);
+       *str_target = string_copy_malloc(string_from_gstring(list_o));
       }
     else
       {
@@ -1645,10 +1768,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)
       {
@@ -1674,8 +1797,8 @@ switch (type)
         }
       else
         {
-        chain = (rewrite_rule **)((uschar *)data_block + (long int)(ol2->value));
-        flagptr = (int *)((uschar *)data_block + (long int)(ol3->value));
+        chain = (rewrite_rule **)(US data_block + (long int)(ol2->value));
+        flagptr = (int *)(US data_block + (long int)(ol3->value));
         }
 
       while ((p = string_nextinlist(CUSS &sptr, &sep, big_buffer, BIG_BUFFER_SIZE)))
@@ -1707,7 +1830,7 @@ switch (type)
       if (data_block == NULL)
         *((uschar **)(ol2->value)) = ss;
       else
-        *((uschar **)((uschar *)data_block + (long int)(ol2->value))) = ss;
+        *((uschar **)(US data_block + (long int)(ol2->value))) = ss;
 
       if (ss != NULL)
         {
@@ -1726,7 +1849,7 @@ switch (type)
     if (data_block == NULL)
       *((uid_t *)(ol->value)) = uid;
     else
-      *((uid_t *)((uschar *)data_block + (long int)(ol->value))) = uid;
+      *((uid_t *)(US data_block + (long int)(ol->value))) = uid;
 
     /* Set the flag indicating a fixed value is set */
 
@@ -1748,7 +1871,7 @@ switch (type)
         if (data_block == NULL)
           *((gid_t *)(ol2->value)) = pw->pw_gid;
         else
-          *((gid_t *)((uschar *)data_block + (long int)(ol2->value))) = pw->pw_gid;
+          *((gid_t *)(US data_block + (long int)(ol2->value))) = pw->pw_gid;
         *set_flag = TRUE;
         }
       }
@@ -1770,7 +1893,7 @@ switch (type)
       if (data_block == NULL)
         *((uschar **)(ol2->value)) = ss;
       else
-        *((uschar **)((uschar *)data_block + (long int)(ol2->value))) = ss;
+        *((uschar **)(US data_block + (long int)(ol2->value))) = ss;
 
       if (ss != NULL)
         {
@@ -1788,7 +1911,7 @@ switch (type)
     if (data_block == NULL)
       *((gid_t *)(ol->value)) = gid;
     else
-      *((gid_t *)((uschar *)data_block + (long int)(ol->value))) = gid;
+      *((gid_t *)(US data_block + (long int)(ol->value))) = gid;
     *(get_set_flag(name, oltop, last, data_block)) = TRUE;
     break;
 
@@ -1818,7 +1941,7 @@ switch (type)
       if (data_block == NULL)
         *((uid_t **)(ol->value)) = list;
       else
-        *((uid_t **)((uschar *)data_block + (long int)(ol->value))) = list;
+        *((uid_t **)(US data_block + (long int)(ol->value))) = list;
 
       p = op;
       while (count-- > 1)
@@ -1859,7 +1982,7 @@ switch (type)
       if (data_block == NULL)
         *((gid_t **)(ol->value)) = list;
       else
-        *((gid_t **)((uschar *)data_block + (long int)(ol->value))) = list;
+        *((gid_t **)(US data_block + (long int)(ol->value))) = list;
 
       p = op;
       while (count-- > 1)
@@ -1895,7 +2018,7 @@ switch (type)
       if (data_block == NULL)
         *((uschar **)(ol2->value)) = sptr;
       else
-        *((uschar **)((uschar *)data_block + (long int)(ol2->value))) = sptr;
+        *((uschar **)(US data_block + (long int)(ol2->value))) = sptr;
       freesptr = FALSE;
       break;
       }
@@ -1933,7 +2056,7 @@ switch (type)
     int bit = 1 << ((ol->type >> 16) & 31);
     int *ptr = (data_block == NULL)?
       (int *)(ol->value) :
-      (int *)((uschar *)data_block + (long int)ol->value);
+      (int *)(US data_block + (long int)ol->value);
     if (boolvalue) *ptr |= bit; else *ptr &= ~bit;
     break;
     }
@@ -1943,7 +2066,7 @@ switch (type)
   if (data_block == NULL)
     *((BOOL *)(ol->value)) = boolvalue;
   else
-    *((BOOL *)((uschar *)data_block + (long int)(ol->value))) = boolvalue;
+    *((BOOL *)(US data_block + (long int)(ol->value))) = boolvalue;
 
   /* Verify fudge */
 
@@ -1956,7 +2079,7 @@ switch (type)
       if (data_block == NULL)
         *((BOOL *)(ol2->value)) = boolvalue;
       else
-        *((BOOL *)((uschar *)data_block + (long int)(ol2->value))) = boolvalue;
+        *((BOOL *)(US data_block + (long int)(ol2->value))) = boolvalue;
       }
     }
 
@@ -1971,7 +2094,7 @@ switch (type)
       if (data_block == NULL)
         *((BOOL *)(ol2->value)) = TRUE;
       else
-        *((BOOL *)((uschar *)data_block + (long int)(ol2->value))) = TRUE;
+        *((BOOL *)(US data_block + (long int)(ol2->value))) = TRUE;
       }
     }
   break;
@@ -1983,7 +2106,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:
@@ -1999,7 +2122,6 @@ switch (type)
         inttype, name);
 
     if (errno != ERANGE)
-      {
       if (tolower(*endptr) == 'k')
         {
         if (lvalue > INT_MAX/1024 || lvalue < INT_MIN/1024) errno = ERANGE;
@@ -2013,7 +2135,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,
@@ -2029,11 +2157,12 @@ switch (type)
   if (data_block == NULL)
     *((int *)(ol->value)) = value;
   else
-    *((int *)((uschar *)data_block + (long int)(ol->value))) = value;
+    *((int *)(US 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:
     {
@@ -2046,22 +2175,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);
@@ -2074,7 +2207,7 @@ switch (type)
   if (data_block == NULL)
     *((int *)(ol->value)) = value;
   else
-    *((int *)((uschar *)data_block + (long int)(ol->value))) = value;
+    *((int *)(US data_block + (long int)(ol->value))) = value;
   break;
 
   /*  Fixed-point number: held to 3 decimal places. */
@@ -2092,6 +2225,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;
@@ -2110,7 +2248,7 @@ switch (type)
   if (data_block == NULL)
     *((int *)(ol->value)) = value;
   else
-    *((int *)((uschar *)data_block + (long int)(ol->value))) = value;
+    *((int *)(US data_block + (long int)(ol->value))) = value;
   break;
 
   /* There's a special routine to read time values. */
@@ -2123,7 +2261,7 @@ switch (type)
   if (data_block == NULL)
     *((int *)(ol->value)) = value;
   else
-    *((int *)((uschar *)data_block + (long int)(ol->value))) = value;
+    *((int *)(US data_block + (long int)(ol->value))) = value;
   break;
 
   /* A time list is a list of colon-separated times, with the first
@@ -2135,7 +2273,7 @@ switch (type)
     int count = 0;
     int *list = (data_block == NULL)?
       (int *)(ol->value) :
-      (int *)((uschar *)data_block + (long int)(ol->value));
+      (int *)(US data_block + (long int)(ol->value));
 
     if (*s != 0) for (count = 1; count <= list[0] - 2; count++)
       {
@@ -2212,10 +2350,10 @@ t /= 24;
 d = t % 7;
 w = t/7;
 
-if (w > 0) { sprintf(CS p, "%dw", w); while (*p) p++; }
-if (d > 0) { sprintf(CS p, "%dd", d); while (*p) p++; }
-if (h > 0) { sprintf(CS p, "%dh", h); while (*p) p++; }
-if (m > 0) { sprintf(CS p, "%dm", m); while (*p) p++; }
+if (w > 0) p += sprintf(CS p, "%dw", w);
+if (d > 0) p += sprintf(CS p, "%dd", d);
+if (h > 0) p += sprintf(CS p, "%dh", h);
+if (m > 0) p += sprintf(CS p, "%dm", m);
 if (s > 0 || p == time_buffer) sprintf(CS p, "%ds", s);
 
 return time_buffer;
@@ -2244,10 +2382,10 @@ Arguments:
   last           one more than the offset of the last entry in optop
   no_labels      do not show "foo = " at the start.
 
-Returns:         nothing
+Returns:         boolean success
 */
 
-static void
+static BOOL
 print_ol(optionlist *ol, uschar *name, void *options_block,
   optionlist *oltop, int last, BOOL no_labels)
 {
@@ -2260,47 +2398,47 @@ gid_t *gidlist;
 uschar *s;
 uschar name2[64];
 
-if (ol == NULL)
+if (!ol)
   {
   printf("%s is not a known option\n", name);
-  return;
+  return FALSE;
   }
 
 /* Non-admin callers cannot see options that have been flagged secure by the
 "hide" prefix. */
 
-if (!admin_user && (ol->type & opt_secure) != 0)
+if (!admin_user && ol->type & opt_secure)
   {
   if (no_labels)
     printf("%s\n", hidden);
   else
     printf("%s = %s\n", name, hidden);
-  return;
+  return TRUE;
   }
 
 /* Else show the value of the option */
 
 value = ol->value;
-if (options_block != NULL)
+if (options_block)
   {
-  if ((ol->type & opt_public) == 0)
+  if (!(ol->type & opt_public))
     options_block = (void *)(((driver_instance *)options_block)->options_block);
-  value = (void *)((uschar *)options_block + (long int)value);
+  value = (void *)(US options_block + (long int)value);
   }
 
 switch(ol->type & opt_mask)
   {
   case opt_stringptr:
   case opt_rewrite:        /* Show the text value */
-  s = *((uschar **)value);
-  if (!no_labels) printf("%s = ", name);
-  printf("%s\n", (s == NULL)? US"" : string_printing2(s, FALSE));
-  break;
+    s = *(USS value);
+    if (!no_labels) printf("%s = ", name);
+    printf("%s\n", s ? string_printing2(s, FALSE) : US"");
+    break;
 
   case opt_int:
-  if (!no_labels) printf("%s = ", name);
-  printf("%d\n", *((int *)value));
-  break;
+    if (!no_labels) printf("%s = ", name);
+    printf("%d\n", *((int *)value));
+    break;
 
   case opt_mkint:
     {
@@ -2323,22 +2461,22 @@ switch(ol->type & opt_mask)
       printf("%d\n", x);
       }
     }
-  break;
+    break;
 
   case opt_Kint:
     {
     int x = *((int *)value);
     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);
+    else if ((x & 1023) == 0) printf("%dM\n", x >> 10);
+    else printf("%dK\n", x);
     }
-  break;
+    break;
 
   case opt_octint:
-  if (!no_labels) printf("%s = ", name);
-  printf("%#o\n", *((int *)value));
-  break;
+    if (!no_labels) printf("%s = ", name);
+    printf("%#o\n", *((int *)value));
+    break;
 
   /* Can be negative only when "unset", in which case integer */
 
@@ -2361,124 +2499,115 @@ switch(ol->type & opt_mask)
       printf("\n");
       }
     }
-  break;
+    break;
 
   /* If the numerical value is unset, try for the string value */
 
   case opt_expand_uid:
-  if (! *get_set_flag(name, oltop, last, options_block))
-    {
-    sprintf(CS name2, "*expand_%.50s", name);
-    ol2 = find_option(name2, oltop, last);
-    if (ol2 != NULL)
+    if (! *get_set_flag(name, oltop, last, options_block))
       {
-      void *value2 = ol2->value;
-      if (options_block != NULL)
-        value2 = (void *)((uschar *)options_block + (long int)value2);
-      s = *((uschar **)value2);
-      if (!no_labels) printf("%s = ", name);
-      printf("%s\n", (s == NULL)? US"" : string_printing(s));
-      break;
+      sprintf(CS name2, "*expand_%.50s", name);
+      if ((ol2 = find_option(name2, oltop, last)))
+       {
+       void *value2 = ol2->value;
+       if (options_block)
+         value2 = (void *)(US options_block + (long int)value2);
+       s = *(USS value2);
+       if (!no_labels) printf("%s = ", name);
+       printf("%s\n", s ? string_printing(s) : US"");
+       break;
+       }
       }
-    }
 
-  /* Else fall through */
+    /* Else fall through */
 
   case opt_uid:
-  if (!no_labels) printf("%s = ", name);
-  if (! *get_set_flag(name, oltop, last, options_block))
-    printf("\n");
-  else
-    {
-    pw = getpwuid(*((uid_t *)value));
-    if (pw == NULL)
-      printf("%ld\n", (long int)(*((uid_t *)value)));
-    else printf("%s\n", pw->pw_name);
-    }
-  break;
+    if (!no_labels) printf("%s = ", name);
+    if (! *get_set_flag(name, oltop, last, options_block))
+      printf("\n");
+    else
+      if ((pw = getpwuid(*((uid_t *)value))))
+       printf("%s\n", pw->pw_name);
+      else
+       printf("%ld\n", (long int)(*((uid_t *)value)));
+    break;
 
   /* If the numerical value is unset, try for the string value */
 
   case opt_expand_gid:
-  if (! *get_set_flag(name, oltop, last, options_block))
-    {
-    sprintf(CS name2, "*expand_%.50s", name);
-    ol2 = find_option(name2, oltop, last);
-    if (ol2 != NULL && (ol2->type & opt_mask) == opt_stringptr)
+    if (! *get_set_flag(name, oltop, last, options_block))
       {
-      void *value2 = ol2->value;
-      if (options_block != NULL)
-        value2 = (void *)((uschar *)options_block + (long int)value2);
-      s = *((uschar **)value2);
-      if (!no_labels) printf("%s = ", name);
-      printf("%s\n", (s == NULL)? US"" : string_printing(s));
-      break;
+      sprintf(CS name2, "*expand_%.50s", name);
+      if (  (ol2 = find_option(name2, oltop, last))
+        && (ol2->type & opt_mask) == opt_stringptr)
+       {
+       void *value2 = ol2->value;
+       if (options_block)
+         value2 = (void *)(US options_block + (long int)value2);
+       s = *(USS value2);
+       if (!no_labels) printf("%s = ", name);
+       printf("%s\n", s ? string_printing(s) : US"");
+       break;
+       }
       }
-    }
 
-  /* Else fall through */
+    /* Else fall through */
 
   case opt_gid:
-  if (!no_labels) printf("%s = ", name);
-  if (! *get_set_flag(name, oltop, last, options_block))
-    printf("\n");
-  else
-    {
-    gr = getgrgid(*((int *)value));
-    if (gr == NULL)
-       printf("%ld\n", (long int)(*((int *)value)));
-    else printf("%s\n", gr->gr_name);
-    }
-  break;
+    if (!no_labels) printf("%s = ", name);
+    if (! *get_set_flag(name, oltop, last, options_block))
+      printf("\n");
+    else
+      if ((gr = getgrgid(*((int *)value))))
+       printf("%s\n", gr->gr_name);
+      else
+        printf("%ld\n", (long int)(*((int *)value)));
+    break;
 
   case opt_uidlist:
-  uidlist = *((uid_t **)value);
-  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++)
+    uidlist = *((uid_t **)value);
+    if (!no_labels) printf("%s =", name);
+    if (uidlist)
       {
-      uschar *name = NULL;
-      pw = getpwuid(uidlist[i]);
-      if (pw != NULL) name = US pw->pw_name;
-      if (sep != '\0') printf("%c", sep);
-      if (name != NULL) printf("%s", name);
-        else printf("%ld", (long int)(uidlist[i]));
-      sep = ':';
+      int i;
+      uschar sep = no_labels ? '\0' : ' ';
+      for (i = 1; i <= (int)(uidlist[0]); i++)
+       {
+       uschar *name = NULL;
+       if ((pw = getpwuid(uidlist[i]))) name = US pw->pw_name;
+       if (sep != '\0') printf("%c", sep);
+       if (name) printf("%s", name);
+       else printf("%ld", (long int)(uidlist[i]));
+       sep = ':';
+       }
       }
-    }
-  printf("\n");
-  break;
+    printf("\n");
+    break;
 
   case opt_gidlist:
-  gidlist = *((gid_t **)value);
-  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++)
+    gidlist = *((gid_t **)value);
+    if (!no_labels) printf("%s =", name);
+    if (gidlist)
       {
-      uschar *name = NULL;
-      gr = getgrgid(gidlist[i]);
-      if (gr != NULL) name = US gr->gr_name;
-      if (sep != '\0') printf("%c", sep);
-      if (name != NULL) printf("%s", name);
-        else printf("%ld", (long int)(gidlist[i]));
-      sep = ':';
+      int i;
+      uschar sep = no_labels ? '\0' : ' ';
+      for (i = 1; i <= (int)(gidlist[0]); i++)
+       {
+       uschar *name = NULL;
+       if ((gr = getgrgid(gidlist[i]))) name = US gr->gr_name;
+       if (sep != '\0') printf("%c", sep);
+       if (name) printf("%s", name);
+       else printf("%ld", (long int)(gidlist[i]));
+       sep = ':';
+       }
       }
-    }
-  printf("\n");
-  break;
+    printf("\n");
+    break;
 
   case opt_time:
-  if (!no_labels) printf("%s = ", name);
-  printf("%s\n", readconf_printtime(*((int *)value)));
-  break;
+    if (!no_labels) printf("%s = ", name);
+    printf("%s\n", readconf_printtime(*((int *)value)));
+    break;
 
   case opt_timelist:
     {
@@ -2486,42 +2615,42 @@ switch(ol->type & opt_mask)
     int *list = (int *)value;
     if (!no_labels) printf("%s = ", name);
     for (i = 0; i < list[1]; i++)
-      printf("%s%s", (i == 0)? "" : ":", readconf_printtime(list[i+2]));
+      printf("%s%s", i == 0 ? "" : ":", readconf_printtime(list[i+2]));
     printf("\n");
     }
-  break;
+    break;
 
   case opt_bit:
-  printf("%s%s\n", ((*((int *)value)) & (1 << ((ol->type >> 16) & 31)))?
-    "" : "no_", name);
-  break;
+    printf("%s%s\n", ((*((int *)value)) & (1 << ((ol->type >> 16) & 31)))?
+      "" : "no_", name);
+    break;
 
   case opt_expand_bool:
-  sprintf(CS name2, "*expand_%.50s", name);
-  ol2 = find_option(name2, oltop, last);
-  if (ol2 != NULL && ol2->value != NULL)
-    {
-    void *value2 = ol2->value;
-    if (options_block != NULL)
-      value2 = (void *)((uschar *)options_block + (long int)value2);
-    s = *((uschar **)value2);
-    if (s != NULL)
+    sprintf(CS name2, "*expand_%.50s", name);
+    if ((ol2 = find_option(name2, oltop, last)) && ol2->value)
       {
-      if (!no_labels) printf("%s = ", name);
-      printf("%s\n", string_printing(s));
-      break;
+      void *value2 = ol2->value;
+      if (options_block)
+       value2 = (void *)(US options_block + (long int)value2);
+      s = *(USS value2);
+      if (s)
+       {
+       if (!no_labels) printf("%s = ", name);
+       printf("%s\n", string_printing(s));
+       break;
+       }
+      /* s == NULL => string not set; fall through */
       }
-    /* s == NULL => string not set; fall through */
-    }
 
-  /* Fall through */
+    /* Fall through */
 
   case opt_bool:
   case opt_bool_verify:
   case opt_bool_set:
-  printf("%s%s\n", (*((BOOL *)value))? "" : "no_", name);
-  break;
+    printf("%s%s\n", (*((BOOL *)value))? "" : "no_", name);
+    break;
   }
+return TRUE;
 }
 
 
@@ -2549,6 +2678,7 @@ second argument is NULL. There are some special values:
   +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
@@ -2559,10 +2689,10 @@ Arguments:
   type        NULL or driver type name, as described above
   no_labels   avoid the "foo = " at the start of an item
 
-Returns:      nothing
+Returns:      Boolean success
 */
 
-void
+BOOL
 readconf_print(uschar *name, uschar *type, BOOL no_labels)
 {
 BOOL names_only = FALSE;
@@ -2572,7 +2702,7 @@ driver_instance *d = NULL;
 macro_item *m;
 int size = 0;
 
-if (type == NULL)
+if (!type)
   {
   if (*name == '+')
     {
@@ -2585,9 +2715,7 @@ if (type == NULL)
       &hostlist_anchor, &localpartlist_anchor };
 
     for (i = 0; i < 4; i++)
-      {
-      t = tree_search(*(anchors[i]), name+1);
-      if (t != NULL)
+      if ((t = tree_search(*(anchors[i]), name+1)))
         {
         found = TRUE;
         if (no_labels)
@@ -2596,54 +2724,50 @@ if (type == NULL)
           printf("%slist %s = %s\n", types[i], name+1,
             ((namedlist_block *)(t->data.ptr))->string);
         }
-      }
 
     if (!found)
       printf("no address, domain, host, or local part list called \"%s\" "
         "exists\n", name+1);
 
-    return;
+    return found;
     }
 
-  if ( Ustrcmp(name, "configure_file") == 0
-     ||Ustrcmp(name, "config_file") == 0)
+  if (  Ustrcmp(name, "configure_file") == 0
+     || Ustrcmp(name, "config_file") == 0)
     {
     printf("%s\n", CS config_main_filename);
-    return;
+    return TRUE;
     }
 
   if (Ustrcmp(name, "all") == 0)
     {
     for (ol = optionlist_config;
-         ol < optionlist_config + optionlist_config_size; ol++)
-      {
-      if ((ol->type & opt_hidden) == 0)
-        print_ol(ol, US ol->name, NULL,
-            optionlist_config, optionlist_config_size,
-            no_labels);
-      }
-    return;
+         ol < optionlist_config + nelem(optionlist_config); ol++)
+      if (!(ol->type & opt_hidden))
+        (void) print_ol(ol, US ol->name, NULL,
+                 optionlist_config, nelem(optionlist_config),
+                 no_labels);
+    return TRUE;
     }
 
   if (Ustrcmp(name, "local_scan") == 0)
     {
-    #ifndef LOCAL_SCAN_HAS_OPTIONS
+#ifndef LOCAL_SCAN_HAS_OPTIONS
     printf("local_scan() options are not supported\n");
-    #else
+    return FALSE;
+#else
     for (ol = local_scan_options;
          ol < local_scan_options + local_scan_options_count; ol++)
-      {
-      print_ol(ol, US ol->name, NULL, local_scan_options,
-        local_scan_options_count, no_labels);
-      }
-    #endif
-    return;
+      (void) print_ol(ol, US ol->name, NULL, local_scan_options,
+                 local_scan_options_count, no_labels);
+    return TRUE;
+#endif
     }
 
   if (Ustrcmp(name, "config") == 0)
     {
-    print_config(admin_user);
-    return;
+    print_config(admin_user, no_labels);
+    return TRUE;
     }
 
   if (Ustrcmp(name, "routers") == 0)
@@ -2656,53 +2780,62 @@ if (type == NULL)
     type = US"transport";
     name = NULL;
     }
-
   else if (Ustrcmp(name, "authenticators") == 0)
     {
     type = US"authenticator";
     name = NULL;
     }
-
   else if (Ustrcmp(name, "macros") == 0)
     {
     type = US"macro";
     name = NULL;
     }
-
   else if (Ustrcmp(name, "router_list") == 0)
     {
     type = US"router";
     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
+  else if (Ustrcmp(name, "environment") == 0)
     {
-    print_ol(find_option(name, optionlist_config, optionlist_config_size),
-      name, NULL, optionlist_config, optionlist_config_size, no_labels);
-    return;
+    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 TRUE;
     }
+
+  else
+    return print_ol(find_option(name,
+      optionlist_config, nelem(optionlist_config)),
+      name, NULL, optionlist_config, nelem(optionlist_config), no_labels);
   }
 
 /* Handle the options for a router or transport. Skip options that are flagged
@@ -2737,54 +2870,55 @@ else if (Ustrcmp(type, "macro") == 0)
   if (!admin_user)
     {
     fprintf(stderr, "exim: permission denied\n");
-    exit(EXIT_FAILURE);
+    return FALSE;
     }
-  for (m = macros; m != NULL; m = m->next)
-    {
-    if (name == NULL || Ustrcmp(name, m->name) == 0)
+  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)
-        return;
+      if (name)
+        return TRUE;
       }
-    }
-  if (name != NULL)
-    printf("%s %s not found\n", type, name);
-  return;
+  if (!name) return TRUE;
+
+  printf("%s %s not found\n", type, name);
+  return FALSE;
   }
 
 if (names_only)
   {
-  for (; d != NULL; d = d->next) printf("%s\n", CS d->name);
-  return;
+  for (; d; d = d->next) printf("%s\n", CS d->name);
+  return TRUE;
   }
 
 /* Either search for a given driver, or print all of them */
 
-for (; d != NULL; d = d->next)
+for (; d; d = d->next)
   {
-  if (name == NULL)
+  BOOL rc = FALSE;
+  if (!name)
     printf("\n%s %s:\n", d->name, type);
   else if (Ustrcmp(d->name, name) != 0) continue;
 
   for (ol = ol2; ol < ol2 + size; ol++)
-    {
-    if ((ol->type & opt_hidden) == 0)
-      print_ol(ol, US ol->name, d, ol2, size, no_labels);
-    }
+    if (!(ol->type & opt_hidden))
+      rc |= 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), no_labels);
-    }
-  if (name != NULL) return;
+    if (!(ol->type & opt_hidden))
+      rc |= print_ol(ol, US ol->name, d, d->info->options,
+                   *d->info->options_count, no_labels);
+
+  if (name) return rc;
   }
-if (name != NULL) printf("%s %s not found\n", type, name);
+if (!name) return TRUE;
+
+printf("%s %s not found\n", type, name);
+return FALSE;
 }
 
 
@@ -2922,7 +3056,7 @@ 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;
@@ -2936,9 +3070,9 @@ if (  !tls_advertise_hosts
    || Ustrcmp(tls_advertise_hosts, ":") == 0
    )
   return TRUE;
-else if (!tls_certificate)
-  log_write(0, LOG_MAIN|LOG_PANIC,
-    "Warning: No server certificate defined; TLS connections will fail.\n"
+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);
@@ -2954,12 +3088,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);
   }
@@ -3010,7 +3141,7 @@ 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;
@@ -3019,9 +3150,9 @@ 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)
@@ -3080,14 +3211,44 @@ 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 *p;
+  uschar *last_slash = Ustrrchr(filename, '/');
   config_filename = config_main_filename = string_copy(filename);
 
-  p = Ustrrchr(filename, '/');
-  config_main_directory = p ? string_copyn(filename, p - filename)
-                            : string_copy(US".");
+  /* 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];
+      gstring * g;
+
+      if (os_getcwd(buf, PATH_MAX) == NULL)
+        {
+        perror("exim: getcwd");
+        exit(EXIT_FAILURE);
+        }
+      g = string_cat(NULL, buf);
+
+      /* If the dir does not end with a "/", append one */
+      if (g->s[g->ptr-1] != '/')
+        g = string_catn(g, US"/", 1);
+
+      /* If the config file contains a "/", extract the directory part */
+      if (last_slash)
+        g = string_catn(g, filename, last_slash - filename);
+
+      config_main_directory = string_from_gstring(g);
+    }
+  config_directory = config_main_directory;
   }
 else
   {
@@ -3099,6 +3260,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). */
 
@@ -3129,9 +3299,14 @@ if (trusted_config && Ustrcmp(filename, US"/dev/null"))
 letter. If we see something starting with an upper case letter, it is taken as
 a macro definition. */
 
-while ((s = get_config_line()) != NULL)
+while ((s = get_config_line()))
   {
-  if (isupper(s[0])) read_macro_assignment(s);
+  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]))
+    { if (!macro_read_assignment(s)) exim_exit(EXIT_FAILURE, US""); }
 
   else if (Ustrncmp(s, "domainlist", 10) == 0)
     read_named_list(&domainlist_anchor, &domainlist_count,
@@ -3294,7 +3469,7 @@ if (*log_file_path != 0)
 openlog(). Default is LOG_MAIL set in globals.c. Allow the user to omit the
 leading "log_". */
 
-if (syslog_facility_str != NULL)
+if (syslog_facility_str)
   {
   int i;
   uschar *s = syslog_facility_str;
@@ -3304,27 +3479,22 @@ if (syslog_facility_str != NULL)
     s += 4;
 
   for (i = 0; i < syslog_list_size; i++)
-    {
     if (strcmpic(s, syslog_list[i].name) == 0)
       {
       syslog_facility = syslog_list[i].value;
       break;
       }
-    }
 
   if (i >= syslog_list_size)
-    {
     log_write(0, LOG_PANIC_DIE|LOG_CONFIG,
       "failed to interpret syslog_facility \"%s\"", syslog_facility_str);
-    }
   }
 
 /* Expand pid_file_path */
 
 if (*pid_file_path != 0)
   {
-  s = expand_string(pid_file_path);
-  if (s == NULL)
+  if (!(s = expand_string(pid_file_path)))
     log_write(0, LOG_MAIN|LOG_PANIC_DIE, "failed to expand pid_file_path "
       "\"%s\": %s", pid_file_path, expand_string_message);
   pid_file_path = s;
@@ -3332,7 +3502,7 @@ if (*pid_file_path != 0)
 
 /* Set default value of process_log_path */
 
-if (process_log_path == NULL || *process_log_path =='\0')
+if (!process_log_path || *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
@@ -3342,23 +3512,19 @@ regex_From = regex_must_compile(uucp_from_pattern, FALSE, TRUE);
 
 /* Unpick the SMTP rate limiting options, if set */
 
-if (smtp_ratelimit_mail != NULL)
-  {
+if (smtp_ratelimit_mail)
   unpick_ratelimit(smtp_ratelimit_mail, &smtp_rlm_threshold,
     &smtp_rlm_base, &smtp_rlm_factor, &smtp_rlm_limit);
-  }
 
-if (smtp_ratelimit_rcpt != NULL)
-  {
+if (smtp_ratelimit_rcpt)
   unpick_ratelimit(smtp_ratelimit_rcpt, &smtp_rlr_threshold,
     &smtp_rlr_base, &smtp_rlr_factor, &smtp_rlr_limit);
-  }
 
 /* The qualify domains default to the primary host name */
 
-if (qualify_domain_sender == NULL)
+if (!qualify_domain_sender)
   qualify_domain_sender = primary_hostname;
-if (qualify_domain_recipient == NULL)
+if (!qualify_domain_recipient)
   qualify_domain_recipient = qualify_domain_sender;
 
 /* Setting system_filter_user in the configuration sets the gid as well if a
@@ -3367,7 +3533,7 @@ name is given, but a numerical value does not. */
 if (system_filter_uid_set && !system_filter_gid_set)
   {
   struct passwd *pw = getpwuid(system_filter_uid);
-  if (pw == NULL)
+  if (!pw)
     log_write(0, LOG_MAIN|LOG_PANIC_DIE, "Failed to look up uid %ld",
       (long int)system_filter_uid);
   system_filter_gid = pw->pw_gid;
@@ -3377,14 +3543,14 @@ if (system_filter_uid_set && !system_filter_gid_set)
 /* If the errors_reply_to field is set, check that it is syntactically valid
 and ensure it contains a domain. */
 
-if (errors_reply_to != NULL)
+if (errors_reply_to)
   {
   uschar *errmess;
   int start, end, domain;
   uschar *recipient = parse_extract_address(errors_reply_to, &errmess,
     &start, &end, &domain, FALSE);
 
-  if (recipient == NULL)
+  if (!recipient)
     log_write(0, LOG_PANIC_DIE|LOG_CONFIG,
       "error in errors_reply_to (%s): %s", errors_reply_to, errmess);
 
@@ -3406,12 +3572,13 @@ if (smtp_accept_max == 0 &&
 so that it can be computed from the host name, for example. We do this last
 so as to ensure that everything else is set up before the expansion. */
 
-if (host_number_string != NULL)
+if (host_number_string)
   {
   long int n;
   uschar *end;
   uschar *s = expand_string(host_number_string);
-  if (s == NULL)
+
+  if (!s)
     log_write(0, LOG_MAIN|LOG_PANIC_DIE,
         "failed to expand localhost_number \"%s\": %s",
         host_number_string, expand_string_message);
@@ -3430,15 +3597,14 @@ if (host_number_string != NULL)
 #ifdef SUPPORT_TLS
 /* If tls_verify_hosts is set, tls_verify_certificates must also be set */
 
-if ((tls_verify_hosts != NULL || tls_try_verify_hosts != NULL) &&
-     tls_verify_certificates == NULL)
+if ((tls_verify_hosts || tls_try_verify_hosts) && !tls_verify_certificates)
   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_");
+    tls_verify_hosts ? "" : "try_");
 
 /* This also checks that the library linkage is working and we can call
 routines in it, so call even if tls_require_ciphers is unset */
-if (!tls_dropprivs_validate_require_cipher())
+if (!tls_dropprivs_validate_require_cipher(nowarn))
   exit(1);
 
 /* Magic number: at time of writing, 1024 has been the long-standing value
@@ -3449,24 +3615,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
   }
-
-if (gnutls_require_kx || gnutls_require_mac || gnutls_require_proto)
-  log_write(0, LOG_MAIN, "WARNING: main options"
-      " gnutls_require_kx, gnutls_require_mac and gnutls_require_protocols"
-      " are obsolete\n");
 #endif /*SUPPORT_TLS*/
+
+if (!nowarn && !keep_environment && environ && *environ)
+  log_write(0, LOG_MAIN,
+      "Warning: purging the environment.\n"
+      " Suggested action: use keep_environment.");
 }
 
 
@@ -3497,7 +3663,7 @@ init_driver(driver_instance *d, driver_info *drivers_available,
 driver_info *dd;
 
 for (dd = drivers_available; dd->driver_name[0] != 0;
-     dd = (driver_info *)(((uschar *)dd) + size_of_info))
+     dd = (driver_info *)((US dd) + size_of_info))
   {
   if (Ustrcmp(d->driver_name, dd->driver_name) == 0)
     {
@@ -3579,15 +3745,15 @@ 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);
       d = NULL;
       }
-    read_macro_assignment(buffer);
+    if (!macro_read_assignment(buffer)) exim_exit(EXIT_FAILURE, US"");
     continue;
     }
 
@@ -3601,9 +3767,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);
@@ -3611,7 +3777,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);
@@ -3622,7 +3788,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 */
@@ -3640,8 +3806,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
@@ -3650,7 +3816,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);
     }
 
@@ -3658,11 +3824,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. */
 
@@ -3672,9 +3836,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);
@@ -3712,7 +3876,7 @@ for (ol = d->info->options; ol < d->info->options + count; ol++)
   int type = ol->type & opt_mask;
   if (type != opt_stringptr) continue;
   options_block = ((ol->type & opt_public) == 0)? d->options_block : (void *)d;
-  value = *(uschar **)((uschar *)options_block + (long int)(ol->value));
+  value = *(uschar **)(US options_block + (long int)(ol->value));
   if (value != NULL && (ss = Ustrstr(value, s)) != NULL)
     {
     if (ss <= value || (ss[-1] != '$' && ss[-1] != '{') ||
@@ -3788,14 +3952,14 @@ else if (len == 7 && strncmpic(pp, US"timeout", len) == 0)
     static int values[] =
       { 'A',   'M',    RTEF_CTOUT,  RTEF_CTOUT|'A', RTEF_CTOUT|'M' };
 
-    for (i = 0; i < sizeof(extras)/sizeof(uschar *); i++)
+    for (i = 0; i < nelem(extras); i++)
       if (strncmpic(x, extras[i], xlen) == 0)
         {
         *more_errno = values[i];
         break;
         }
 
-    if (i >= sizeof(extras)/sizeof(uschar *))
+    if (i >= nelem(extras))
       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 "
@@ -4024,6 +4188,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 */
@@ -4033,22 +4198,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);
-      }
-    }
   }
 }
 
@@ -4092,7 +4254,7 @@ between ACLs. */
 
 acl_line = get_config_line();
 
-while(acl_line != NULL)
+while(acl_line)
   {
   uschar name[64];
   tree_node *node;
@@ -4101,7 +4263,7 @@ while(acl_line != NULL)
   p = readconf_readname(name, sizeof(name), acl_line);
   if (isupper(*name) && *p == '=')
     {
-    read_macro_assignment(acl_line);
+    if (!macro_read_assignment(acl_line)) exim_exit(EXIT_FAILURE, US"");
     acl_line = get_config_line();
     continue;
     }
@@ -4145,7 +4307,7 @@ log_write(0, LOG_PANIC_DIE|LOG_CONFIG_IN, "local_scan() options not supported: "
 #else
 
 uschar *p;
-while ((p = get_config_line()) != NULL)
+while ((p = get_config_line()))
   {
   (void) readconf_handle_option(p, local_scan_options, local_scan_options_count,
     NULL, US"local_scan option \"%s\" unknown");
@@ -4193,7 +4355,7 @@ while(next_section[0] != 0)
   {
   int bit;
   int first = 0;
-  int last = sizeof(section_list) / sizeof(uschar *);
+  int last = nelem(section_list);
   int mid = last/2;
   int n = Ustrlen(next_section);
 
@@ -4235,20 +4397,21 @@ while(next_section[0] != 0)
 void
 readconf_save_config(const uschar *s)
 {
-  save_config_line(string_sprintf("# Exim Configuration (%s)",
-    running_in_test_harness ? US"X" : 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));
+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)
 {
@@ -4268,10 +4431,10 @@ 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)
+print_config(BOOL admin, BOOL terse)
 {
 config_line_item *i;
-const int TS = 2;
+const int TS = terse ? 0 : 2;
 int indent = 0;
 
 for (i = config_lines; i; i = i->next)
@@ -4311,7 +4474,7 @@ for (i = config_lines; i; i = i->next)
   /* begin lines are left aligned */
   else if (Ustrncmp(current, "begin", 5) == 0 && isspace(current[5]))
     {
-    puts("");
+    if (!terse) puts("");
     puts(CCS current);
     indent = TS;
     }
@@ -4319,7 +4482,8 @@ for (i = config_lines; i; i = i->next)
   /* router/acl/transport block names */
   else if (current[Ustrlen(current)-1] == ':' && !Ustrchr(current, '='))
     {
-    printf("\n%*s%s\n", TS, "", current);
+    if (!terse) puts("");
+    printf("%*s%s\n", TS, "", current);
     indent = 2 * TS;
     }
 
@@ -4346,6 +4510,7 @@ for (i = config_lines; i; i = i->next)
   }
 }
 
+#endif /*!MACRO_PREDEF*/
 /* vi: aw ai sw=2
 */
 /* End of readconf.c */