Change acl expansion-condition syntax to "acl {{name} {arg1}{arg2}...}"
[users/heiko/exim.git] / src / src / exim.c
index dce42f0c42647d7537381c7a4dbf1c9bd941ff07..a59cfea9a9b44becd0d87008914dec3b8d1edadc 100644 (file)
@@ -1,10 +1,8 @@
-/* $Cambridge: exim/src/src/exim.c,v 1.71 2010/06/07 00:12:42 pdp Exp $ */
-
 /*************************************************
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
 /*************************************************
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
-/* Copyright (c) University of Cambridge 1995 - 2009 */
+/* Copyright (c) University of Cambridge 1995 - 2012 */
 /* See the file NOTICE for conditions of use and distribution. */
 
 
 /* See the file NOTICE for conditions of use and distribution. */
 
 
@@ -14,6 +12,8 @@ Also a few functions that don't naturally fit elsewhere. */
 
 #include "exim.h"
 
 
 #include "exim.h"
 
+extern void init_lookup_list(void);
+
 
 
 /*************************************************
 
 
 /*************************************************
@@ -52,6 +52,16 @@ store_free(block);
 
 
 
 
 
 
+/*************************************************
+*         Enums for cmdline interface            *
+*************************************************/
+
+enum commandline_info { CMDINFO_NONE=0,
+  CMDINFO_HELP, CMDINFO_SIEVE, CMDINFO_DSCP };
+
+
+
+
 /*************************************************
 *  Compile regular expression and panic on fail  *
 *************************************************/
 /*************************************************
 *  Compile regular expression and panic on fail  *
 *************************************************/
@@ -138,6 +148,38 @@ return yield;
 
 
 
 
 
 
+/*************************************************
+*            Set up processing details           *
+*************************************************/
+
+/* Save a text string for dumping when SIGUSR1 is received.
+Do checks for overruns.
+
+Arguments: format and arguments, as for printf()
+Returns:   nothing
+*/
+
+void
+set_process_info(const char *format, ...)
+{
+int len;
+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 ****");
+len = Ustrlen(process_info);
+process_info[len+0] = '\n';
+process_info[len+1] = '\0';
+process_info_len = len + 1;
+DEBUG(D_process_info) debug_printf("set_process_info: %s", process_info);
+va_end(ap);
+}
+
+
+
+
 /*************************************************
 *             Handler for SIGUSR1                *
 *************************************************/
 /*************************************************
 *             Handler for SIGUSR1                *
 *************************************************/
@@ -147,6 +189,8 @@ what it is currently doing. It will only be used if the OS is capable of
 setting up a handler that causes automatic restarting of any system call
 that is in progress at the time.
 
 setting up a handler that causes automatic restarting of any system call
 that is in progress at the time.
 
+This function takes care to be signal-safe.
+
 Argument: the signal number (SIGUSR1)
 Returns:  nothing
 */
 Argument: the signal number (SIGUSR1)
 Returns:  nothing
 */
@@ -154,10 +198,32 @@ Returns:  nothing
 static void
 usr1_handler(int sig)
 {
 static void
 usr1_handler(int sig)
 {
-sig = sig;      /* Keep picky compilers happy */
-log_write(0, LOG_PROCESS, "%s", process_info);
-log_close_all();
-os_restarting_signal(SIGUSR1, usr1_handler);
+int fd;
+
+os_restarting_signal(sig, usr1_handler);
+
+fd = Uopen(process_log_path, O_APPEND|O_WRONLY, LOG_MODE);
+if (fd < 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
+  root, do the creation in an exim:exim subprocess. */
+
+  int euid = geteuid();
+  if (euid == exim_uid)
+    fd = Uopen(process_log_path, O_CREAT|O_APPEND|O_WRONLY, LOG_MODE);
+  else if (euid == root_uid)
+    fd = log_create_as_exim(process_log_path);
+  }
+
+/* If we are neither exim nor root, or if we failed to create the log file,
+give up. There is not much useful we can do with errors, since we don't want
+to disrupt whatever is going on outside the signal handler. */
+
+if (fd < 0) return;
+
+(void)write(fd, process_info, process_info_len);
+(void)close(fd);
 }
 
 
 }
 
 
@@ -346,35 +412,6 @@ if (exim_tvcmp(&now_tv, then_tv) <= 0)
 
 
 
 
 
 
-/*************************************************
-*            Set up processing details           *
-*************************************************/
-
-/* Save a text string for dumping when SIGUSR1 is received.
-Do checks for overruns.
-
-Arguments: format and arguments, as for printf()
-Returns:   nothing
-*/
-
-void
-set_process_info(char *format, ...)
-{
-int len;
-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, format, ap))
-  Ustrcpy(process_info + len, "**** string overflowed buffer ****");
-DEBUG(D_process_info) debug_printf("set_process_info: %s\n", process_info);
-va_end(ap);
-}
-
-
-
-
-
 /*************************************************
 *   Call fopen() with umask 777 and adjust mode  *
 *************************************************/
 /*************************************************
 *   Call fopen() with umask 777 and adjust mode  *
 *************************************************/
@@ -395,7 +432,7 @@ Returns:          the fopened FILE or NULL
 */
 
 FILE *
 */
 
 FILE *
