openssl guidance: install shared libraries too
[exim.git] / src / src / exim.c
index 14e0b9d67d76a49f2cda24ed9c0a6975b7998ef3..d71af0ac4056768586320b24e8b00b615ae19a03 100644 (file)
@@ -2,7 +2,7 @@
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
-/* Copyright (c) University of Cambridge 1995 - 2016 */
+/* Copyright (c) University of Cambridge 1995 - 2017 */
 /* See the file NOTICE for conditions of use and distribution. */
 
 
 /* See the file NOTICE for conditions of use and distribution. */
 
 
@@ -12,7 +12,7 @@ Also a few functions that don't naturally fit elsewhere. */
 
 #include "exim.h"
 
 
 #include "exim.h"
 
-#ifdef __GLIBC__
+#if defined(__GLIBC__) && !defined(__UCLIBC__)
 # include <gnu/libc-version.h>
 #endif
 
 # include <gnu/libc-version.h>
 #endif
 
@@ -174,10 +174,8 @@ Returns:   nothing
 void
 set_process_info(const char *format, ...)
 {
 void
 set_process_info(const char *format, ...)
 {
-int len;
+int len = sprintf(CS process_info, "%5d ", (int)getpid());
 va_list ap;
 va_list ap;
-sprintf(CS process_info, "%5d ", (int)getpid());
-len = Ustrlen(process_info);
 va_start(ap, format);
 if (!string_vformat(process_info + len, PROCESS_INFO_SIZE - len - 2, format, ap))
   Ustrcpy(process_info + len, "**** string overflowed buffer ****");
 va_start(ap, format);
 if (!string_vformat(process_info + len, PROCESS_INFO_SIZE - len - 2, format, ap))
   Ustrcpy(process_info + len, "**** string overflowed buffer ****");
@@ -214,8 +212,7 @@ int fd;
 
 os_restarting_signal(sig, usr1_handler);
 
 
 os_restarting_signal(sig, usr1_handler);
 
-fd = Uopen(process_log_path, O_APPEND|O_WRONLY, LOG_MODE);
-if (fd < 0)
+if ((fd = Uopen(process_log_path, O_APPEND|O_WRONLY, LOG_MODE)) < 0)
   {
   /* If we are already running as the Exim user, try to create it in the
   current process (assuming spool_directory exists). Otherwise, if we are
   {
   /* If we are already running as the Exim user, try to create it in the
   current process (assuming spool_directory exists). Otherwise, if we are
@@ -347,7 +344,7 @@ Arguments:
 Returns:      -1, 0, or +1
 */
 
 Returns:      -1, 0, or +1
 */
 
-int
+static int
 exim_tvcmp(struct timeval *t1, struct timeval *t2)
 {
 if (t1->tv_sec > t2->tv_sec) return +1;
 exim_tvcmp(struct timeval *t1, struct timeval *t2)
 {
 if (t1->tv_sec > t2->tv_sec) return +1;
@@ -367,7 +364,7 @@ return 0;
 /* Exim uses a time + a pid to generate a unique identifier in two places: its
 message IDs, and in file names for maildir deliveries. Because some OS now
 re-use pids within the same second, sub-second times are now being used.
 /* Exim uses a time + a pid to generate a unique identifier in two places: its
 message IDs, and in file names for maildir deliveries. Because some OS now
 re-use pids within the same second, sub-second times are now being used.
-However, for absolute certaintly, we must ensure the clock has ticked before
+However, for absolute certainty, we must ensure the clock has ticked before
 allowing the relevant process to complete. At the time of implementation of
 this code (February 2003), the speed of processors is such that the clock will
 invariably have ticked already by the time a process has done its job. This
 allowing the relevant process to complete. At the time of implementation of
 this code (February 2003), the speed of processors is such that the clock will
 invariably have ticked already by the time a process has done its job. This
@@ -661,12 +658,13 @@ Returns:     does not return
 */
 
 void
 */
 
 void
-exim_exit(int rc)
+exim_exit(int rc, const uschar * process)
 {
 search_tidyup();
 DEBUG(D_any)
 {
 search_tidyup();
 DEBUG(D_any)
-  debug_printf(">>>>>>>>>>>>>>>> Exim pid=%d terminating with rc=%d "
-    ">>>>>>>>>>>>>>>>\n", (int)getpid(), rc);
+  debug_printf(">>>>>>>>>>>>>>>> Exim pid=%d %s%s%sterminating with rc=%d "
+    ">>>>>>>>>>>>>>>>\n", (int)getpid(),
+    process ? "(" : "", process, process ? ") " : "", rc);
 exit(rc);
 }
 
 exit(rc);
 }
 
@@ -842,6 +840,16 @@ fprintf(f, "Support for:");
 #ifdef SUPPORT_SOCKS
   fprintf(f, " SOCKS");
 #endif
 #ifdef SUPPORT_SOCKS
   fprintf(f, " SOCKS");
 #endif
+#ifdef TCP_FASTOPEN
+  deliver_init();
+  if (tcp_fastopen_ok) fprintf(f, " TCP_Fast_Open");
+#endif
+#ifdef EXPERIMENTAL_LMDB
+  fprintf(f, " Experimental_LMDB");
+#endif
+#ifdef EXPERIMENTAL_QUEUEFILE
+  fprintf(f, " Experimental_QUEUEFILE");
+#endif
 #ifdef EXPERIMENTAL_SPF
   fprintf(f, " Experimental_SPF");
 #endif
 #ifdef EXPERIMENTAL_SPF
   fprintf(f, " Experimental_SPF");
 #endif
@@ -887,6 +895,9 @@ fprintf(f, "Lookups (built-in):");
 #if defined(LOOKUP_LDAP) && LOOKUP_LDAP!=2
   fprintf(f, " ldap ldapdn ldapm");
 #endif
 #if defined(LOOKUP_LDAP) && LOOKUP_LDAP!=2
   fprintf(f, " ldap ldapdn ldapm");
 #endif
+#ifdef EXPERIMENTAL_LMDB
+  fprintf(f, " lmdb");
+#endif
 #if defined(LOOKUP_MYSQL) && LOOKUP_MYSQL!=2
   fprintf(f, " mysql");
 #endif
 #if defined(LOOKUP_MYSQL) && LOOKUP_MYSQL!=2
   fprintf(f, " mysql");
 #endif
@@ -992,6 +1003,9 @@ fprintf(f, "Transports:");
 #ifdef TRANSPORT_PIPE
   fprintf(f, " pipe");
 #endif
 #ifdef TRANSPORT_PIPE
   fprintf(f, " pipe");
 #endif
+#ifdef EXPERIMENTAL_QUEUEFILE
+  fprintf(f, " queuefile");
+#endif
 #ifdef TRANSPORT_SMTP
   fprintf(f, " smtp");
 #endif
 #ifdef TRANSPORT_SMTP
   fprintf(f, " smtp");
 #endif
@@ -1006,6 +1020,8 @@ if (fixed_never_users[0] > 0)
   fprintf(f, "%d\n", (unsigned int)fixed_never_users[i]);
   }
 
   fprintf(f, "%d\n", (unsigned int)fixed_never_users[i]);
   }
 
+fprintf(f, "Configure owner: %d:%d\n", config_uid, config_gid);
+
 fprintf(f, "Size of off_t: " SIZE_T_FMT "\n", sizeof(off_t));
 
 /* Everything else is details which are only worth reporting when debugging.
 fprintf(f, "Size of off_t: " SIZE_T_FMT "\n", sizeof(off_t));
 
 /* Everything else is details which are only worth reporting when debugging.
@@ -1029,7 +1045,7 @@ DEBUG(D_any) do {
   fprintf(f, "Compiler: <unknown>\n");
 #endif
 
   fprintf(f, "Compiler: <unknown>\n");
 #endif
 
-#ifdef __GLIBC__
+#if defined(__GLIBC__) && !defined(__UCLIBC__)
   fprintf(f, "Library version: Glibc: Compile: %d.%d\n",
                __GLIBC__, __GLIBC_MINOR__);
   if (__GLIBC_PREREQ(2, 1))
   fprintf(f, "Library version: Glibc: Compile: %d.%d\n",
                __GLIBC__, __GLIBC_MINOR__);
   if (__GLIBC_PREREQ(2, 1))
@@ -1135,8 +1151,7 @@ uschar *
 local_part_quote(uschar *lpart)
 {
 BOOL needs_quote = FALSE;
 local_part_quote(uschar *lpart)
 {
 BOOL needs_quote = FALSE;
-int size, ptr;
-uschar *yield;
+gstring * g;
 uschar *t;
 
 for (t = lpart; !needs_quote && *t != 0; t++)
 uschar *t;
 
 for (t = lpart; !needs_quote && *t != 0; t++)
@@ -1147,26 +1162,24 @@ for (t = lpart; !needs_quote && *t != 0; t++)
 
 if (!needs_quote) return lpart;
 
 
 if (!needs_quote) return lpart;
 
-size = ptr = 0;
-yield = string_catn(NULL, &size, &ptr, US"\"", 1);
+g = string_catn(NULL, US"\"", 1);
 
 for (;;)
   {
   uschar *nq = US Ustrpbrk(lpart, "\\\"");
   if (nq == NULL)
     {
 
 for (;;)
   {
   uschar *nq = US Ustrpbrk(lpart, "\\\"");
   if (nq == NULL)
     {
-    yield = string_cat(yield, &size, &ptr, lpart);
+    g = string_cat(g, lpart);
     break;
     }
     break;
     }
-  yield = string_catn(yield, &size, &ptr, lpart, nq - lpart);
-  yield = string_catn(yield, &size, &ptr, US"\\", 1);
-  yield = string_catn(yield, &size, &ptr, nq, 1);
+  g = string_catn(g, lpart, nq - lpart);
+  g = string_catn(g, US"\\", 1);
+  g = string_catn(g, nq, 1);
   lpart = nq + 1;
   }
 
   lpart = nq + 1;
   }
 
-yield = string_catn(yield, &size, &ptr, US"\"", 1);
-yield[ptr] = 0;
-return yield;
+g = string_catn(g, US"\"", 1);
+return string_from_gstring(g);
 }
 
 
 }
 
 
@@ -1239,11 +1252,9 @@ static uschar *
 get_stdinput(char *(*fn_readline)(const char *), void(*fn_addhist)(const char *))
 {
 int i;
 get_stdinput(char *(*fn_readline)(const char *), void(*fn_addhist)(const char *))
 {
 int i;
-int size = 0;
-int ptr = 0;
-uschar *yield = NULL;
+gstring * g = NULL;
 
 
-if (fn_readline == NULL) { printf("> "); fflush(stdout); }
+if (!fn_readline) { printf("> "); fflush(stdout); }
 
 for (i = 0;; i++)
   {
 
 for (i = 0;; i++)
   {
@@ -1278,23 +1289,22 @@ for (i = 0;; i++)
     while (p < ss && isspace(*p)) p++;   /* leading space after cont */
     }
 
     while (p < ss && isspace(*p)) p++;   /* leading space after cont */
     }
 
-  yield = string_catn(yield, &size, &ptr, p, ss - p);
+  g = string_catn(g, p, ss - p);
 
   #ifdef USE_READLINE
 
   #ifdef USE_READLINE
-  if (fn_readline != NULL) free(readline_line);
+  if (fn_readline) free(readline_line);
   #endif
 
   #endif
 
-  /* yield can only be NULL if ss==p */
-  if (ss == p || yield[ptr-1] != '\\')
-    {
-    if (yield) yield[ptr] = 0;
+  /* g can only be NULL if ss==p */
+  if (ss == p || g->s[g->ptr-1] != '\\')
     break;
     break;
-    }
-  yield[--ptr] = 0;
+
+  --g->ptr;
+  (void) string_from_gstring(g);
   }
 
   }
 
-if (yield == NULL) printf("\n");
-return yield;
+if (!g) printf("\n");
+return string_from_gstring(g);
 }
 
 
 }
 
 
@@ -1316,7 +1326,7 @@ static void
 exim_usage(uschar *progname)
 {
 
 exim_usage(uschar *progname)
 {
 
-/* Handle specific program invocation varients */
+/* Handle specific program invocation variants */
 if (Ustrcmp(progname, US"-mailq") == 0)
   {
   fprintf(stderr,
 if (Ustrcmp(progname, US"-mailq") == 0)
   {
   fprintf(stderr,
@@ -1343,12 +1353,12 @@ exit(EXIT_FAILURE);
 /* Typically, Exim will drop privileges if macros are supplied.  In some
 cases, we want to not do so.
 
 /* Typically, Exim will drop privileges if macros are supplied.  In some
 cases, we want to not do so.
 
-Arguments:    none (macros is a global)
+Arguments:    opt_D_used - true if the commandline had a "-D" option
 Returns:      true if trusted, false otherwise
 */
 
 static BOOL
 Returns:      true if trusted, false otherwise
 */
 
 static BOOL
-macros_trusted(void)
+macros_trusted(BOOL opt_D_used)
 {
 #ifdef WHITELIST_D_MACROS
 macro_item *m;
 {
 #ifdef WHITELIST_D_MACROS
 macro_item *m;
@@ -1358,7 +1368,7 @@ size_t len;
 BOOL prev_char_item, found;
 #endif
 
 BOOL prev_char_item, found;
 #endif
 
-if (macros == NULL)
+if (!opt_D_used)
   return TRUE;
 #ifndef WHITELIST_D_MACROS
 return FALSE;
   return TRUE;
 #ifndef WHITELIST_D_MACROS
 return FALSE;
@@ -1415,8 +1425,9 @@ for (p = whitelisted, i = 0; (p != end) && (i < white_count); ++p)
   }
 whites[i] = NULL;
 
   }
 whites[i] = NULL;
 
-/* The list of macros should be very short.  Accept the N*M complexity. */
-for (m = macros; m != NULL; m = m->next)
+/* The list of commandline macros should be very short.
+Accept the N*M complexity. */
+for (m = macros; m; m = m->next) if (m->command_line)
   {
   found = FALSE;
   for (w = whites; *w; ++w)
   {
   found = FALSE;
   for (w = whites; *w; ++w)
@@ -1427,10 +1438,9 @@ for (m = macros; m != NULL; m = m->next)
       }
   if (!found)
     return FALSE;
       }
   if (!found)
     return FALSE;
-  if (m->replacement == NULL)
+  if (!m->replacement)
     continue;
     continue;
-  len = Ustrlen(m->replacement);
-  if (len == 0)
+  if ((len = m->replen) == 0)
     continue;
   n = pcre_exec(regex_whitelisted_macro, NULL, CS m->replacement, len,
    0, PCRE_EOPT, NULL, 0);
     continue;
   n = pcre_exec(regex_whitelisted_macro, NULL, CS m->replacement, len,
    0, PCRE_EOPT, NULL, 0);
@@ -1447,6 +1457,39 @@ return TRUE;
 }
 
 
 }
 
 
+/*************************************************
+*          Expansion testing                    *
+*************************************************/
+
+/* Expand and print one item, doing macro-processing.
+
+Arguments:
+  item         line for expansion
+*/
+
+static void
+expansion_test_line(uschar * line)
+{
+int len;
+BOOL dummy_macexp;
+
+Ustrncpy(big_buffer, line, big_buffer_size);
+big_buffer[big_buffer_size-1] = '\0';
+len = Ustrlen(big_buffer);
+
+(void) macros_expand(0, &len, &dummy_macexp);
+
+if (isupper(big_buffer[0]))
+  {
+  if (macro_read_assignment(big_buffer))
+    printf("Defined macro '%s'\n", mlast->name);
+  }
+else
+  if ((line = expand_string(big_buffer))) printf("%s\n", CS line);
+  else printf("Failed: %s\n", expand_string_message);
+}
+
+
 /*************************************************
 *          Entry point and high-level code       *
 *************************************************/
 /*************************************************
 *          Entry point and high-level code       *
 *************************************************/
@@ -1506,6 +1549,7 @@ BOOL list_config = FALSE;
 BOOL local_queue_only;
 BOOL more = TRUE;
 BOOL one_msg_action = FALSE;
 BOOL local_queue_only;
 BOOL more = TRUE;
 BOOL one_msg_action = FALSE;
+BOOL opt_D_used = FALSE;
 BOOL queue_only_set = FALSE;
 BOOL receiving_message = TRUE;
 BOOL sender_ident_set = FALSE;
 BOOL queue_only_set = FALSE;
 BOOL receiving_message = TRUE;
 BOOL sender_ident_set = FALSE;
@@ -1647,8 +1691,7 @@ os_non_restarting_signal(SIGALRM, sigalrm_handler);
 /* Ensure we have a buffer for constructing log entries. Use malloc directly,
 because store_malloc writes a log entry on failure. */
 
 /* Ensure we have a buffer for constructing log entries. Use malloc directly,
 because store_malloc writes a log entry on failure. */
 
-log_buffer = (uschar *)malloc(LOG_BUFFER_SIZE);
-if (log_buffer == NULL)
+if (!(log_buffer = US malloc(LOG_BUFFER_SIZE)))
   {
   fprintf(stderr, "exim: failed to get store for log buffer\n");
   exit(EXIT_FAILURE);
   {
   fprintf(stderr, "exim: failed to get store for log buffer\n");
   exit(EXIT_FAILURE);
@@ -1884,7 +1927,7 @@ for (i = 1; i < argc; i++)
     break;
     }
 
     break;
     }
 
-  /* An option consistion of -- terminates the options */
+  /* An option consisting of -- terminates the options */
 
   if (Ustrcmp(arg, "--") == 0)
     {
 
   if (Ustrcmp(arg, "--") == 0)
     {
@@ -2278,7 +2321,7 @@ for (i = 1; i < argc; i++)
       #ifdef ALT_CONFIG_PREFIX
       int sep = 0;
       int len = Ustrlen(ALT_CONFIG_PREFIX);
       #ifdef ALT_CONFIG_PREFIX
       int sep = 0;
       int len = Ustrlen(ALT_CONFIG_PREFIX);
-      uschar *list = argrest;
+      const uschar *list = argrest;
       uschar *filename;
       while((filename = string_nextinlist(&list, &sep, big_buffer,
              big_buffer_size)) != NULL)
       uschar *filename;
       while((filename = string_nextinlist(&list, &sep, big_buffer,
              big_buffer_size)) != NULL)
@@ -2406,11 +2449,11 @@ for (i = 1; i < argc; i++)
     #else
       {
       int ptr = 0;
     #else
       {
       int ptr = 0;
-      macro_item *mlast = NULL;
       macro_item *m;
       uschar name[24];
       uschar *s = argrest;
 
       macro_item *m;
       uschar name[24];
       uschar *s = argrest;
 
+      opt_D_used = TRUE;
       while (isspace(*s)) s++;
 
       if (*s < 'A' || *s > 'Z')
       while (isspace(*s)) s++;
 
       if (*s < 'A' || *s > 'Z')
@@ -2434,22 +2477,14 @@ for (i = 1; i < argc; i++)
         while (isspace(*s)) s++;
         }
 
         while (isspace(*s)) s++;
         }
 
-      for (m = macros; m != NULL; m = m->next)
-        {
+      for (m = macros; m; m = m->next)
         if (Ustrcmp(m->name, name) == 0)
           {
           fprintf(stderr, "exim: duplicated -D in command line\n");
           exit(EXIT_FAILURE);
           }
         if (Ustrcmp(m->name, name) == 0)
           {
           fprintf(stderr, "exim: duplicated -D in command line\n");
           exit(EXIT_FAILURE);
           }
-        mlast = m;
-        }
 
 
-      m = store_get(sizeof(macro_item) + Ustrlen(name));
-      m->next = NULL;
-      m->command_line = TRUE;
-      if (mlast == NULL) macros = m; else mlast->next = m;
-      Ustrcpy(m->name, name);
-      m->replacement = string_copy(s);
+      m = macro_create(string_copy(name), string_copy(s), TRUE);
 
       if (clmacro_count >= MAX_CLMACROS)
         {
 
       if (clmacro_count >= MAX_CLMACROS)
         {
@@ -2700,18 +2735,19 @@ for (i = 1; i < argc; i++)
         return EXIT_FAILURE;
         }
 
         return EXIT_FAILURE;
         }
 
-      /* Set up $sending_ip_address and $sending_port */
+      /* Set up $sending_ip_address and $sending_port, unless proxied */
 
 
-      if (getsockname(fileno(stdin), (struct sockaddr *)(&interface_sock),
-          &size) == 0)
-        sending_ip_address = host_ntoa(-1, &interface_sock, NULL,
-          &sending_port);
-      else
-        {
-        fprintf(stderr, "exim: getsockname() failed after -MC option: %s\n",
-          strerror(errno));
-        return EXIT_FAILURE;
-        }
+      if (!continue_proxy_cipher)
+       if (getsockname(fileno(stdin), (struct sockaddr *)(&interface_sock),
+           &size) == 0)
+         sending_ip_address = host_ntoa(-1, &interface_sock, NULL,
+           &sending_port);
+       else
+         {
+         fprintf(stderr, "exim: getsockname() failed after -MC option: %s\n",
+           strerror(errno));
+         return EXIT_FAILURE;
+         }
 
       if (running_in_test_harness) millisleep(500);
       break;
 
       if (running_in_test_harness) millisleep(500);
       break;
@@ -2719,7 +2755,7 @@ for (i = 1; i < argc; i++)
 
     else if (*argrest == 'C' && argrest[1] && !argrest[2])
       {
 
     else if (*argrest == 'C' && argrest[1] && !argrest[2])
       {
-       switch(argrest[1])
+      switch(argrest[1])
        {
     /* -MCA: set the smtp_authenticated flag; this is useful only when it
     precedes -MC (see above). The flag indicates that the host to which
        {
     /* -MCA: set the smtp_authenticated flag; this is useful only when it
     precedes -MC (see above). The flag indicates that the host to which
@@ -2730,7 +2766,7 @@ for (i = 1; i < argc; i++)
     /* -MCD: set the smtp_use_dsn flag; this indicates that the host
        that exim is connected to supports the esmtp extension DSN */
 
     /* -MCD: set the smtp_use_dsn flag; this indicates that the host
        that exim is connected to supports the esmtp extension DSN */
 
-       case 'D': smtp_peer_options |= PEER_OFFERED_DSN; break;
+       case 'D': smtp_peer_options |= OPTION_DSN; break;
 
     /* -MCG: set the queue name, to a non-default value */
 
 
     /* -MCG: set the queue name, to a non-default value */
 
@@ -2740,12 +2776,12 @@ for (i = 1; i < argc; i++)
 
     /* -MCK: the peer offered CHUNKING.  Must precede -MC */
 
 
     /* -MCK: the peer offered CHUNKING.  Must precede -MC */
 
-       case 'K': smtp_peer_options |= PEER_OFFERED_CHUNKING; break;
+       case 'K': smtp_peer_options |= OPTION_CHUNKING; break;
 
     /* -MCP: set the smtp_use_pipelining flag; this is useful only when
     it preceded -MC (see above) */
 
 
     /* -MCP: set the smtp_use_pipelining flag; this is useful only when
     it preceded -MC (see above) */
 
-       case 'P': smtp_peer_options |= PEER_OFFERED_PIPE; break;
+       case 'P': smtp_peer_options |= OPTION_PIPE; break;
 
     /* -MCQ: pass on the pid of the queue-running process that started
     this chain of deliveries and the fd of its synchronizing pipe; this
 
     /* -MCQ: pass on the pid of the queue-running process that started
     this chain of deliveries and the fd of its synchronizing pipe; this
@@ -2760,14 +2796,27 @@ for (i = 1; i < argc; i++)
     /* -MCS: set the smtp_use_size flag; this is useful only when it
     precedes -MC (see above) */
 
     /* -MCS: set the smtp_use_size flag; this is useful only when it
     precedes -MC (see above) */
 
-       case 'S': smtp_peer_options |= PEER_OFFERED_SIZE; break;
+       case 'S': smtp_peer_options |= OPTION_SIZE; break;
 
 #ifdef SUPPORT_TLS
 
 #ifdef SUPPORT_TLS
+    /* -MCt: similar to -MCT below but the connection is still open
+    via a proxy proces which handles the TLS context and coding.
+    Require three arguments for the proxied local address and port,
+    and the TLS cipher.  */
+
+       case 't': if (++i < argc) sending_ip_address = argv[i];
+                 else badarg = TRUE;
+                 if (++i < argc) sending_port = (int)(Uatol(argv[i]));
+                 else badarg = TRUE;
+                 if (++i < argc) continue_proxy_cipher = argv[i];
+                 else badarg = TRUE;
+                 /*FALLTHROUGH*/
+
     /* -MCT: set the tls_offered flag; this is useful only when it
     precedes -MC (see above). The flag indicates that the host to which
     Exim is connected has offered TLS support. */
 
     /* -MCT: set the tls_offered flag; this is useful only when it
     precedes -MC (see above). The flag indicates that the host to which
     Exim is connected has offered TLS support. */
 
-       case 'T': smtp_peer_options |= PEER_OFFERED_TLS; break;
+       case 'T': smtp_peer_options |= OPTION_TLS; break;
 #endif
 
        default:  badarg = TRUE; break;
 #endif
 
        default:  badarg = TRUE; break;
@@ -3084,7 +3133,14 @@ for (i = 1; i < argc; i++)
 
       /* -oMr: Received protocol */
 
 
       /* -oMr: Received protocol */
 
-      else if (Ustrcmp(argrest, "Mr") == 0) received_protocol = argv[++i];
+      else if (Ustrcmp(argrest, "Mr") == 0)
+
+        if (received_protocol)
+          {
+          fprintf(stderr, "received_protocol is set already\n");
+          exit(EXIT_FAILURE);
+          }
+        else received_protocol = argv[++i];
 
       /* -oMs: Set sender host name */
 
 
       /* -oMs: Set sender host name */
 
@@ -3173,21 +3229,30 @@ for (i = 1; i < argc; i++)
     which sets the host protocol and host name */
 
     if (*argrest == 0)
     which sets the host protocol and host name */
 
     if (*argrest == 0)
-      {
-      if (i+1 < argc) argrest = argv[++i]; else
+      if (i+1 < argc)
+       argrest = argv[++i];
+      else
         { badarg = TRUE; break; }
         { badarg = TRUE; break; }
-      }
 
     if (*argrest != 0)
       {
 
     if (*argrest != 0)
       {
-      uschar *hn = Ustrchr(argrest, ':');
-      if (hn == NULL)
+      uschar *hn;
+
+      if (received_protocol)
         {
         {
-        received_protocol = argrest;
+        fprintf(stderr, "received_protocol is set already\n");
+        exit(EXIT_FAILURE);
         }
         }
+
+      hn = Ustrchr(argrest, ':');
+      if (hn == NULL)
+        received_protocol = argrest;
       else
         {
       else
         {
+       int old_pool = store_pool;
+       store_pool = POOL_PERM;
         received_protocol = string_copyn(argrest, hn - argrest);
         received_protocol = string_copyn(argrest, hn - argrest);
+       store_pool = old_pool;
         sender_host_name = hn + 1;
         }
       }
         sender_host_name = hn + 1;
         }
       }
@@ -3680,7 +3745,7 @@ configuration file changes and macro definitions haven't happened. */
 
 if ((                                            /* EITHER */
     (!trusted_config ||                          /* Config changed, or */
 
 if ((                                            /* EITHER */
     (!trusted_config ||                          /* Config changed, or */
-     !macros_trusted()) &&                       /*  impermissible macros and */
+     !macros_trusted(opt_D_used)) &&            /*  impermissible macros and */
     real_uid != root_uid &&                      /* Not root, and */
     !running_in_test_harness                     /* Not fudged */
     ) ||                                         /*   OR   */
     real_uid != root_uid &&                      /* Not root, and */
     !running_in_test_harness                     /* Not fudged */
     ) ||                                         /*   OR   */
@@ -3761,12 +3826,9 @@ NOTE: immediatly after opening the configuration file we change the working
 directory to "/"! Later we change to $spool_directory. We do it there, because
 during readconf_main() some expansion takes place already. */
 
 directory to "/"! Later we change to $spool_directory. We do it there, because
 during readconf_main() some expansion takes place already. */
 
-/* Store the initial cwd before we change directories */
-if ((initial_cwd = os_getcwd(NULL, 0)) == NULL)
-  {
-  perror("exim: can't get the current working directory");
-  exit(EXIT_FAILURE);
-  }
+/* Store the initial cwd before we change directories.  Can be NULL if the
+dir has already been unlinked. */
+initial_cwd = os_getcwd(NULL, 0);
 
 /* checking:
     -be[m] expansion test        -
 
 /* checking:
     -be[m] expansion test        -
@@ -3786,6 +3848,10 @@ defined) */
 
 readconf_main(checking || list_options);
 
 
 readconf_main(checking || list_options);
 
+if (builtin_macros_create_trigger) DEBUG(D_any)
+  debug_printf("Builtin macros created (expensive) due to config line '%.*s'\n",
+    Ustrlen(builtin_macros_create_trigger)-1, builtin_macros_create_trigger);
+
 /* Now in directory "/" */
 
 if (cleanup_environment() == FALSE)
 /* Now in directory "/" */
 
 if (cleanup_environment() == FALSE)
@@ -3805,17 +3871,13 @@ if (real_uid == root_uid || real_uid == exim_uid || real_gid == exim_gid)
 else
   {
   int i, j;
 else
   {
   int i, j;
-  for (i = 0; i < group_count; i++)
-    {
-    if (group_list[i] == exim_gid) admin_user = TRUE;
-    else if (admin_groups != NULL)
-      {
-      for (j = 1; j <= (int)(admin_groups[0]); j++)
+  for (i = 0; i < group_count && !admin_user; i++)
+    if (group_list[i] == exim_gid)
+      admin_user = TRUE;
+    else if (admin_groups)
+      for (j = 1; j <= (int)admin_groups[0] && !admin_user; j++)
         if (admin_groups[j] == group_list[i])
         if (admin_groups[j] == group_list[i])
-          { admin_user = TRUE; break; }
-      }
-    if (admin_user) break;
-    }
+          admin_user = TRUE;
   }
 
 /* Another group of privileged users are the trusted users. These are root,
   }
 
 /* Another group of privileged users are the trusted users. These are root,
@@ -3829,29 +3891,28 @@ else
   {
   int i, j;
 
   {
   int i, j;
 
-  if (trusted_users != NULL)
-    {
-    for (i = 1; i <= (int)(trusted_users[0]); i++)
+  if (trusted_users)
+    for (i = 1; i <= (int)trusted_users[0] && !trusted_caller; i++)
       if (trusted_users[i] == real_uid)
       if (trusted_users[i] == real_uid)
-        { trusted_caller = TRUE; break; }
-    }
+        trusted_caller = TRUE;
 
 
-  if (!trusted_caller && trusted_groups != NULL)
-    {
-    for (i = 1; i <= (int)(trusted_groups[0]); i++)
-      {
+  if (trusted_groups)
+    for (i = 1; i <= (int)trusted_groups[0] && !trusted_caller; i++)
       if (trusted_groups[i] == real_gid)
         trusted_caller = TRUE;
       if (trusted_groups[i] == real_gid)
         trusted_caller = TRUE;
-      else for (j = 0; j < group_count; j++)
-        {
+      else for (j = 0; j < group_count && !trusted_caller; j++)
         if (trusted_groups[i] == group_list[j])
         if (trusted_groups[i] == group_list[j])
-          { trusted_caller = TRUE; break; }
-        }
-      if (trusted_caller) break;
-      }
-    }
+          trusted_caller = TRUE;
   }
 
   }
 
+/* At this point, we know if the user is privileged and some command-line
+options become possibly impermissible, depending upon the configuration file. */
+
+if (checking && commandline_checks_require_admin && !admin_user) {
+  fprintf(stderr, "exim: those command-line flags are set to require admin\n");
+  exit(EXIT_FAILURE);
+}
+
 /* Handle the decoding of logging options. */
 
 decode_bits(log_selector, log_selector_size, log_notall,
 /* Handle the decoding of logging options. */
 
 decode_bits(log_selector, log_selector_size, log_notall,
@@ -3932,7 +3993,6 @@ if (Ustrlen(syslog_processname) > 32)
     "syslog_processname is longer than 32 chars: aborting");
 
 if (log_oneline)
     "syslog_processname is longer than 32 chars: aborting");
 
 if (log_oneline)
-  {
   if (admin_user)
     {
     log_write(0, LOG_MAIN, "%s", log_oneline);
   if (admin_user)
     {
     log_write(0, LOG_MAIN, "%s", log_oneline);
@@ -3940,7 +4000,6 @@ if (log_oneline)
     }
   else
     return EXIT_FAILURE;
     }
   else
     return EXIT_FAILURE;
-  }
 
 /* In some operating systems, the environment variable TMPDIR controls where
 temporary files are created; Exim doesn't use these (apart from when delivering
 
 /* In some operating systems, the environment variable TMPDIR controls where
 temporary files are created; Exim doesn't use these (apart from when delivering
@@ -3954,17 +4013,14 @@ EXIM_TMPDIR by the build scripts.
 #ifdef EXIM_TMPDIR
   {
   uschar **p;
 #ifdef EXIM_TMPDIR
   {
   uschar **p;
-  if (environ) for (p = USS environ; *p != NULL; p++)
-    {
-    if (Ustrncmp(*p, "TMPDIR=", 7) == 0 &&
-        Ustrcmp(*p+7, EXIM_TMPDIR) != 0)
+  if (environ) for (p = USS environ; *p; p++)
+    if (Ustrncmp(*p, "TMPDIR=", 7) == 0 && Ustrcmp(*p+7, EXIM_TMPDIR) != 0)
       {
       {
-      uschar *newp = malloc(Ustrlen(EXIM_TMPDIR) + 8);
+      uschar * newp = store_malloc(Ustrlen(EXIM_TMPDIR) + 8);
       sprintf(CS newp, "TMPDIR=%s", EXIM_TMPDIR);
       *p = newp;
       DEBUG(D_any) debug_printf("reset TMPDIR=%s in environment\n", EXIM_TMPDIR);
       }
       sprintf(CS newp, "TMPDIR=%s", EXIM_TMPDIR);
       *p = newp;
       DEBUG(D_any) debug_printf("reset TMPDIR=%s in environment\n", EXIM_TMPDIR);
       }
-    }
   }
 #endif
 
   }
 #endif
 
@@ -3978,33 +4034,28 @@ about this earlier - but hopefully nothing will normally be logged earlier than
 this. We have to make a new environment if TZ is wrong, but don't bother if
 timestamps_utc is set, because then all times are in UTC anyway. */
 
 this. We have to make a new environment if TZ is wrong, but don't bother if
 timestamps_utc is set, because then all times are in UTC anyway. */
 
-if (timezone_string != NULL && strcmpic(timezone_string, US"UTC") == 0)
-  {
+if (timezone_string && strcmpic(timezone_string, US"UTC") == 0)
   timestamps_utc = TRUE;
   timestamps_utc = TRUE;
-  }
 else
   {
   uschar *envtz = US getenv("TZ");
 else
   {
   uschar *envtz = US getenv("TZ");
-  if ((envtz == NULL && timezone_string != NULL) ||
-      (envtz != NULL &&
-        (timezone_string == NULL ||
-         Ustrcmp(timezone_string, envtz) != 0)))
+  if (envtz
+      ? !timezone_string || Ustrcmp(timezone_string, envtz) != 0
+      : timezone_string != NULL
+     )
     {
     uschar **p = USS environ;
     uschar **new;
     uschar **newp;
     int count = 0;
     {
     uschar **p = USS environ;
     uschar **new;
     uschar **newp;
     int count = 0;
-    if (environ) while (*p++ != NULL) count++;
-    if (envtz == NULL) count++;
-    newp = new = malloc(sizeof(uschar *) * (count + 1));
-    if (environ) for (p = USS environ; *p != NULL; p++)
-      {
-      if (Ustrncmp(*p, "TZ=", 3) == 0) continue;
-      *newp++ = *p;
-      }
-    if (timezone_string != NULL)
+    if (environ) while (*p++) count++;
+    if (!envtz) count++;
+    newp = new = store_malloc(sizeof(uschar *) * (count + 1));
+    if (environ) for (p = USS environ; *p; p++)
+      if (Ustrncmp(*p, "TZ=", 3) != 0) *newp++ = *p;
+    if (timezone_string)
       {
       {
-      *newp = malloc(Ustrlen(timezone_string) + 4);
+      *newp = store_malloc(Ustrlen(timezone_string) + 4);
       sprintf(CS *newp++, "TZ=%s", timezone_string);
       }
     *newp = NULL;
       sprintf(CS *newp++, "TZ=%s", timezone_string);
       }
     *newp = NULL;
@@ -4036,16 +4087,15 @@ Exim user", but it hasn't, because either the -D option set macros, or the
       root for -C or -D, the caller must either be root or be invoking a
       trusted configuration file (when deliver_drop_privilege is false). */
 
       root for -C or -D, the caller must either be root or be invoking a
       trusted configuration file (when deliver_drop_privilege is false). */
 
-if (removed_privilege && (!trusted_config || macros != NULL) &&
-    real_uid == exim_uid)
-  {
+if (  removed_privilege
+   && (!trusted_config || opt_D_used)
+   && real_uid == exim_uid)
   if (deliver_drop_privilege)
     really_exim = TRUE; /* let logging work normally */
   else
     log_write(0, LOG_MAIN|LOG_PANIC,
       "exim user lost privilege for using %s option",
       trusted_config? "-D" : "-C");
   if (deliver_drop_privilege)
     really_exim = TRUE; /* let logging work normally */
   else
     log_write(0, LOG_MAIN|LOG_PANIC,
       "exim user lost privilege for using %s option",
       trusted_config? "-D" : "-C");
-  }
 
 /* Start up Perl interpreter if Perl support is configured and there is a
 perl_startup option, and the configuration or the command line specifies
 
 /* Start up Perl interpreter if Perl support is configured and there is a
 perl_startup option, and the configuration or the command line specifies
@@ -4105,9 +4155,8 @@ if (((debug_selector & D_any) != 0 || LOGGING(arguments))
       quote = US"";
       while (*pp != 0) if (isspace(*pp++)) { quote = US"\""; break; }
       }
       quote = US"";
       while (*pp != 0) if (isspace(*pp++)) { quote = US"\""; break; }
       }
-    sprintf(CS p, " %s%.*s%s", quote, (int)(big_buffer_size -
+    p += sprintf(CS p, " %s%.*s%s", quote, (int)(big_buffer_size -
       (p - big_buffer) - 4), printing, quote);
       (p - big_buffer) - 4), printing, quote);
-    while (*p) p++;
     }
 
   if (LOGGING(arguments))
     }
 
   if (LOGGING(arguments))
@@ -4128,6 +4177,7 @@ if (Uchdir(spool_directory) != 0)
   int dummy;
   (void)directory_make(spool_directory, US"", SPOOL_DIRECTORY_MODE, FALSE);
   dummy = /* quieten compiler */ Uchdir(spool_directory);
   int dummy;
   (void)directory_make(spool_directory, US"", SPOOL_DIRECTORY_MODE, FALSE);
   dummy = /* quieten compiler */ Uchdir(spool_directory);
+  dummy = dummy;       /* yet more compiler quietening, sigh */
   }
 
 /* Handle calls with the -bi option. This is a sendmail option to rebuild *the*
   }
 
 /* Handle calls with the -bi option. This is a sendmail option to rebuild *the*
@@ -4338,11 +4388,8 @@ if (!unprivileged &&                      /* originally had root AND */
         (msg_action_arg < 0 ||            /*       and               */
           msg_action != MSG_DELIVER) &&   /* not delivering and      */
         (!checking || !address_test_mode) /* not address checking    */
         (msg_action_arg < 0 ||            /*       and               */
           msg_action != MSG_DELIVER) &&   /* not delivering and      */
         (!checking || !address_test_mode) /* not address checking    */
-        )
-      ))
-  {
+   )  ) )
   exim_setugid(exim_uid, exim_gid, TRUE, US"privilege not needed");
   exim_setugid(exim_uid, exim_gid, TRUE, US"privilege not needed");
-  }
 
 /* When we are retaining a privileged uid, we still change to the exim gid. */
 
 
 /* When we are retaining a privileged uid, we still change to the exim gid. */
 
@@ -4356,7 +4403,6 @@ else
   there's no security risk.  For me, it's { exim -bV } on a just-built binary,
   no need to complain then. */
   if (rv == -1)
   there's no security risk.  For me, it's { exim -bV } on a just-built binary,
   no need to complain then. */
   if (rv == -1)
-    {
     if (!(unprivileged || removed_privilege))
       {
       fprintf(stderr,
     if (!(unprivileged || removed_privilege))
       {
       fprintf(stderr,
@@ -4366,7 +4412,6 @@ else
     else
       DEBUG(D_any) debug_printf("changing group to %ld failed: %s\n",
           (long int)exim_gid, strerror(errno));
     else
       DEBUG(D_any) debug_printf("changing group to %ld failed: %s\n",
           (long int)exim_gid, strerror(errno));
-    }
   }
 
 /* Handle a request to scan a file for malware */
   }
 
 /* Handle a request to scan a file for malware */
@@ -4467,7 +4512,7 @@ if (test_retry_arg >= 0)
   if (test_retry_arg >= argc)
     {
     printf("-brt needs a domain or address argument\n");
   if (test_retry_arg >= argc)
     {
     printf("-brt needs a domain or address argument\n");
-    exim_exit(EXIT_FAILURE);
+    exim_exit(EXIT_FAILURE, US"main");
     }
   s1 = argv[test_retry_arg++];
   s2 = NULL;
     }
   s1 = argv[test_retry_arg++];
   s2 = NULL;
@@ -4516,8 +4561,9 @@ if (test_retry_arg >= 0)
       }
     }
 
       }
     }
 
-  yield = retry_find_config(s1, s2, basic_errno, more_errno);
-  if (yield == NULL) printf("No retry information found\n"); else
+  if (!(yield = retry_find_config(s1, s2, basic_errno, more_errno)))
+    printf("No retry information found\n");
+  else
     {
     retry_rule *r;
     more_errno = yield->more_errno;
     {
     retry_rule *r;
     more_errno = yield->more_errno;
@@ -4549,7 +4595,7 @@ if (test_retry_arg >= 0)
       printf("auth_failed  ");
     else printf("*  ");
 
       printf("auth_failed  ");
     else printf("*  ");
 
-    for (r = yield->rules; r != NULL; r = r->next)
+    for (r = yield->rules; r; r = r->next)
       {
       printf("%c,%s", r->rule, readconf_printtime(r->timeout)); /* Do not */
       printf(",%s", readconf_printtime(r->p1));                 /* amalgamate */
       {
       printf("%c,%s", r->rule, readconf_printtime(r->timeout)); /* Do not */
       printf(",%s", readconf_printtime(r->p1));                 /* amalgamate */
@@ -4572,7 +4618,7 @@ if (test_retry_arg >= 0)
 
     printf("\n");
     }
 
     printf("\n");
     }
-  exim_exit(EXIT_SUCCESS);
+  exim_exit(EXIT_SUCCESS, US"main");
   }
 
 /* Handle a request to list one or more configuration options */
   }
 
 /* Handle a request to list one or more configuration options */
@@ -4596,14 +4642,14 @@ if (list_options)
         }
       else readconf_print(argv[i], NULL, flag_n);
       }
         }
       else readconf_print(argv[i], NULL, flag_n);
       }
-  exim_exit(EXIT_SUCCESS);
+  exim_exit(EXIT_SUCCESS, US"main");
   }
 
 if (list_config)
   {
   set_process_info("listing config");
   readconf_print(US"config", NULL, flag_n);
   }
 
 if (list_config)
   {
   set_process_info("listing config");
   readconf_print(US"config", NULL, flag_n);
-  exim_exit(EXIT_SUCCESS);
+  exim_exit(EXIT_SUCCESS, US"main");
   }
 
 
   }
 
 
@@ -4632,7 +4678,7 @@ if (msg_action_arg > 0 && msg_action != MSG_LOAD)
   if (prod_requires_admin && !admin_user)
     {
     fprintf(stderr, "exim: Permission denied\n");
   if (prod_requires_admin && !admin_user)
     {
     fprintf(stderr, "exim: Permission denied\n");
-    exim_exit(EXIT_FAILURE);
+    exim_exit(EXIT_FAILURE, US"main");
     }
   set_process_info("delivering specified messages");
   if (deliver_give_up) forced_delivery = deliver_force_thaw = TRUE;
     }
   set_process_info("delivering specified messages");
   if (deliver_give_up) forced_delivery = deliver_force_thaw = TRUE;
@@ -4651,11 +4697,11 @@ if (msg_action_arg > 0 && msg_action != MSG_LOAD)
       {
       fprintf(stderr, "failed to fork delivery process for %s: %s\n", argv[i],
         strerror(errno));
       {
       fprintf(stderr, "failed to fork delivery process for %s: %s\n", argv[i],
         strerror(errno));
-      exim_exit(EXIT_FAILURE);
+      exim_exit(EXIT_FAILURE, US"main");
       }
     else wait(&status);
     }
       }
     else wait(&status);
     }