-modefopen(uschar *filename, char *options, mode_t mode)
+modefopen(const uschar *filename, const char *options, mode_t mode)
 {
 mode_t saved_umask = umask(0777);
 FILE *f = Ufopen(filename, options);
 {
 mode_t saved_umask = umask(0777);
 FILE *f = Ufopen(filename, options);
@@ -489,7 +526,7 @@ close_unwanted(void)
 if (smtp_input)
   {
   #ifdef SUPPORT_TLS
 if (smtp_input)
   {
   #ifdef SUPPORT_TLS
-  tls_close(FALSE);      /* Shut down the TLS library */
+  tls_close(FALSE, FALSE);      /* Shut down the TLS library */
   #endif
   (void)close(fileno(smtp_in));
   (void)close(fileno(smtp_out));
   #endif
   (void)close(fileno(smtp_in));
   (void)close(fileno(smtp_out));
@@ -568,17 +605,20 @@ if (euid == root_uid || euid != uid || egid != gid || igflag)
 
 DEBUG(D_uid)
   {
 
 DEBUG(D_uid)
   {
-  int group_count;
+  int group_count, save_errno;
   gid_t group_list[NGROUPS_MAX];
   debug_printf("changed uid/gid: %s\n  uid=%ld gid=%ld pid=%ld\n", msg,
     (long int)geteuid(), (long int)getegid(), (long int)getpid());
   group_count = getgroups(NGROUPS_MAX, group_list);
   gid_t group_list[NGROUPS_MAX];
   debug_printf("changed uid/gid: %s\n  uid=%ld gid=%ld pid=%ld\n", msg,
     (long int)geteuid(), (long int)getegid(), (long int)getpid());
   group_count = getgroups(NGROUPS_MAX, group_list);
+  save_errno = errno;
   debug_printf("  auxiliary group list:");
   if (group_count > 0)
     {
     int i;
     for (i = 0; i < group_count; i++) debug_printf(" %d", (int)group_list[i]);
     }
   debug_printf("  auxiliary group list:");
   if (group_count > 0)
     {
     int i;
     for (i = 0; i < group_count; i++) debug_printf(" %d", (int)group_list[i]);
     }
+  else if (group_count < 0)
+    debug_printf(" <error: %s>", strerror(save_errno));
   else debug_printf(" <none>");
   debug_printf("\n");
   }
   else debug_printf(" <none>");
   debug_printf("\n");
   }
@@ -695,6 +735,8 @@ Returns:    nothing
 static void
 show_whats_supported(FILE *f)
 {
 static void
 show_whats_supported(FILE *f)
 {
+  auth_info *authi;
+
 #ifdef DB_VERSION_STRING
 fprintf(f, "Berkeley DB: %s\n", DB_VERSION_STRING);
 #elif defined(BTREEVERSION) && defined(HASHVERSION)
 #ifdef DB_VERSION_STRING
 fprintf(f, "Berkeley DB: %s\n", DB_VERSION_STRING);
 #elif defined(BTREEVERSION) && defined(HASHVERSION)
@@ -776,53 +818,53 @@ fprintf(f, "Support for:");
 #endif
 fprintf(f, "\n");
 
 #endif
 fprintf(f, "\n");
 
-fprintf(f, "Lookups:");
-#ifdef LOOKUP_LSEARCH
+fprintf(f, "Lookups (built-in):");
+#if defined(LOOKUP_LSEARCH) && LOOKUP_LSEARCH!=2
   fprintf(f, " lsearch wildlsearch nwildlsearch iplsearch");
 #endif
   fprintf(f, " lsearch wildlsearch nwildlsearch iplsearch");
 #endif
-#ifdef LOOKUP_CDB
+#if defined(LOOKUP_CDB) && LOOKUP_CDB!=2
   fprintf(f, " cdb");
 #endif
   fprintf(f, " cdb");
 #endif
-#ifdef LOOKUP_DBM
-  fprintf(f, " dbm dbmnz");
+#if defined(LOOKUP_DBM) && LOOKUP_DBM!=2
+  fprintf(f, " dbm dbmjz dbmnz");
 #endif
 #endif
-#ifdef LOOKUP_DNSDB
+#if defined(LOOKUP_DNSDB) && LOOKUP_DNSDB!=2
   fprintf(f, " dnsdb");
 #endif
   fprintf(f, " dnsdb");
 #endif
-#ifdef LOOKUP_DSEARCH
+#if defined(LOOKUP_DSEARCH) && LOOKUP_DSEARCH!=2
   fprintf(f, " dsearch");
 #endif
   fprintf(f, " dsearch");
 #endif
-#ifdef LOOKUP_IBASE
+#if defined(LOOKUP_IBASE) && LOOKUP_IBASE!=2
   fprintf(f, " ibase");
 #endif
   fprintf(f, " ibase");
 #endif
-#ifdef LOOKUP_LDAP
+#if defined(LOOKUP_LDAP) && LOOKUP_LDAP!=2
   fprintf(f, " ldap ldapdn ldapm");
 #endif
   fprintf(f, " ldap ldapdn ldapm");
 #endif
-#ifdef LOOKUP_MYSQL
+#if defined(LOOKUP_MYSQL) && LOOKUP_MYSQL!=2
   fprintf(f, " mysql");
 #endif
   fprintf(f, " mysql");
 #endif
-#ifdef LOOKUP_NIS
+#if defined(LOOKUP_NIS) && LOOKUP_NIS!=2
   fprintf(f, " nis nis0");
 #endif
   fprintf(f, " nis nis0");
 #endif
-#ifdef LOOKUP_NISPLUS
+#if defined(LOOKUP_NISPLUS) && LOOKUP_NISPLUS!=2
   fprintf(f, " nisplus");
 #endif
   fprintf(f, " nisplus");
 #endif
-#ifdef LOOKUP_ORACLE
+#if defined(LOOKUP_ORACLE) && LOOKUP_ORACLE!=2
   fprintf(f, " oracle");
 #endif
   fprintf(f, " oracle");
 #endif
-#ifdef LOOKUP_PASSWD
+#if defined(LOOKUP_PASSWD) && LOOKUP_PASSWD!=2
   fprintf(f, " passwd");
 #endif
   fprintf(f, " passwd");
 #endif
-#ifdef LOOKUP_PGSQL
+#if defined(LOOKUP_PGSQL) && LOOKUP_PGSQL!=2
   fprintf(f, " pgsql");
 #endif
   fprintf(f, " pgsql");
 #endif
-#ifdef LOOKUP_SQLITE
+#if defined(LOOKUP_SQLITE) && LOOKUP_SQLITE!=2
   fprintf(f, " sqlite");
 #endif
   fprintf(f, " sqlite");
 #endif
-#ifdef LOOKUP_TESTDB
+#if defined(LOOKUP_TESTDB) && LOOKUP_TESTDB!=2
   fprintf(f, " testdb");
 #endif
   fprintf(f, " testdb");
 #endif
-#ifdef LOOKUP_WHOSON
+#if defined(LOOKUP_WHOSON) && LOOKUP_WHOSON!=2
   fprintf(f, " whoson");
 #endif
 fprintf(f, "\n");
   fprintf(f, " whoson");
 #endif
 fprintf(f, "\n");
@@ -837,6 +879,12 @@ fprintf(f, "Authenticators:");
 #ifdef AUTH_DOVECOT
   fprintf(f, " dovecot");
 #endif
 #ifdef AUTH_DOVECOT
   fprintf(f, " dovecot");
 #endif
+#ifdef AUTH_GSASL
+  fprintf(f, " gsasl");
+#endif
+#ifdef AUTH_HEIMDAL_GSSAPI
+  fprintf(f, " heimdal_gssapi");
+#endif
 #ifdef AUTH_PLAINTEXT
   fprintf(f, " plaintext");
 #endif
 #ifdef AUTH_PLAINTEXT
   fprintf(f, " plaintext");
 #endif
@@ -907,16 +955,108 @@ if (fixed_never_users[0] > 0)
 
 fprintf(f, "Size of off_t: " SIZE_T_FMT "\n", sizeof(off_t));
 
 
 fprintf(f, "Size of off_t: " SIZE_T_FMT "\n", sizeof(off_t));
 
-/* This runtime check is to help diagnose library linkage mismatches which
-result in segfaults and the like; as such, it's left until the end,
-just in case.  There will still be a "Configuration file is" line still to
-come. */
+/* Everything else is details which are only worth reporting when debugging.
+Perhaps the tls_version_report should move into this too. */
+DEBUG(D_any) do {
+
+  int i;
+
+/* clang defines __GNUC__ (at least, for me) so test for it first */
+#if defined(__clang__)
+  fprintf(f, "Compiler: CLang [%s]\n", __clang_version__);
+#elif defined(__GNUC__)
+  fprintf(f, "Compiler: GCC [%s]\n",
+# ifdef __VERSION__
+      __VERSION__
+# else
+      "? unknown version ?"
+# endif
+      );
+#else
+  fprintf(f, "Compiler: <unknown>\n");
+#endif
+
 #ifdef SUPPORT_TLS
 #ifdef SUPPORT_TLS
-tls_version_report(f);
+  tls_version_report(f);
 #endif
 #endif
+
+  for (authi = auths_available; *authi->driver_name != '\0'; ++authi) {
+    if (authi->version_report) {
+      (*authi->version_report)(f);
+    }
+  }
+
+  /* PCRE_PRERELEASE is either defined and empty or a bare sequence of
+  characters; unless it's an ancient version of PCRE in which case it
+  is not defined. */
+#ifndef PCRE_PRERELEASE
+#define PCRE_PRERELEASE
+#endif
+#define QUOTE(X) #X
+#define EXPAND_AND_QUOTE(X) QUOTE(X)
+  fprintf(f, "Library version: PCRE: Compile: %d.%d%s\n"
+             "                       Runtime: %s\n",
+          PCRE_MAJOR, PCRE_MINOR,
+          EXPAND_AND_QUOTE(PCRE_PRERELEASE) "",
+          pcre_version());
+#undef QUOTE
+#undef EXPAND_AND_QUOTE
+
+  init_lookup_list();
+  for (i = 0; i < lookup_list_count; i++)
+    {
+    if (lookup_list[i]->version_report)
+      lookup_list[i]->version_report(f);
+    }
+
+#ifdef WHITELIST_D_MACROS
+  fprintf(f, "WHITELIST_D_MACROS: \"%s\"\n", WHITELIST_D_MACROS);
+#else
+  fprintf(f, "WHITELIST_D_MACROS unset\n");
+#endif
+#ifdef TRUSTED_CONFIG_LIST
+  fprintf(f, "TRUSTED_CONFIG_LIST: \"%s\"\n", TRUSTED_CONFIG_LIST);
+#else
+  fprintf(f, "TRUSTED_CONFIG_LIST unset\n");
+#endif
+
+} while (0);
 }
 
 
 }
 
 
+/*************************************************
+*     Show auxiliary information about Exim      *
+*************************************************/
+
+static void
+show_exim_information(enum commandline_info request, FILE *stream)
+{
+const uschar **pp;
+
+switch(request)
+  {
+  case CMDINFO_NONE:
+    fprintf(stream, "Oops, something went wrong.\n");
+    return;
+  case CMDINFO_HELP:
+    fprintf(stream,
+"The -bI: flag takes a string indicating which information to provide.\n"
+"If the string is not recognised, you'll get this help (on stderr).\n"
+"\n"
+"  exim -bI:help    this information\n"
+"  exim -bI:dscp    dscp value keywords known\n"
+"  exim -bI:sieve   list of supported sieve extensions, one per line.\n"
+);
+    return;
+  case CMDINFO_SIEVE:
+    for (pp = exim_sieve_extension_list; *pp; ++pp)
+      fprintf(stream, "%s\n", *pp);
+    return;
+  case CMDINFO_DSCP:
+    dscp_list_to_stream(stream);
+    return;
+  }
+}
 
 
 /*************************************************
 
 
 /*************************************************
@@ -990,19 +1130,23 @@ Returns:            the dlopen handle or NULL on failure
 */
 
 static void *
 */
 
 static void *
-set_readline(char * (**fn_readline_ptr)(char *),
-             char * (**fn_addhist_ptr)(char *))
+set_readline(char * (**fn_readline_ptr)(const char *),
+             void   (**fn_addhist_ptr)(const char *))
 {
 void *dlhandle;
 {
 void *dlhandle;
-void *dlhandle_curses = dlopen("libcurses.so", RTLD_GLOBAL|RTLD_LAZY);
+void *dlhandle_curses = dlopen("libcurses." DYNLIB_FN_EXT, RTLD_GLOBAL|RTLD_LAZY);
 
 
-dlhandle = dlopen("libreadline.so", RTLD_GLOBAL|RTLD_NOW);
+dlhandle = dlopen("libreadline." DYNLIB_FN_EXT, RTLD_GLOBAL|RTLD_NOW);
 if (dlhandle_curses != NULL) dlclose(dlhandle_curses);
 
 if (dlhandle != NULL)
   {
 if (dlhandle_curses != NULL) dlclose(dlhandle_curses);
 
 if (dlhandle != NULL)
   {
-  *fn_readline_ptr = (char *(*)(char*))dlsym(dlhandle, "readline");
-  *fn_addhist_ptr = (char *(*)(char*))dlsym(dlhandle, "add_history");
+  /* Checked manual pages; at least in GNU Readline 6.1, the prototypes are:
+   *   char * readline (const char *prompt);
+   *   void add_history (const char *string);
+   */
+  *fn_readline_ptr = (char *(*)(const char*))dlsym(dlhandle, "readline");
+  *fn_addhist_ptr = (void(*)(const char*))dlsym(dlhandle, "add_history");
   }
 else
   {
   }
 else
   {
@@ -1032,7 +1176,7 @@ Returns:        pointer to dynamic memory, or NULL at end of file
 */
 
 static uschar *
 */
 
 static uschar *
-get_stdinput(char *(*fn_readline)(char *), char *(*fn_addhist)(char *))
+get_stdinput(char *(*fn_readline)(const char *), void(*fn_addhist)(const char *))
 {
 int i;
 int size = 0;
 {
 int i;
 int size = 0;
@@ -1236,7 +1380,7 @@ for (m = macros; m != NULL; m = m->next)
     return FALSE;
     }
   }
     return FALSE;
     }
   }
-debug_printf("macros_trusted overriden to true by whitelisting\n");
+DEBUG(D_any) debug_printf("macros_trusted overridden to true by whitelisting\n");
 return TRUE;
 #endif
 }
 return TRUE;
 #endif
 }
@@ -1271,7 +1415,7 @@ int  arg_error_handling = error_handling;
 int  filter_sfd = -1;
 int  filter_ufd = -1;
 int  group_count;
 int  filter_sfd = -1;
 int  filter_ufd = -1;
 int  group_count;
-int  i;
+int  i, rv;
 int  list_queue_option = 0;
 int  msg_action = 0;
 int  msg_action_arg = -1;
 int  list_queue_option = 0;
 int  msg_action = 0;
 int  msg_action_arg = -1;
@@ -1290,6 +1434,8 @@ BOOL checking = FALSE;
 BOOL count_queue = FALSE;
 BOOL expansion_test = FALSE;
 BOOL extract_recipients = FALSE;
 BOOL count_queue = FALSE;
 BOOL expansion_test = FALSE;
 BOOL extract_recipients = FALSE;
+BOOL flag_G = FALSE;
+BOOL flag_n = FALSE;
 BOOL forced_delivery = FALSE;
 BOOL f_end_dot = FALSE;
 BOOL deliver_give_up = FALSE;
 BOOL forced_delivery = FALSE;
 BOOL f_end_dot = FALSE;
 BOOL deliver_give_up = FALSE;
@@ -1310,6 +1456,7 @@ BOOL verify_as_sender = FALSE;
 BOOL version_printed = FALSE;
 uschar *alias_arg = NULL;
 uschar *called_as = US"";
 BOOL version_printed = FALSE;
 uschar *alias_arg = NULL;
 uschar *called_as = US"";
+uschar *cmdline_syslog_name = NULL;
 uschar *start_queue_run_id = NULL;
 uschar *stop_queue_run_id = NULL;
 uschar *expansion_test_message = NULL;
 uschar *start_queue_run_id = NULL;
 uschar *stop_queue_run_id = NULL;
 uschar *expansion_test_message = NULL;
@@ -1320,6 +1467,7 @@ uschar *ftest_suffix = NULL;
 uschar *malware_test_file = NULL;
 uschar *real_sender_address;
 uschar *originator_home = US"/";
 uschar *malware_test_file = NULL;
 uschar *real_sender_address;
 uschar *originator_home = US"/";
+size_t sz;
 void *reset_point;
 
 struct passwd *pw;
 void *reset_point;
 
 struct passwd *pw;
@@ -1328,6 +1476,10 @@ pid_t passed_qr_pid = (pid_t)0;
 int passed_qr_pipe = -1;
 gid_t group_list[NGROUPS_MAX];
 
 int passed_qr_pipe = -1;
 gid_t group_list[NGROUPS_MAX];
 
+/* For the -bI: flag */
+enum commandline_info info_flag = CMDINFO_NONE;
+BOOL info_stdout = FALSE;
+
 /* Possible options for -R and -S */
 
 static uschar *rsopts[] = { US"f", US"ff", US"r", US"rf", US"rff" };
 /* Possible options for -R and -S */
 
 static uschar *rsopts[] = { US"f", US"ff", US"r", US"rf", US"rff" };
@@ -1351,7 +1503,19 @@ if (route_finduser(US EXIM_USERNAME, &pw, &exim_uid))
       EXIM_USERNAME);
     exit(EXIT_FAILURE);
     }
       EXIM_USERNAME);
     exit(EXIT_FAILURE);
     }