-  exim_exit(EXIT_SUCCESS);
+  exim_exit(EXIT_SUCCESS, US"main");
   }
 
 
   }
 
 
@@ -4674,7 +4720,7 @@ if (queue_interval == 0 && !daemon_listen)
   else
     set_process_info("running the queue (single queue run)");
   queue_run(start_queue_run_id, stop_queue_run_id, FALSE);
   else
     set_process_info("running the queue (single queue run)");
   queue_run(start_queue_run_id, stop_queue_run_id, FALSE);
-  exim_exit(EXIT_SUCCESS);
+  exim_exit(EXIT_SUCCESS, US"main");
   }
 
 
   }
 
 
@@ -4697,10 +4743,9 @@ for (i = 0;;)
     /* If user name has not been set by -F, set it from the passwd entry
     unless -f has been used to set the sender address by a trusted user. */
 
     /* If user name has not been set by -F, set it from the passwd entry
     unless -f has been used to set the sender address by a trusted user. */
 
-    if (originator_name == NULL)
+    if (!originator_name)
       {
       {
-      if (sender_address == NULL ||
-           (!trusted_caller && filter_test == FTEST_NONE))
+      if (!sender_address || (!trusted_caller && filter_test == FTEST_NONE))
         {
         uschar *name = US pw->pw_gecos;
         uschar *amp = Ustrchr(name, '&');
         {
         uschar *name = US pw->pw_gecos;
         uschar *amp = Ustrchr(name, '&');
@@ -4710,11 +4755,11 @@ for (i = 0;;)
         replaced by a copy of the login name, and some even specify that
         the first character should be upper cased, so that's what we do. */
 
         replaced by a copy of the login name, and some even specify that
         the first character should be upper cased, so that's what we do. */
 
-        if (amp != NULL)
+        if (amp)
           {
           int loffset;
           string_format(buffer, sizeof(buffer), "%.*s%n%s%s",
           {
           int loffset;
           string_format(buffer, sizeof(buffer), "%.*s%n%s%s",
-            amp - name, name, &loffset, originator_login, amp + 1);
+            (int)(amp - name), name, &loffset, originator_login, amp + 1);
           buffer[loffset] = toupper(buffer[loffset]);
           name = buffer;
           }
           buffer[loffset] = toupper(buffer[loffset]);
           name = buffer;
           }
@@ -4722,7 +4767,7 @@ for (i = 0;;)
         /* If a pattern for matching the gecos field was supplied, apply
         it and then expand the name string. */
 
         /* If a pattern for matching the gecos field was supplied, apply
         it and then expand the name string. */
 
-        if (gecos_pattern != NULL && gecos_name != NULL)
+        if (gecos_pattern && gecos_name)
           {
           const pcre *re;
           re = regex_must_compile(gecos_pattern, FALSE, TRUE); /* Use malloc */
           {
           const pcre *re;
           re = regex_must_compile(gecos_pattern, FALSE, TRUE); /* Use malloc */
@@ -4731,7 +4776,7 @@ for (i = 0;;)
             {
             uschar *new_name = expand_string(gecos_name);
             expand_nmax = -1;
             {
             uschar *new_name = expand_string(gecos_name);
             expand_nmax = -1;
-            if (new_name != NULL)
+            if (new_name)
               {
               DEBUG(D_receive) debug_printf("user name \"%s\" extracted from "
                 "gecos field \"%s\"\n", new_name, name);
               {
               DEBUG(D_receive) debug_printf("user name \"%s\" extracted from "
                 "gecos field \"%s\"\n", new_name, name);
@@ -4828,10 +4873,10 @@ if (test_rewrite_arg >= 0)
   if (test_rewrite_arg >= argc)
     {
     printf("-brw needs an address argument\n");
   if (test_rewrite_arg >= argc)
     {
     printf("-brw needs an address argument\n");
-    exim_exit(EXIT_FAILURE);
+    exim_exit(EXIT_FAILURE, US"main");
     }
   rewrite_test(argv[test_rewrite_arg]);
     }
   rewrite_test(argv[test_rewrite_arg]);
-  exim_exit(EXIT_SUCCESS);
+  exim_exit(EXIT_SUCCESS, US"main");
   }
 
 /* A locally-supplied message is considered to be coming from a local user
   }
 
 /* A locally-supplied message is considered to be coming from a local user
@@ -4946,7 +4991,7 @@ if (verify_address_mode || address_test_mode)
     }
 
   route_tidyup();
     }
 
   route_tidyup();
-  exim_exit(exit_value);
+  exim_exit(exit_value, US"main");
   }
 
 /* Handle expansion checking. Either expand items on the command line, or read
   }
 
 /* Handle expansion checking. Either expand items on the command line, or read
@@ -4976,7 +5021,7 @@ if (expansion_test)
   /* Read a test message from a file. We fudge it up to be on stdin, saving
   stdin itself for later reading of expansion strings. */
 
   /* Read a test message from a file. We fudge it up to be on stdin, saving
   stdin itself for later reading of expansion strings. */
 
-  else if (expansion_test_message != NULL)
+  else if (expansion_test_message)
     {
     int save_stdin = dup(0);
     int fd = Uopen(expansion_test_message, O_RDONLY, 0);
     {
     int save_stdin = dup(0);
     int fd = Uopen(expansion_test_message, O_RDONLY, 0);
@@ -4996,6 +5041,10 @@ if (expansion_test)
     clearerr(stdin);               /* Required by Darwin */
     }
 
     clearerr(stdin);               /* Required by Darwin */
     }
 
+  /* Only admin users may see config-file macros this way */
+
+  if (!admin_user) macros = mlast = NULL;
+
   /* Allow $recipients for this testing */
 
   enable_dollar_recipients = TRUE;
   /* Allow $recipients for this testing */
 
   enable_dollar_recipients = TRUE;
@@ -5003,15 +5052,8 @@ if (expansion_test)
   /* Expand command line items */
 
   if (recipients_arg < argc)
   /* Expand command line items */
 
   if (recipients_arg < argc)
-    {
     while (recipients_arg < argc)
     while (recipients_arg < argc)
-      {
-      uschar *s = argv[recipients_arg++];
-      uschar *ss = expand_string(s);
-      if (ss == NULL) printf ("Failed: %s\n", expand_string_message);
-      else printf("%s\n", CS ss);
-      }
-    }
+      expansion_test_line(argv[recipients_arg++]);
 
   /* Read stdin */
 
 
   /* Read stdin */
 
@@ -5019,25 +5061,18 @@ if (expansion_test)
     {
     char *(*fn_readline)(const char *) = NULL;
     void (*fn_addhist)(const char *) = NULL;
     {
     char *(*fn_readline)(const char *) = NULL;
     void (*fn_addhist)(const char *) = NULL;
+    uschar * s;
 
 
-    #ifdef USE_READLINE
+#ifdef USE_READLINE
     void *dlhandle = set_readline(&fn_readline, &fn_addhist);
     void *dlhandle = set_readline(&fn_readline, &fn_addhist);
-    #endif
+#endif
 
 
-    for (;;)
-      {
-      uschar *ss;
-      uschar *source = get_stdinput(fn_readline, fn_addhist);
-      if (source == NULL) break;
-      ss = expand_string(source);
-      if (ss == NULL)
-        printf ("Failed: %s\n", expand_string_message);
-      else printf("%s\n", CS ss);
-      }
+    while (s = get_stdinput(fn_readline, fn_addhist))
+      expansion_test_line(s);
 
 
-    #ifdef USE_READLINE
-    if (dlhandle != NULL) dlclose(dlhandle);
-    #endif
+#ifdef USE_READLINE
+    if (dlhandle) dlclose(dlhandle);
+#endif
     }
 
   /* The data file will be open after -Mset */
     }
 
   /* The data file will be open after -Mset */
@@ -5048,7 +5083,7 @@ if (expansion_test)
     deliver_datafile = -1;
     }
 
     deliver_datafile = -1;
     }
 
-  exim_exit(EXIT_SUCCESS);
+  exim_exit(EXIT_SUCCESS, US"main: expansion test");
   }
 
 
   }
 
 
@@ -5090,7 +5125,7 @@ if (host_checking)
       verify_get_ident(1413);
     }
 
       verify_get_ident(1413);
     }
 
-  /* In case the given address is a non-canonical IPv6 address, canonicize
+  /* In case the given address is a non-canonical IPv6 address, canonicalize
   it. The code works for both IPv4 and IPv6, as it happens. */
 
   size = host_aton(sender_host_address, x);
   it. The code works for both IPv4 and IPv6, as it happens. */
 
   size = host_aton(sender_host_address, x);
@@ -5124,16 +5159,25 @@ if (host_checking)
 
   if (smtp_start_session())
     {
 
   if (smtp_start_session())
     {
-    reset_point = store_get(0);
-    for (;;)
+    for (reset_point = store_get(0); ; store_reset(reset_point))
       {
       {
-      store_reset(reset_point);
       if (smtp_setup_msg() <= 0) break;
       if (!receive_msg(FALSE)) break;
       if (smtp_setup_msg() <= 0) break;
       if (!receive_msg(FALSE)) break;
+
+      return_path = sender_address = NULL;
+      dnslist_domain = dnslist_matched = NULL;
+#ifndef DISABLE_DKIM
+      dkim_cur_signer = NULL;
+#endif
+      acl_var_m = NULL;
+      deliver_localpart_orig = NULL;
+      deliver_domain_orig = NULL;
+      callout_address = sending_ip_address = NULL;
+      sender_rate = sender_rate_limit = sender_rate_period = NULL;
       }
     smtp_log_no_mail();
     }
       }
     smtp_log_no_mail();
     }
-  exim_exit(EXIT_SUCCESS);
+  exim_exit(EXIT_SUCCESS, US"main");
   }
 
 
   }
 
 
@@ -5252,8 +5296,11 @@ if (smtp_input)
   }
 else
   {
   }
 else
   {
-  if (received_protocol == NULL)
+  int old_pool = store_pool;
+  store_pool = POOL_PERM;
+  if (!received_protocol)
     received_protocol = string_sprintf("local%s", called_as);
     received_protocol = string_sprintf("local%s", called_as);
+  store_pool = old_pool;
   set_process_info("accepting a local non-SMTP message from <%s>",
     sender_address);
   }
   set_process_info("accepting a local non-SMTP message from <%s>",
     sender_address);
   }
@@ -5294,7 +5341,7 @@ if (smtp_input)
   if (!smtp_start_session())
     {
     mac_smtp_fflush();
   if (!smtp_start_session())
     {
     mac_smtp_fflush();
-    exim_exit(EXIT_SUCCESS);
+    exim_exit(EXIT_SUCCESS, US"smtp_start toplevel");
     }
   }
 
     }
   }
 
@@ -5303,15 +5350,13 @@ if (smtp_input)
 else
   {
   thismessage_size_limit = expand_string_integer(message_size_limit, TRUE);
 else
   {
   thismessage_size_limit = expand_string_integer(message_size_limit, TRUE);
-  if (expand_string_message != NULL)
-    {
+  if (expand_string_message)
     if (thismessage_size_limit == -1)
       log_write(0, LOG_MAIN|LOG_PANIC_DIE, "failed to expand "
         "message_size_limit: %s", expand_string_message);
     else
       log_write(0, LOG_MAIN|LOG_PANIC_DIE, "invalid value for "
         "message_size_limit: %s", expand_string_message);
     if (thismessage_size_limit == -1)
       log_write(0, LOG_MAIN|LOG_PANIC_DIE, "failed to expand "
         "message_size_limit: %s", expand_string_message);
     else
       log_write(0, LOG_MAIN|LOG_PANIC_DIE, "invalid value for "
         "message_size_limit: %s", expand_string_message);
-    }
   }
 
 /* Loop for several messages when reading SMTP input. If we fork any child
   }
 
 /* Loop for several messages when reading SMTP input. If we fork any child
@@ -5339,7 +5384,7 @@ February 2003: That's *still* not the end of the story. There are now versions
 of Linux (where SIG_IGN does work) that are picky. If, having set SIG_IGN, a
 process then calls waitpid(), a grumble is written to the system log, because
 this is logically inconsistent. In other words, it doesn't like the paranoia.
 of Linux (where SIG_IGN does work) that are picky. If, having set SIG_IGN, a
 process then calls waitpid(), a grumble is written to the system log, because
 this is logically inconsistent. In other words, it doesn't like the paranoia.
-As a consequenc of this, the waitpid() below is now excluded if we are sure
+As a consequence of this, the waitpid() below is now excluded if we are sure
 that SIG_IGN works. */
 
 if (!synchronous_delivery)
 that SIG_IGN works. */
 
 if (!synchronous_delivery)
@@ -5367,7 +5412,6 @@ collapsed). */
 
 while (more)
   {
 
 while (more)
   {
-  store_reset(reset_point);
   message_id[0] = 0;
 
   /* Handle the SMTP case; call smtp_setup_mst() to deal with the initial SMTP
   message_id[0] = 0;
 
   /* Handle the SMTP case; call smtp_setup_mst() to deal with the initial SMTP
@@ -5409,15 +5453,17 @@ while (more)
       more = receive_msg(extract_recipients);
       if (message_id[0] == 0)
         {
       more = receive_msg(extract_recipients);
       if (message_id[0] == 0)
         {
-        if (more) continue;
+       cancel_cutthrough_connection(TRUE, US"receive dropped");
+        if (more) goto moreloop;
         smtp_log_no_mail();               /* Log no mail if configured */
         smtp_log_no_mail();               /* Log no mail if configured */
-        exim_exit(EXIT_FAILURE);
+        exim_exit(EXIT_FAILURE, US"receive toplevel");
         }
       }
     else
       {
         }
       }
     else
       {
+      cancel_cutthrough_connection(TRUE, US"message setup dropped");
       smtp_log_no_mail();               /* Log no mail if configured */
       smtp_log_no_mail();               /* Log no mail if configured */
-      exim_exit((rc == 0)? EXIT_SUCCESS : EXIT_FAILURE);
+      exim_exit(rc ? EXIT_FAILURE : EXIT_SUCCESS, US"msg setup toplevel");
       }
     }
 
       }
     }
 