-  exim_gid = pw->pw_gid;
+  /* If ref:name uses a number as the name, route_finduser() returns
+  TRUE with exim_uid set and pw coerced to NULL. */
+  if (pw)
+    exim_gid = pw->pw_gid;
+#ifndef EXIM_GROUPNAME
+  else
+    {
+    fprintf(stderr,
+        "exim: ref:name should specify a usercode, not a group.\n"
+        "exim: can't let you get away with it unless you also specify a group.\n");
+    exit(EXIT_FAILURE);
+    }
+#endif
   }
 else
   {
   }
 else
   {
@@ -1610,8 +1774,20 @@ real_gid = getgid();
 
 if (real_uid == root_uid)
   {
 
 if (real_uid == root_uid)
   {
-  setgid(real_gid);
-  setuid(real_uid);
+  rv = setgid(real_gid);
+  if (rv)
+    {
+    fprintf(stderr, "exim: setgid(%ld) failed: %s\n",
+        (long int)real_gid, strerror(errno));
+    exit(EXIT_FAILURE);
+    }
+  rv = setuid(real_uid);
+  if (rv)
+    {
+    fprintf(stderr, "exim: setuid(%ld) failed: %s\n",
+        (long int)real_uid, strerror(errno));
+    exit(EXIT_FAILURE);
+    }
   }
 
 /* If neither the original real uid nor the original euid was root, Exim is
   }
 
 /* If neither the original real uid nor the original euid was root, Exim is
@@ -1702,6 +1878,26 @@ for (i = 1; i < argc; i++)
 
   switch(switchchar)
     {
 
   switch(switchchar)
     {
+
+    /* sendmail uses -Ac and -Am to control which .cf file is used;
+    we ignore them. */
+    case 'A':
+    if (*argrest == '\0') { badarg = TRUE; break; }
+    else
+      {
+      BOOL ignore = FALSE;
+      switch (*argrest)
+        {
+        case 'c':
+        case 'm':
+          if (*(argrest + 1) == '\0')
+            ignore = TRUE;
+          break;
+        }
+      if (!ignore) { badarg = TRUE; break; }
+      }
+    break;
+
     /* -Btype is a sendmail option for 7bit/8bit setting. Exim is 8-bit clean
     so has no need of it. */
 
     /* -Btype is a sendmail option for 7bit/8bit setting. Exim is 8-bit clean
     so has no need of it. */
 
@@ -1803,6 +1999,32 @@ for (i = 1; i < argc; i++)
 
     else if (Ustrcmp(argrest, "i") == 0) bi_option = TRUE;
 
 
     else if (Ustrcmp(argrest, "i") == 0) bi_option = TRUE;
 
+    /* -bI: provide information, of the type to follow after a colon.
+    This is an Exim flag. */
+
+    else if (argrest[0] == 'I' && Ustrlen(argrest) >= 2 && argrest[1] == ':')
+      {
+      uschar *p = &argrest[2];
+      info_flag = CMDINFO_HELP;
+      if (Ustrlen(p))
+        {
+        if (strcmpic(p, CUS"sieve") == 0)
+          {
+          info_flag = CMDINFO_SIEVE;
+          info_stdout = TRUE;
+          }
+        else if (strcmpic(p, CUS"dscp") == 0)
+          {
+          info_flag = CMDINFO_DSCP;
+          info_stdout = TRUE;
+          }
+        else if (strcmpic(p, CUS"help") == 0)
+          {
+          info_stdout = TRUE;
+          }
+        }
+      }
+
     /* -bm: Accept and deliver message - the default option. Reinstate
     receiving_message, which got turned off for all -b options. */
 
     /* -bm: Accept and deliver message - the default option. Reinstate
     receiving_message, which got turned off for all -b options. */
 
@@ -1936,6 +2158,24 @@ for (i = 1; i < argc; i++)
       show_whats_supported(stdout);
       }
 
       show_whats_supported(stdout);
       }
 
+    /* -bw: inetd wait mode, accept a listening socket as stdin */
+
+    else if (*argrest == 'w')
+      {
+      inetd_wait_mode = TRUE;
+      background_daemon = FALSE;
+      daemon_listen = TRUE;
+      if (*(++argrest) != '\0')
+        {
+        inetd_wait_timeout = readconf_readtime(argrest, 0, FALSE);
+        if (inetd_wait_timeout <= 0)
+          {
+          fprintf(stderr, "exim: bad time value %s: abandoned\n", argv[i]);
+          exit(EXIT_FAILURE);
+          }
+        }
+      }
+
     else badarg = TRUE;
     break;
 
     else badarg = TRUE;
     break;
 
@@ -2267,9 +2507,13 @@ for (i = 1; i < argc; i++)
       }
     break;
 
       }
     break;
 
-    /* This is some Sendmail thing which can be ignored */
+    /* -G: sendmail invocation to specify that it's a gateway submission and
+    sendmail may complain about problems instead of fixing them.
+    We make it equivalent to an ACL "control = suppress_local_fixups" and do
+    not at this time complain about problems. */
 
     case 'G':
 
     case 'G':
+    flag_G = TRUE;
     break;
 
     /* -h: Set the hop count for an incoming message. Exim does not currently
     break;
 
     /* -h: Set the hop count for an incoming message. Exim does not currently
@@ -2294,6 +2538,29 @@ for (i = 1; i < argc; i++)
     break;
 
 
     break;
 
 
+    /* -L: set the identifier used for syslog; equivalent to setting
+    syslog_processname in the config file, but needs to be an admin option. */
+
+    case 'L':
+    if (*argrest == '\0')
+      {
+      if(++i < argc) argrest = argv[i]; else
+        { badarg = TRUE; break; }
+      }
+    sz = Ustrlen(argrest);
+    if (sz > 32)
+      {
+      fprintf(stderr, "exim: the -L syslog name is too long: \"%s\"\n", argrest);
+      return EXIT_FAILURE;
+      }
+    if (sz < 1)
+      {
+      fprintf(stderr, "exim: the -L syslog name is too short\n");
+      return EXIT_FAILURE;
+      }
+    cmdline_syslog_name = argrest;
+    break;
+
     case 'M':
     receiving_message = FALSE;
 
     case 'M':
     receiving_message = FALSE;
 