@@ -5468,14 +5514,12 @@ while (more)
           if (error_handling == ERRORS_STDERR)
             {
             fprintf(stderr, "exim: too many recipients\n");
           if (error_handling == ERRORS_STDERR)
             {
             fprintf(stderr, "exim: too many recipients\n");
-            exim_exit(EXIT_FAILURE);
+            exim_exit(EXIT_FAILURE, US"main");
             }
           else
             }
           else
-            {
             return
               moan_to_sender(ERRMESS_TOOMANYRECIP, NULL, NULL, stdin, TRUE)?
                 errors_sender_rc : EXIT_FAILURE;
             return
               moan_to_sender(ERRMESS_TOOMANYRECIP, NULL, NULL, stdin, TRUE)?
                 errors_sender_rc : EXIT_FAILURE;
-            }
 
 #ifdef SUPPORT_I18N
        {
 
 #ifdef SUPPORT_I18N
        {
@@ -5504,7 +5548,7 @@ while (more)
             {
             fprintf(stderr, "exim: bad recipient address \"%s\": %s\n",
               string_printing(list[i]), errmess);
             {
             fprintf(stderr, "exim: bad recipient address \"%s\": %s\n",
               string_printing(list[i]), errmess);
-            exim_exit(EXIT_FAILURE);
+            exim_exit(EXIT_FAILURE, US"main");
             }
           else
             {
             }
           else
             {
@@ -5543,7 +5587,7 @@ while (more)
     ignored; rejecting here would just add complication, and it can just as
     well be done later. Allow $recipients to be visible in the ACL. */
 
     ignored; rejecting here would just add complication, and it can just as
     well be done later. Allow $recipients to be visible in the ACL. */
 
-    if (acl_not_smtp_start != NULL)
+    if (acl_not_smtp_start)
       {
       uschar *user_msg, *log_msg;
       enable_dollar_recipients = TRUE;
       {
       uschar *user_msg, *log_msg;
       enable_dollar_recipients = TRUE;
@@ -5552,6 +5596,20 @@ while (more)
       enable_dollar_recipients = FALSE;
       }
 
       enable_dollar_recipients = FALSE;
       }
 
+    /* Pause for a while waiting for input.  If none received in that time,
+    close the logfile, if we had one open; then if we wait for a long-running
+    datasource (months, in one use-case) log rotation will not leave us holding
+    the file copy. */
+
+    if (!receive_timeout)
+      {
+      struct timeval t = { .tv_sec = 30*60, .tv_usec = 0 };    /* 30 minutes */
+      fd_set r;
+
+      FD_ZERO(&r); FD_SET(0, &r);
+      if (select(1, &r, NULL, NULL, &t) == 0) mainlog_close();
+      }
+
     /* Read the data for the message. If filter_test is not FTEST_NONE, this
     will just read the headers for the message, and not write anything onto the
     spool. */
     /* Read the data for the message. If filter_test is not FTEST_NONE, this
     will just read the headers for the message, and not write anything onto the
     spool. */
@@ -5563,7 +5621,7 @@ while (more)
     for real; when reading the headers of a message for filter testing,
     it is TRUE if the headers were terminated by '.' and FALSE otherwise. */
 
     for real; when reading the headers of a message for filter testing,
     it is TRUE if the headers were terminated by '.' and FALSE otherwise. */
 
-    if (message_id[0] == 0) exim_exit(EXIT_FAILURE);
+    if (message_id[0] == 0) exim_exit(EXIT_FAILURE, US"main");
     }  /* Non-SMTP message reception */
 
   /* If this is a filter testing run, there are headers in store, but
     }  /* Non-SMTP message reception */
 
   /* If this is a filter testing run, there are headers in store, but
@@ -5608,7 +5666,7 @@ while (more)
     if (chdir("/"))   /* Get away from wherever the user is running this from */
       {
       DEBUG(D_receive) debug_printf("chdir(\"/\") failed\n");
     if (chdir("/"))   /* Get away from wherever the user is running this from */
       {
       DEBUG(D_receive) debug_printf("chdir(\"/\") failed\n");
-      exim_exit(EXIT_FAILURE);
+      exim_exit(EXIT_FAILURE, US"main");
       }
 
     /* Now we run either a system filter test, or a user filter test, or both.
       }
 
     /* Now we run either a system filter test, or a user filter test, or both.
@@ -5617,20 +5675,16 @@ while (more)
     explicitly. */
 
     if ((filter_test & FTEST_SYSTEM) != 0)
     explicitly. */
 
     if ((filter_test & FTEST_SYSTEM) != 0)
-      {
       if (!filter_runtest(filter_sfd, filter_test_sfile, TRUE, more))
       if (!filter_runtest(filter_sfd, filter_test_sfile, TRUE, more))
-        exim_exit(EXIT_FAILURE);
-      }
+        exim_exit(EXIT_FAILURE, US"main");
 
     memcpy(filter_sn, filter_n, sizeof(filter_sn));
 
     if ((filter_test & FTEST_USER) != 0)
 
     memcpy(filter_sn, filter_n, sizeof(filter_sn));
 
     if ((filter_test & FTEST_USER) != 0)
-      {
       if (!filter_runtest(filter_ufd, filter_test_ufile, FALSE, more))
       if (!filter_runtest(filter_ufd, filter_test_ufile, FALSE, more))
-        exim_exit(EXIT_FAILURE);
-      }
+        exim_exit(EXIT_FAILURE, US"main");
 
 
-    exim_exit(EXIT_SUCCESS);
+    exim_exit(EXIT_SUCCESS, US"main");
     }
 
   /* Else act on the result of message reception. We should not get here unless
     }
 
   /* Else act on the result of message reception. We should not get here unless
@@ -5677,21 +5731,28 @@ while (more)
   not if queue_only is set (case 0). Case 1 doesn't happen here (too many
   connections). */
 
   not if queue_only is set (case 0). Case 1 doesn't happen here (too many
   connections). */
 
-  if (local_queue_only) switch(queue_only_reason)
+  if (local_queue_only)
     {
     {
-    case 2:
-    log_write(L_delay_delivery,
-              LOG_MAIN, "no immediate delivery: more than %d messages "
-      "received in one connection", smtp_accept_queue_per_connection);
-    break;
+    cancel_cutthrough_connection(TRUE, US"no delivery; queueing");
+    switch(queue_only_reason)
+      {
+      case 2:
+       log_write(L_delay_delivery,
+               LOG_MAIN, "no immediate delivery: more than %d messages "
+         "received in one connection", smtp_accept_queue_per_connection);
+       break;
 
 
-    case 3:
-    log_write(L_delay_delivery,
-              LOG_MAIN, "no immediate delivery: load average %.2f",
-              (double)load_average/1000.0);
-    break;
+      case 3:
+       log_write(L_delay_delivery,
+               LOG_MAIN, "no immediate delivery: load average %.2f",
+               (double)load_average/1000.0);
+      break;
+      }
     }
 
     }
 
+  else if (queue_only_policy || deliver_freeze)
+    cancel_cutthrough_connection(TRUE, US"no delivery; queueing");
+
   /* Else do the delivery unless the ACL or local_scan() called for queue only
   or froze the message. Always deliver in a separate process. A fork failure is
   not a disaster, as the delivery will eventually happen on a subsequent queue
   /* Else do the delivery unless the ACL or local_scan() called for queue only
   or froze the message. Always deliver in a separate process. A fork failure is
   not a disaster, as the delivery will eventually happen on a subsequent queue
@@ -5700,7 +5761,7 @@ while (more)
   thereby defer the delivery if it tries to use (for example) a cached ldap
   connection that the parent has called unbind on. */
 
   thereby defer the delivery if it tries to use (for example) a cached ldap
   connection that the parent has called unbind on. */
 
-  else if (!queue_only_policy && !deliver_freeze)
+  else
     {
     pid_t pid;
     search_tidyup();
     {
     pid_t pid;
     search_tidyup();
@@ -5716,8 +5777,7 @@ while (more)
 
       if (geteuid() != root_uid && !deliver_drop_privilege && !unprivileged)
         {
 
       if (geteuid() != root_uid && !deliver_drop_privilege && !unprivileged)
         {
-        (void)child_exec_exim(CEE_EXEC_EXIT, FALSE, NULL, FALSE,
-               2, US"-Mc", message_id);
+       delivery_re_exec(CEE_EXEC_EXIT);
         /* Control does not return here. */
         }
 
         /* Control does not return here. */
         }
 
@@ -5731,22 +5791,27 @@ while (more)
 
     if (pid < 0)
       {
 
     if (pid < 0)
       {
+      cancel_cutthrough_connection(TRUE, US"delivery fork failed");
       log_write(0, LOG_MAIN|LOG_PANIC, "failed to fork automatic delivery "
         "process: %s", strerror(errno));
       }
       log_write(0, LOG_MAIN|LOG_PANIC, "failed to fork automatic delivery "
         "process: %s", strerror(errno));
       }
+    else
+      {
+      release_cutthrough_connection(US"msg passed for delivery");
 
 
-    /* In the parent, wait if synchronous delivery is required. This will
-    always be the case in MUA wrapper mode. */
+      /* In the parent, wait if synchronous delivery is required. This will
+      always be the case in MUA wrapper mode. */
 
 
-    else if (synchronous_delivery)
-      {
-      int status;
-      while (wait(&status) != pid);
-      if ((status & 0x00ff) != 0)
-        log_write(0, LOG_MAIN|LOG_PANIC,
-          "process %d crashed with signal %d while delivering %s",
-          (int)pid, status & 0x00ff, message_id);
-      if (mua_wrapper && (status & 0xffff) != 0) exim_exit(EXIT_FAILURE);
+      if (synchronous_delivery)
+       {
+       int status;
+       while (wait(&status) != pid);
+       if ((status & 0x00ff) != 0)
+         log_write(0, LOG_MAIN|LOG_PANIC,
+           "process %d crashed with signal %d while delivering %s",
+           (int)pid, status & 0x00ff, message_id);
+       if (mua_wrapper && (status & 0xffff) != 0) exim_exit(EXIT_FAILURE, US"main");
+       }
       }
     }
 
       }
     }
 
@@ -5758,10 +5823,28 @@ while (more)
   #ifndef SIG_IGN_WORKS
   while (waitpid(-1, NULL, WNOHANG) > 0);
   #endif
   #ifndef SIG_IGN_WORKS
   while (waitpid(-1, NULL, WNOHANG) > 0);
   #endif
+
+moreloop:
+  return_path = sender_address = NULL;
+  authenticated_sender = NULL;
+  deliver_localpart_orig = NULL;
+  deliver_domain_orig = NULL;
+  deliver_host = deliver_host_address = NULL;
+  dnslist_domain = dnslist_matched = NULL;
+#ifdef WITH_CONTENT_SCAN
+  malware_name = NULL;
+#endif
+  callout_address = NULL;
+  sending_ip_address = NULL;
+  acl_var_m = NULL;
+  { int i; for(i=0; i<REGEX_VARS; i++) regex_vars[i] = NULL; }
+
+  store_reset(reset_point);
   }
 
   }
 
-exim_exit(EXIT_SUCCESS);   /* Never returns */
+exim_exit(EXIT_SUCCESS, US"main");   /* Never returns */
 return 0;                  /* To stop compiler warning */
 }
 
 return 0;                  /* To stop compiler warning */
 }
 
+
 /* End of exim.c */
 /* End of exim.c */