@@ -2556,10 +2823,12 @@ for (i = 1; i < argc; i++)
     break;
 
 
     break;
 
 
-    /* -n: This means "don't alias" in sendmail, apparently. Just ignore
-    it. */
+    /* -n: This means "don't alias" in sendmail, apparently.
+    For normal invocations, it has no effect.
+    It may affect some other options. */
 
     case 'n':
 
     case 'n':
+    flag_n = TRUE;
     break;
 
     /* -O: Just ignore it. In sendmail, apparently -O option=value means set
     break;
 
     /* -O: Just ignore it. In sendmail, apparently -O option=value means set
@@ -3007,7 +3276,7 @@ for (i = 1; i < argc; i++)
     /* -tls-on-connect: don't wait for STARTTLS (for old clients) */
 
     #ifdef SUPPORT_TLS
     /* -tls-on-connect: don't wait for STARTTLS (for old clients) */
 
     #ifdef SUPPORT_TLS
-    else if (Ustrcmp(argrest, "ls-on-connect") == 0) tls_on_connect = TRUE;
+    else if (Ustrcmp(argrest, "ls-on-connect") == 0) tls_in.on_connect = TRUE;
     #endif
 
     else badarg = TRUE;
     #endif
 
     else badarg = TRUE;
@@ -3048,6 +3317,20 @@ for (i = 1; i < argc; i++)
     if (*argrest != 0) badarg = TRUE;
     break;
 
     if (*argrest != 0) badarg = TRUE;
     break;
 
+    /* -X: in sendmail: takes one parameter, logfile, and sends debugging
+    logs to that file.  We swallow the parameter and otherwise ignore it. */
+
+    case 'X':
+    if (*argrest == '\0')
+      {
+      if (++i >= argc)
+        {
+        fprintf(stderr, "exim: string expected after -X\n");
+        exit(EXIT_FAILURE);
+        }
+      }
+    break;
+
     /* All other initial characters are errors */
 
     default:
     /* All other initial characters are errors */
 
     default:
@@ -3098,6 +3381,9 @@ if ((
     daemon_listen && queue_interval == 0
     ) ||
     (
     daemon_listen && queue_interval == 0
     ) ||
     (
+    inetd_wait_mode && queue_interval >= 0
+    ) ||
+    (
     list_options &&
     (checking || smtp_input || extract_recipients ||
       filter_test != FTEST_NONE || bi_option)
     list_options &&
     (checking || smtp_input || extract_recipients ||
       filter_test != FTEST_NONE || bi_option)
@@ -3143,7 +3429,8 @@ if (debug_selector != 0)
     debug_printf("Exim version %s uid=%ld gid=%ld pid=%d D=%x\n",
       version_string, (long int)real_uid, (long int)real_gid, (int)getpid(),
       debug_selector);
     debug_printf("Exim version %s uid=%ld gid=%ld pid=%d D=%x\n",
       version_string, (long int)real_uid, (long int)real_gid, (int)getpid(),
       debug_selector);
-    show_whats_supported(stderr);
+    if (!version_printed)
+      show_whats_supported(stderr);
     }
   }
 
     }
   }
 
@@ -3223,6 +3510,11 @@ till after reading the config, which might specify the exim gid. Therefore,
 save the group list here first. */
 
 group_count = getgroups(NGROUPS_MAX, group_list);
 save the group list here first. */
 
 group_count = getgroups(NGROUPS_MAX, group_list);
+if (group_count < 0)
+  {
+  fprintf(stderr, "exim: getgroups() failed: %s\n", strerror(errno));
+  exit(EXIT_FAILURE);
+  }
 
 /* There is a fundamental difference in some BSD systems in the matter of
 groups. FreeBSD and BSDI are known to be different; NetBSD and OpenBSD are
 
 /* There is a fundamental difference in some BSD systems in the matter of
 groups. FreeBSD and BSDI are known to be different; NetBSD and OpenBSD are
@@ -3285,9 +3577,13 @@ if ((                                            /* EITHER */
   and should be used for any logging information because attempts to write
   to the log will usually fail. To arrange this, we unset really_exim. However,
   if no stderr is available there is no point - we might as well have a go
   and should be used for any logging information because attempts to write
   to the log will usually fail. To arrange this, we unset really_exim. However,
   if no stderr is available there is no point - we might as well have a go
-  at the log (if it fails, syslog will be written). */
+  at the log (if it fails, syslog will be written).
 
 
-  if (log_stderr != NULL) really_exim = FALSE;
+  Note that if the invoker is Exim, the logs remain available. Messing with
+  this causes unlogged successful deliveries.  */
+
+  if ((log_stderr != NULL) && (real_uid != exim_uid))
+    really_exim = FALSE;
   }
 
 /* Privilege is to be retained for the moment. It may be dropped later,
   }
 
 /* Privilege is to be retained for the moment. It may be dropped later,
@@ -3322,12 +3618,83 @@ if ((filter_test & FTEST_USER) != 0)
     }
   }
 
     }
   }
 
+/* Initialise lookup_list
+If debugging, already called above via version reporting.
+In either case, we initialise the list of available lookups while running
+as root.  All dynamically modules are loaded from a directory which is
+hard-coded into the binary and is code which, if not a module, would be
+part of Exim already.  Ability to modify the content of the directory
+is equivalent to the ability to modify a setuid binary!
+
+This needs to happen before we read the main configuration. */
+init_lookup_list();
+
 /* Read the main runtime configuration data; this gives up if there
 is a failure. It leaves the configuration file open so that the subsequent
 configuration data for delivery can be read if needed. */
 
 readconf_main();
 
 /* Read the main runtime configuration data; this gives up if there
 is a failure. It leaves the configuration file open so that the subsequent
 configuration data for delivery can be read if needed. */
 
 readconf_main();
 
+/* If an action on specific messages is requested, or if a daemon or queue
+runner is being started, we need to know if Exim was called by an admin user.
+This is the case if the real user is root or exim, or if the real group is
+exim, or if one of the supplementary groups is exim or a group listed in
+admin_groups. We don't fail all message actions immediately if not admin_user,
+since some actions can be performed by non-admin users. Instead, set admin_user
+for later interrogation. */
+
+if (real_uid == root_uid || real_uid == exim_uid || real_gid == exim_gid)
+  admin_user = TRUE;
+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++)
+        if (admin_groups[j] == group_list[i])
+          { admin_user = TRUE; break; }
+      }
+    if (admin_user) break;
+    }
+  }
+
+/* Another group of privileged users are the trusted users. These are root,
+exim, and any caller matching trusted_users or trusted_groups. Trusted callers
+are permitted to specify sender_addresses with -f on the command line, and
+other message parameters as well. */
+
+if (real_uid == root_uid || real_uid == exim_uid)
+  trusted_caller = TRUE;
+else
+  {
+  int i, j;
+
+  if (trusted_users != NULL)
+    {
+    for (i = 1; i <= (int)(trusted_users[0]); i++)
+      if (trusted_users[i] == real_uid)
+        { trusted_caller = TRUE; break; }
+    }
+
+  if (!trusted_caller && trusted_groups != NULL)
+    {
+    for (i = 1; i <= (int)(trusted_groups[0]); i++)
+      {
+      if (trusted_groups[i] == real_gid)
+        trusted_caller = TRUE;
+      else for (j = 0; j < group_count; j++)
+        {
+        if (trusted_groups[i] == group_list[j])
+          { trusted_caller = TRUE; break; }
+        }
+      if (trusted_caller) break;
+      }
+    }
+  }
+
 /* Handle the decoding of logging options. */
 
 decode_bits(&log_write_selector, &log_extra_selector, 0, 0,
 /* Handle the decoding of logging options. */
 
 decode_bits(&log_write_selector, &log_extra_selector, 0, 0,
@@ -3359,6 +3726,24 @@ if (sender_address != NULL)
     }
   }
 
     }
   }
 
+/* See if an admin user overrode our logging. */
+
+if (cmdline_syslog_name != NULL)
+  {
+  if (admin_user)
+    {
+    syslog_processname = cmdline_syslog_name;
+    log_file_path = string_copy(CUS"syslog");
+    }
+  else
+    {
+    /* not a panic, non-privileged users should not be able to spam paniclog */
+    fprintf(stderr,
+        "exim: you lack sufficient privilege to specify syslog process name\n");
+    return EXIT_FAILURE;
+    }
+  }
+
 /* Paranoia check of maximum lengths of certain strings. There is a check
 on the length of the log file path in log.c, which will come into effect
 if there are any calls to write the log earlier than this. However, if we
 /* Paranoia check of maximum lengths of certain strings. There is a check
 on the length of the log file path in log.c, which will come into effect
 if there are any calls to write the log earlier than this. However, if we
@@ -3602,65 +3987,9 @@ if (bi_option)
     }
   }
 
     }
   }
 
-/* If an action on specific messages is requested, or if a daemon or queue
-runner is being started, we need to know if Exim was called by an admin user.
-This is the case if the real user is root or exim, or if the real group is
-exim, or if one of the supplementary groups is exim or a group listed in
-admin_groups. We don't fail all message actions immediately if not admin_user,
-since some actions can be performed by non-admin users. Instead, set admin_user
-for later interrogation. */
-
-if (real_uid == root_uid || real_uid == exim_uid || real_gid == exim_gid)
-  admin_user = TRUE;
-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++)
-        if (admin_groups[j] == group_list[i])
-          { admin_user = TRUE; break; }
-      }
-    if (admin_user) break;
-    }
-  }
-
-/* Another group of privileged users are the trusted users. These are root,
-exim, and any caller matching trusted_users or trusted_groups. Trusted callers
-are permitted to specify sender_addresses with -f on the command line, and
-other message parameters as well. */
-
-if (real_uid == root_uid || real_uid == exim_uid)
-  trusted_caller = TRUE;
-else
-  {
-  int i, j;
-
-  if (trusted_users != NULL)
-    {
-    for (i = 1; i <= (int)(trusted_users[0]); i++)
-      if (trusted_users[i] == real_uid)
-        { trusted_caller = TRUE; break; }
-    }
-
-  if (!trusted_caller && trusted_groups != NULL)
-    {
-    for (i = 1; i <= (int)(trusted_groups[0]); i++)
-      {
-      if (trusted_groups[i] == real_gid)
-        trusted_caller = TRUE;
-      else for (j = 0; j < group_count; j++)
-        {
-        if (trusted_groups[i] == group_list[j])
-          { trusted_caller = TRUE; break; }
-        }
-      if (trusted_caller) break;
-      }
-    }
-  }
+/* We moved the admin/trusted check to be immediately after reading the
+configuration file.  We leave these prints here to ensure that syslog setup,
+logfile setup, and so on has already happened. */
 
 if (trusted_caller) DEBUG(D_any) debug_printf("trusted user\n");
 if (admin_user) DEBUG(D_any) debug_printf("admin user\n");
 
 if (trusted_caller) DEBUG(D_any) debug_printf("trusted user\n");
 if (admin_user) DEBUG(D_any) debug_printf("admin user\n");
@@ -3728,6 +4057,21 @@ else
     interface_port = check_port(interface_address);
   }
 
     interface_port = check_port(interface_address);
   }
 
+/* If the caller is trusted, then they can use -G to suppress_local_fixups. */
+if (flag_G)
+  {
+  if (trusted_caller)
+    {
+    suppress_local_fixups = suppress_local_fixups_default = TRUE;
+    DEBUG(D_acl) debug_printf("suppress_local_fixups forced on by -G\n");
+    }
+  else
+    {
+    fprintf(stderr, "exim: permission denied (-G requires a trusted user)\n");
+    return EXIT_FAILURE;
+    }
+  }
+
 /* If an SMTP message is being received check to see if the standard input is a
 TCP/IP socket. If it is, we assume that Exim was called from inetd if the
 caller is root or the Exim user, or if the port is a privileged one. Otherwise,
 /* If an SMTP message is being received check to see if the standard input is a
 TCP/IP socket. If it is, we assume that Exim was called from inetd if the
 caller is root or the Exim user, or if the port is a privileged one. Otherwise,
@@ -3749,7 +4093,7 @@ if (smtp_input)
         interface_address = host_ntoa(-1, &interface_sock, NULL,
           &interface_port);
 
         interface_address = host_ntoa(-1, &interface_sock, NULL,
           &interface_port);
 
-      if (host_is_tls_on_connect_port(interface_port)) tls_on_connect = TRUE;
+      if (host_is_tls_on_connect_port(interface_port)) tls_in.on_connect = TRUE;
 
       if (real_uid == root_uid || real_uid == exim_uid || interface_port < 1024)
         {
 
       if (real_uid == root_uid || real_uid == exim_uid || interface_port < 1024)
         {
@@ -3825,7 +4169,28 @@ if (!unprivileged &&                      /* originally had root AND */
 
 /* 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. */
 
-else setgid(exim_gid);
+else
+  {
+  int rv;
+  rv = setgid(exim_gid);
+  /* Impact of failure is that some stuff might end up with an incorrect group.
+  We track this for failures from root, since any attempt to change privilege
+  by root should succeed and failures should be examined.  For non-root,
+  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,
+          "exim: changing group failed: %s\n", strerror(errno));
+      exit(EXIT_FAILURE);
+      }
+    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 */
 if (malware_test_file)
 
 /* Handle a request to scan a file for malware */
 if (malware_test_file)
@@ -4035,11 +4400,12 @@ if (test_retry_arg >= 0)
   }
 
 /* Handle a request to list one or more configuration options */
   }
 
 /* Handle a request to list one or more configuration options */
+/* If -n was set, we suppress some information */
 
 if (list_options)
   {
   set_process_info("listing variables");
 
 if (list_options)
   {
   set_process_info("listing variables");
-  if (recipients_arg >= argc) readconf_print(US"all", NULL);
+  if (recipients_arg >= argc) readconf_print(US"all", NULL, flag_n);
     else for (i = recipients_arg; i < argc; i++)
       {
       if (i < argc - 1 &&
     else for (i = recipients_arg; i < argc; i++)
       {
       if (i < argc - 1 &&
@@ -4048,10 +4414,10 @@ if (list_options)
            Ustrcmp(argv[i], "authenticator") == 0 ||
            Ustrcmp(argv[i], "macro") == 0))
         {
            Ustrcmp(argv[i], "authenticator") == 0 ||
            Ustrcmp(argv[i], "macro") == 0))
         {
-        readconf_print(argv[i+1], argv[i]);
+        readconf_print(argv[i+1], argv[i], flag_n);
         i++;
         }
         i++;
         }
-      else readconf_print(argv[i], NULL);
+      else readconf_print(argv[i], NULL, flag_n);
       }
   exim_exit(EXIT_SUCCESS);
   }
       }
   exim_exit(EXIT_SUCCESS);
   }
@@ -4240,7 +4606,7 @@ returns. We leave this till here so that the originator_ fields are available
 for incoming messages via the daemon. The daemon cannot be run in mua_wrapper
 mode. */
 
 for incoming messages via the daemon. The daemon cannot be run in mua_wrapper
 mode. */
 
-if (daemon_listen || queue_interval > 0)
+if (daemon_listen || inetd_wait_mode || queue_interval > 0)
   {
   if (mua_wrapper)
     {
   {
   if (mua_wrapper)
     {
@@ -4457,8 +4823,8 @@ if (expansion_test)
 
   else
     {
 
   else
     {
-    char *(*fn_readline)(char *) = NULL;
-    char *(*fn_addhist)(char *) = NULL;
+    char *(*fn_readline)(const char *) = NULL;
+    void (*fn_addhist)(const char *) = NULL;
 
     #ifdef USE_READLINE
     void *dlhandle = set_readline(&fn_readline, &fn_addhist);
 
     #ifdef USE_READLINE
     void *dlhandle = set_readline(&fn_readline, &fn_addhist);
@@ -4578,7 +4944,8 @@ if (host_checking)
 
 /* Arrange for message reception if recipients or SMTP were specified;
 otherwise complain unless a version print (-bV) happened or this is a filter
 
 /* Arrange for message reception if recipients or SMTP were specified;
 otherwise complain unless a version print (-bV) happened or this is a filter
-verification test. In the former case, show the configuration file name. */
+verification test or info dump.
+In the former case, show the configuration file name. */
 
 if (recipients_arg >= argc && !extract_recipients && !smtp_input)
   {
 
 if (recipients_arg >= argc && !extract_recipients && !smtp_input)
   {
@@ -4588,6 +4955,12 @@ if (recipients_arg >= argc && !extract_recipients && !smtp_input)
     return EXIT_SUCCESS;
     }
 
     return EXIT_SUCCESS;
     }
 
+  if (info_flag != CMDINFO_NONE)
+    {
+    show_exim_information(info_flag, info_stdout ? stdout : stderr);
+    return info_stdout ? EXIT_SUCCESS : EXIT_FAILURE;
+    }
+
   if (filter_test == FTEST_NONE)
     exim_usage(called_as);
   }
   if (filter_test == FTEST_NONE)
     exim_usage(called_as);
   }