summaryrefslogtreecommitdiffstats
path: root/src/exim.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/exim.c')
-rw-r--r--src/exim.c6097
1 files changed, 6097 insertions, 0 deletions
diff --git a/src/exim.c b/src/exim.c
new file mode 100644
index 0000000..fd01d13
--- /dev/null
+++ b/src/exim.c
@@ -0,0 +1,6097 @@
+/*************************************************
+* Exim - an Internet mail transport agent *
+*************************************************/
+
+/* Copyright (c) The Exim Maintainers 2020 - 2022 */
+/* Copyright (c) University of Cambridge 1995 - 2018 */
+/* See the file NOTICE for conditions of use and distribution. */
+
+
+/* The main function: entry point, initialization, and high-level control.
+Also a few functions that don't naturally fit elsewhere. */
+
+
+#include "exim.h"
+
+#if defined(__GLIBC__) && !defined(__UCLIBC__)
+# include <gnu/libc-version.h>
+#endif
+
+#ifdef USE_GNUTLS
+# include <gnutls/gnutls.h>
+# if GNUTLS_VERSION_NUMBER < 0x030103 && !defined(DISABLE_OCSP)
+# define DISABLE_OCSP
+# endif
+#endif
+
+#ifndef _TIME_H
+# include <time.h>
+#endif
+
+extern void init_lookup_list(void);
+
+
+
+/*************************************************
+* Function interface to store functions *
+*************************************************/
+
+/* We need some real functions to pass to the PCRE regular expression library
+for store allocation via Exim's store manager. The normal calls are actually
+macros that pass over location information to make tracing easier. These
+functions just interface to the standard macro calls. A good compiler will
+optimize out the tail recursion and so not make them too expensive. */
+
+static void *
+function_store_malloc(PCRE2_SIZE size, void * tag)
+{
+return store_malloc((int)size);
+}
+
+static void
+function_store_free(void * block, void * tag)
+{
+/* At least some version of pcre2 pass a null pointer */
+if (block) 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 *
+*************************************************/
+
+/* This function is called when failure to compile a regular expression leads
+to a panic exit. In other cases, pcre_compile() is called directly. In many
+cases where this function is used, the results of the compilation are to be
+placed in long-lived store, so we temporarily reset the store management
+functions that PCRE uses if the use_malloc flag is set.
+
+Argument:
+ pattern the pattern to compile
+ caseless TRUE if caseless matching is required
+ use_malloc TRUE if compile into malloc store
+
+Returns: pointer to the compiled pattern
+*/
+
+const pcre2_code *
+regex_must_compile(const uschar * pattern, BOOL caseless, BOOL use_malloc)
+{
+size_t offset;
+int options = caseless ? PCRE_COPT|PCRE2_CASELESS : PCRE_COPT;
+const pcre2_code * yield;
+int err;
+pcre2_general_context * gctx;
+pcre2_compile_context * cctx;
+
+if (use_malloc)
+ {
+ gctx = pcre2_general_context_create(function_store_malloc, function_store_free, NULL);
+ cctx = pcre2_compile_context_create(gctx);
+ }
+else
+ cctx = pcre_cmp_ctx;
+
+if (!(yield = pcre2_compile((PCRE2_SPTR)pattern, PCRE2_ZERO_TERMINATED, options,
+ &err, &offset, cctx)))
+ {
+ uschar errbuf[128];
+ pcre2_get_error_message(err, errbuf, sizeof(errbuf));
+ log_write(0, LOG_MAIN|LOG_PANIC_DIE, "regular expression error: "
+ "%s at offset %ld while compiling %s", errbuf, (long)offset, pattern);
+ }
+
+if (use_malloc)
+ {
+ pcre2_compile_context_free(cctx);
+ pcre2_general_context_free(gctx);
+ }
+return yield;
+}
+
+
+static void
+pcre_init(void)
+{
+pcre_gen_ctx = pcre2_general_context_create(function_store_malloc, function_store_free, NULL);
+pcre_cmp_ctx = pcre2_compile_context_create(pcre_gen_ctx);
+pcre_mtc_ctx = pcre2_match_context_create(pcre_gen_ctx);
+}
+
+
+
+
+/*************************************************
+* Execute regular expression and set strings *
+*************************************************/
+
+/* This function runs a regular expression match, and sets up the pointers to
+the matched substrings. The matched strings are copied so the lifetime of
+the subject is not a problem.
+
+Arguments:
+ re the compiled expression
+ subject the subject string
+ options additional PCRE options
+ setup if < 0 do full setup
+ if >= 0 setup from setup+1 onwards,
+ excluding the full matched string
+
+Returns: TRUE if matched, or FALSE
+*/
+
+BOOL
+regex_match_and_setup(const pcre2_code * re, const uschar * subject, int options, int setup)
+{
+pcre2_match_data * md = pcre2_match_data_create_from_pattern(re, pcre_gen_ctx);
+int res = pcre2_match(re, (PCRE2_SPTR)subject, PCRE2_ZERO_TERMINATED, 0,
+ PCRE_EOPT | options, md, pcre_mtc_ctx);
+BOOL yield;
+
+if ((yield = (res >= 0)))
+ {
+ res = pcre2_get_ovector_count(md);
+ expand_nmax = setup < 0 ? 0 : setup + 1;
+ for (int matchnum = setup < 0 ? 0 : 1; matchnum < res; matchnum++)
+ {
+ PCRE2_SIZE len;
+ pcre2_substring_get_bynumber(md, matchnum,
+ (PCRE2_UCHAR **)&expand_nstring[expand_nmax], &len);
+ expand_nlength[expand_nmax++] = (int)len;
+ }
+ expand_nmax--;
+ }
+else if (res != PCRE2_ERROR_NOMATCH) DEBUG(D_any)
+ {
+ uschar errbuf[128];
+ pcre2_get_error_message(res, errbuf, sizeof(errbuf));
+ debug_printf_indent("pcre2: %s\n", errbuf);
+ }
+pcre2_match_data_free(md);
+return yield;
+}
+
+
+/* Check just for match with regex. Uses the common memory-handling.
+
+Arguments:
+ re compiled regex
+ subject string to be checked
+ slen length of subject; -1 for nul-terminated
+ rptr pointer for matched string, copied, or NULL
+
+Return: TRUE for a match.
+*/
+
+BOOL
+regex_match(const pcre2_code * re, const uschar * subject, int slen, uschar ** rptr)
+{
+pcre2_match_data * md = pcre2_match_data_create(1, pcre_gen_ctx);
+int rc = pcre2_match(re, (PCRE2_SPTR)subject,
+ slen >= 0 ? slen : PCRE2_ZERO_TERMINATED,
+ 0, PCRE_EOPT, md, pcre_mtc_ctx);
+PCRE2_SIZE * ovec = pcre2_get_ovector_pointer(md);
+if (rc < 0)
+ return FALSE;
+if (rptr)
+ *rptr = string_copyn(subject + ovec[0], ovec[1] - ovec[0]);
+return TRUE;
+}
+
+
+
+/*************************************************
+* 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, ...)
+{
+gstring gs = { .size = PROCESS_INFO_SIZE - 2, .ptr = 0, .s = process_info };
+gstring * g;
+int len;
+va_list ap;
+
+g = string_fmt_append(&gs, "%5d ", (int)getpid());
+len = g->ptr;
+va_start(ap, format);
+if (!string_vformat(g, 0, format, ap))
+ {
+ gs.ptr = len;
+ g = string_cat(&gs, US"**** string overflowed buffer ****");
+ }
+g = string_catn(g, US"\n", 1);
+string_from_gstring(g);
+process_info_len = g->ptr;
+DEBUG(D_process_info) debug_printf("set_process_info: %s", process_info);
+va_end(ap);
+}
+
+/***********************************************
+* Handler for SIGTERM *
+***********************************************/
+
+static void
+term_handler(int sig)
+{
+exit(1);
+}
+
+
+/***********************************************
+* Handler for SIGSEGV *
+***********************************************/
+
+static void
+#ifdef SA_SIGINFO
+segv_handler(int sig, siginfo_t * info, void * uctx)
+{
+log_write(0, LOG_MAIN|LOG_PANIC, "SIGSEGV (fault address: %p)", info->si_addr);
+# if defined(SEGV_MAPERR) && defined(SEGV_ACCERR) && defined(SEGV_BNDERR) && defined(SEGV_PKUERR)
+switch (info->si_code)
+ {
+ case SEGV_MAPERR: log_write(0, LOG_MAIN|LOG_PANIC, "SEGV_MAPERR"); break;
+ case SEGV_ACCERR: log_write(0, LOG_MAIN|LOG_PANIC, "SEGV_ACCERR"); break;
+ case SEGV_BNDERR: log_write(0, LOG_MAIN|LOG_PANIC, "SEGV_BNDERR"); break;
+ case SEGV_PKUERR: log_write(0, LOG_MAIN|LOG_PANIC, "SEGV_PKUERR"); break;
+ }
+# endif
+if (US info->si_addr < US 4096)
+ log_write(0, LOG_MAIN|LOG_PANIC, "SIGSEGV (null pointer indirection)");
+else
+ log_write(0, LOG_MAIN|LOG_PANIC, "SIGSEGV (maybe attempt to write to immutable memory)");
+if (process_info_len > 0)
+ log_write(0, LOG_MAIN|LOG_PANIC, "SIGSEGV (%.*s)", process_info_len, process_info);
+signal(SIGSEGV, SIG_DFL);
+kill(getpid(), sig);
+}
+
+#else
+segv_handler(int sig)
+{
+log_write(0, LOG_MAIN|LOG_PANIC, "SIGSEGV (maybe attempt to write to immutable memory)");
+if (process_info_len > 0)
+ log_write(0, LOG_MAIN|LOG_PANIC, "SIGSEGV (%.*s)", process_info_len, process_info);
+signal(SIGSEGV, SIG_DFL);
+kill(getpid(), sig);
+}
+#endif
+
+
+/*************************************************
+* Handler for SIGUSR1 *
+*************************************************/
+
+/* SIGUSR1 causes any exim process to write to the process log details of
+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.
+
+This function takes care to be signal-safe.
+
+Argument: the signal number (SIGUSR1)
+Returns: nothing
+*/
+
+static void
+usr1_handler(int sig)
+{
+int fd;
+
+os_restarting_signal(sig, usr1_handler);
+
+if (!process_log_path) return;
+fd = log_open_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);
+}
+
+
+
+/*************************************************
+* Timeout handler *
+*************************************************/
+
+/* This handler is enabled most of the time that Exim is running. The handler
+doesn't actually get used unless alarm() has been called to set a timer, to
+place a time limit on a system call of some kind. When the handler is run, it
+re-enables itself.
+
+There are some other SIGALRM handlers that are used in special cases when more
+than just a flag setting is required; for example, when reading a message's
+input. These are normally set up in the code module that uses them, and the
+SIGALRM handler is reset to this one afterwards.
+
+Argument: the signal value (SIGALRM)
+Returns: nothing
+*/
+
+void
+sigalrm_handler(int sig)
+{
+sigalrm_seen = TRUE;
+os_non_restarting_signal(SIGALRM, sigalrm_handler);
+}
+
+
+
+/*************************************************
+* Sleep for a fractional time interval *
+*************************************************/
+
+/* This function is called by millisleep() and exim_wait_tick() to wait for a
+period of time that may include a fraction of a second. The coding is somewhat
+tedious. We do not expect setitimer() ever to fail, but if it does, the process
+will wait for ever, so we panic in this instance. (There was a case of this
+when a bug in a function that calls milliwait() caused it to pass invalid data.
+That's when I added the check. :-)
+
+We assume it to be not worth sleeping for under 50us; this value will
+require revisiting as hardware advances. This avoids the issue of
+a zero-valued timer setting meaning "never fire".
+
+Argument: an itimerval structure containing the interval
+Returns: nothing
+*/
+
+static void
+milliwait(struct itimerval *itval)
+{
+sigset_t sigmask;
+sigset_t old_sigmask;
+int save_errno = errno;
+
+if (itval->it_value.tv_usec < 50 && itval->it_value.tv_sec == 0)
+ return;
+(void)sigemptyset(&sigmask); /* Empty mask */
+(void)sigaddset(&sigmask, SIGALRM); /* Add SIGALRM */
+(void)sigprocmask(SIG_BLOCK, &sigmask, &old_sigmask); /* Block SIGALRM */
+if (setitimer(ITIMER_REAL, itval, NULL) < 0) /* Start timer */
+ log_write(0, LOG_MAIN|LOG_PANIC_DIE,
+ "setitimer() failed: %s", strerror(errno));
+(void)sigfillset(&sigmask); /* All signals */
+(void)sigdelset(&sigmask, SIGALRM); /* Remove SIGALRM */
+(void)sigsuspend(&sigmask); /* Until SIGALRM */
+(void)sigprocmask(SIG_SETMASK, &old_sigmask, NULL); /* Restore mask */
+errno = save_errno;
+sigalrm_seen = FALSE;
+}
+
+
+
+
+/*************************************************
+* Millisecond sleep function *
+*************************************************/
+
+/* The basic sleep() function has a granularity of 1 second, which is too rough
+in some cases - for example, when using an increasing delay to slow down
+spammers.
+
+Argument: number of millseconds
+Returns: nothing
+*/
+
+void
+millisleep(int msec)
+{
+struct itimerval itval = {.it_interval = {.tv_sec = 0, .tv_usec = 0},
+ .it_value = {.tv_sec = msec/1000,
+ .tv_usec = (msec % 1000) * 1000}};
+milliwait(&itval);
+}
+
+
+
+/*************************************************
+* Compare microsecond times *
+*************************************************/
+
+/*
+Arguments:
+ tv1 the first time
+ tv2 the second time
+
+Returns: -1, 0, or +1
+*/
+
+static int
+exim_tvcmp(struct timeval *t1, struct timeval *t2)
+{
+if (t1->tv_sec > t2->tv_sec) return +1;
+if (t1->tv_sec < t2->tv_sec) return -1;
+if (t1->tv_usec > t2->tv_usec) return +1;
+if (t1->tv_usec < t2->tv_usec) return -1;
+return 0;
+}
+
+
+
+
+/*************************************************
+* Clock tick wait function *
+*************************************************/
+
+#ifdef _POSIX_MONOTONIC_CLOCK
+# ifdef CLOCK_BOOTTIME
+# define EXIM_CLOCKTYPE CLOCK_BOOTTIME
+# else
+# define EXIM_CLOCKTYPE CLOCK_MONOTONIC
+# endif
+
+/* Amount EXIM_CLOCK is behind realtime, at startup. */
+static struct timespec offset_ts;
+
+static void
+exim_clock_init(void)
+{
+struct timeval tv;
+if (clock_gettime(EXIM_CLOCKTYPE, &offset_ts) != 0) return;
+(void)gettimeofday(&tv, NULL);
+offset_ts.tv_sec = tv.tv_sec - offset_ts.tv_sec;
+offset_ts.tv_nsec = tv.tv_usec * 1000 - offset_ts.tv_nsec;
+if (offset_ts.tv_nsec >= 0) return;
+offset_ts.tv_sec--;
+offset_ts.tv_nsec += 1000*1000*1000;
+}
+#endif
+
+
+void
+exim_gettime(struct timeval * tv)
+{
+#ifdef _POSIX_MONOTONIC_CLOCK
+struct timespec now_ts;
+
+if (clock_gettime(EXIM_CLOCKTYPE, &now_ts) == 0)
+ {
+ now_ts.tv_sec += offset_ts.tv_sec;
+ if ((now_ts.tv_nsec += offset_ts.tv_nsec) >= 1000*1000*1000)
+ {
+ now_ts.tv_sec++;
+ now_ts.tv_nsec -= 1000*1000*1000;
+ }
+ tv->tv_sec = now_ts.tv_sec;
+ tv->tv_usec = now_ts.tv_nsec / 1000;
+ }
+else
+#endif
+ (void)gettimeofday(tv, NULL);
+}
+
+
+/* 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 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
+function prepares for the time when things are faster - and it also copes with
+clocks that go backwards.
+
+Arguments:
+ prev_tv A timeval which was used to create uniqueness; its usec field
+ has been rounded down to the value of the resolution.
+ We want to be sure the current time is greater than this.
+ On return, updated to current (rounded down).
+ resolution The resolution that was used to divide the microseconds
+ (1 for maildir, larger for message ids)
+
+Returns: nothing
+*/
+
+void
+exim_wait_tick(struct timeval * prev_tv, int resolution)
+{
+struct timeval now_tv;
+long int now_true_usec;
+
+exim_gettime(&now_tv);
+now_true_usec = now_tv.tv_usec;
+now_tv.tv_usec = (now_true_usec/resolution) * resolution;
+
+while (exim_tvcmp(&now_tv, prev_tv) <= 0)
+ {
+ struct itimerval itval;
+ itval.it_interval.tv_sec = 0;
+ itval.it_interval.tv_usec = 0;
+ itval.it_value.tv_sec = prev_tv->tv_sec - now_tv.tv_sec;
+ itval.it_value.tv_usec = prev_tv->tv_usec + resolution - now_true_usec;
+
+ /* We know that, overall, "now" is less than or equal to "then". Therefore, a
+ negative value for the microseconds is possible only in the case when "now"
+ is more than a second less than "tgt". That means that itval.it_value.tv_sec
+ is greater than zero. The following correction is therefore safe. */
+
+ if (itval.it_value.tv_usec < 0)
+ {
+ itval.it_value.tv_usec += 1000000;
+ itval.it_value.tv_sec -= 1;
+ }
+
+ DEBUG(D_transport|D_receive)
+ {
+ if (!f.running_in_test_harness)
+ {
+ debug_printf("tick check: " TIME_T_FMT ".%06lu " TIME_T_FMT ".%06lu\n",
+ prev_tv->tv_sec, (long) prev_tv->tv_usec,
+ now_tv.tv_sec, (long) now_tv.tv_usec);
+ debug_printf("waiting " TIME_T_FMT ".%06lu sec\n",
+ itval.it_value.tv_sec, (long) itval.it_value.tv_usec);
+ }
+ }
+
+ milliwait(&itval);
+
+ /* Be prapared to go around if the kernel does not implement subtick
+ granularity (GNU Hurd) */
+
+ exim_gettime(&now_tv);
+ now_true_usec = now_tv.tv_usec;
+ now_tv.tv_usec = (now_true_usec/resolution) * resolution;
+ }
+*prev_tv = now_tv;
+}
+
+
+
+
+/*************************************************
+* Call fopen() with umask 777 and adjust mode *
+*************************************************/
+
+/* Exim runs with umask(0) so that files created with open() have the mode that
+is specified in the open() call. However, there are some files, typically in
+the spool directory, that are created with fopen(). They end up world-writeable
+if no precautions are taken. Although the spool directory is not accessible to
+the world, this is an untidiness. So this is a wrapper function for fopen()
+that sorts out the mode of the created file.
+
+Arguments:
+ filename the file name
+ options the fopen() options
+ mode the required mode
+
+Returns: the fopened FILE or NULL
+*/
+
+FILE *
+modefopen(const uschar *filename, const char *options, mode_t mode)
+{
+mode_t saved_umask = umask(0777);
+FILE *f = Ufopen(filename, options);
+(void)umask(saved_umask);
+if (f != NULL) (void)fchmod(fileno(f), mode);
+return f;
+}
+
+
+/*************************************************
+* Ensure stdin, stdout, and stderr exist *
+*************************************************/
+
+/* Some operating systems grumble if an exec() happens without a standard
+input, output, and error (fds 0, 1, 2) being defined. The worry is that some
+file will be opened and will use these fd values, and then some other bit of
+code will assume, for example, that it can write error messages to stderr.
+This function ensures that fds 0, 1, and 2 are open if they do not already
+exist, by connecting them to /dev/null.
+
+This function is also used to ensure that std{in,out,err} exist at all times,
+so that if any library that Exim calls tries to use them, it doesn't crash.
+
+Arguments: None
+Returns: Nothing
+*/
+
+void
+exim_nullstd(void)
+{
+int devnull = -1;
+struct stat statbuf;
+for (int i = 0; i <= 2; i++)
+ {
+ if (fstat(i, &statbuf) < 0 && errno == EBADF)
+ {
+ if (devnull < 0) devnull = open("/dev/null", O_RDWR);
+ if (devnull < 0) log_write(0, LOG_MAIN|LOG_PANIC_DIE, "%s",
+ string_open_failed("/dev/null", NULL));
+ if (devnull != i) (void)dup2(devnull, i);
+ }
+ }
+if (devnull > 2) (void)close(devnull);
+}
+
+
+
+
+/*************************************************
+* Close unwanted file descriptors for delivery *
+*************************************************/
+
+/* This function is called from a new process that has been forked to deliver
+an incoming message, either directly, or using exec.
+
+We want any smtp input streams to be closed in this new process. However, it
+has been observed that using fclose() here causes trouble. When reading in -bS
+input, duplicate copies of messages have been seen. The files will be sharing a
+file pointer with the parent process, and it seems that fclose() (at least on
+some systems - I saw this on Solaris 2.5.1) messes with that file pointer, at
+least sometimes. Hence we go for closing the underlying file descriptors.
+
+If TLS is active, we want to shut down the TLS library, but without molesting
+the parent's SSL connection.
+
+For delivery of a non-SMTP message, we want to close stdin and stdout (and
+stderr unless debugging) because the calling process might have set them up as
+pipes and be waiting for them to close before it waits for the submission
+process to terminate. If they aren't closed, they hold up the calling process
+until the initial delivery process finishes, which is not what we want.
+
+Exception: We do want it for synchronous delivery!
+
+And notwithstanding all the above, if D_resolver is set, implying resolver
+debugging, leave stdout open, because that's where the resolver writes its
+debugging output.
+
+When we close stderr (which implies we've also closed stdout), we also get rid
+of any controlling terminal.
+
+Arguments: None
+Returns: Nothing
+*/
+
+static void
+close_unwanted(void)
+{
+if (smtp_input)
+ {
+#ifndef DISABLE_TLS
+ tls_close(NULL, TLS_NO_SHUTDOWN); /* Shut down the TLS library */
+#endif
+ (void)close(fileno(smtp_in));
+ (void)close(fileno(smtp_out));
+ smtp_in = NULL;
+ }
+else
+ {
+ (void)close(0); /* stdin */
+ if ((debug_selector & D_resolver) == 0) (void)close(1); /* stdout */
+ if (debug_selector == 0) /* stderr */
+ {
+ if (!f.synchronous_delivery)
+ {
+ (void)close(2);
+ log_stderr = NULL;
+ }
+ (void)setsid();
+ }
+ }
+}
+
+
+
+
+/*************************************************
+* Set uid and gid *
+*************************************************/
+
+/* This function sets a new uid and gid permanently, optionally calling
+initgroups() to set auxiliary groups. There are some special cases when running
+Exim in unprivileged modes. In these situations the effective uid will not be
+root; if we already have the right effective uid/gid, and don't need to
+initialize any groups, leave things as they are.
+
+Arguments:
+ uid the uid
+ gid the gid
+ igflag TRUE if initgroups() wanted
+ msg text to use in debugging output and failure log
+
+Returns: nothing; bombs out on failure
+*/
+
+void
+exim_setugid(uid_t uid, gid_t gid, BOOL igflag, const uschar * msg)
+{
+uid_t euid = geteuid();
+gid_t egid = getegid();
+
+if (euid == root_uid || euid != uid || egid != gid || igflag)
+ {
+ /* At least one OS returns +1 for initgroups failure, so just check for
+ non-zero. */
+
+ if (igflag)
+ {
+ struct passwd *pw = getpwuid(uid);
+ if (!pw)
+ log_write(0, LOG_MAIN|LOG_PANIC_DIE, "cannot run initgroups(): "
+ "no passwd entry for uid=%ld", (long int)uid);
+
+ if (initgroups(pw->pw_name, gid) != 0)
+ log_write(0,LOG_MAIN|LOG_PANIC_DIE,"initgroups failed for uid=%ld: %s",
+ (long int)uid, strerror(errno));
+ }
+
+ if (setgid(gid) < 0 || setuid(uid) < 0)
+ log_write(0, LOG_MAIN|LOG_PANIC_DIE, "unable to set gid=%ld or uid=%ld "
+ "(euid=%ld): %s", (long int)gid, (long int)uid, (long int)euid, msg);
+ }
+
+/* Debugging output included uid/gid and all groups */
+
+DEBUG(D_uid)
+ {
+ int group_count, save_errno;
+ gid_t group_list[EXIM_GROUPLIST_SIZE];
+ 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(nelem(group_list), group_list);
+ save_errno = errno;
+ debug_printf(" auxiliary group list:");
+ if (group_count > 0)
+ for (int 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");
+ }
+}
+
+
+
+
+/*************************************************
+* Exit point *
+*************************************************/
+
+/* Exim exits via this function so that it always clears up any open
+databases.
+
+Arguments:
+ rc return code
+
+Returns: does not return
+*/
+
+void
+exim_exit(int rc)
+{
+search_tidyup();
+store_exit();
+DEBUG(D_any)
+ debug_printf(">>>>>>>>>>>>>>>> Exim pid=%d (%s) terminating with rc=%d "
+ ">>>>>>>>>>>>>>>>\n",
+ (int)getpid(), process_purpose, rc);
+exit(rc);
+}
+
+
+void
+exim_underbar_exit(int rc)
+{
+store_exit();
+DEBUG(D_any)
+ debug_printf(">>>>>>>>>>>>>>>> Exim pid=%d (%s) terminating with rc=%d "
+ ">>>>>>>>>>>>>>>>\n",
+ (int)getpid(), process_purpose, rc);
+_exit(rc);
+}
+
+
+
+/* Print error string, then die */
+static void
+exim_fail(const char * fmt, ...)
+{
+va_list ap;
+va_start(ap, fmt);
+vfprintf(stderr, fmt, ap);
+exit(EXIT_FAILURE);
+}
+
+/* fail if a length is too long */
+static inline void
+exim_len_fail_toolong(int itemlen, int maxlen, const char *description)
+{
+if (itemlen <= maxlen)
+ return;
+fprintf(stderr, "exim: length limit exceeded (%d > %d) for: %s\n",
+ itemlen, maxlen, description);
+exit(EXIT_FAILURE);
+}
+
+/* only pass through the string item back to the caller if it's short enough */
+static inline const uschar *
+exim_str_fail_toolong(const uschar *item, int maxlen, const char *description)
+{
+exim_len_fail_toolong(Ustrlen(item), maxlen, description);
+return item;
+}
+
+/* exim_chown_failure() called from exim_chown()/exim_fchown() on failure
+of chown()/fchown(). See src/functions.h for more explanation */
+int
+exim_chown_failure(int fd, const uschar *name, uid_t owner, gid_t group)
+{
+int saved_errno = errno; /* from the preceeding chown call */
+#if 1
+log_write(0, LOG_MAIN|LOG_PANIC,
+ __FILE__ ":%d: chown(%s, %d:%d) failed (%s)."
+ " Please contact the authors and refer to https://bugs.exim.org/show_bug.cgi?id=2391",
+ __LINE__, name?name:US"<unknown>", owner, group, strerror(errno));
+#else
+/* I leave this here, commented, in case the "bug"(?) comes up again.
+ It is not an Exim bug, but we can provide a workaround.
+ See Bug 2391
+ HS 2019-04-18 */
+
+struct stat buf;
+
+if (0 == (fd < 0 ? stat(name, &buf) : fstat(fd, &buf)))
+{
+ if (buf.st_uid == owner && buf.st_gid == group) return 0;
+ log_write(0, LOG_MAIN|LOG_PANIC, "Wrong ownership on %s", name);
+}
+else log_write(0, LOG_MAIN|LOG_PANIC, "Stat failed on %s: %s", name, strerror(errno));
+
+#endif
+errno = saved_errno;
+return -1;
+}
+
+
+/*************************************************
+* Extract port from host address *
+*************************************************/
+
+/* Called to extract the port from the values given to -oMa and -oMi.
+It also checks the syntax of the address, and terminates it before the
+port data when a port is extracted.
+
+Argument:
+ address the address, with possible port on the end
+
+Returns: the port, or zero if there isn't one
+ bombs out on a syntax error
+*/
+
+static int
+check_port(uschar *address)
+{
+int port = host_address_extract_port(address);
+if (string_is_ip_address(address, NULL) == 0)
+ exim_fail("exim abandoned: \"%s\" is not an IP address\n", address);
+return port;
+}
+
+
+
+/*************************************************
+* Test/verify an address *
+*************************************************/
+
+/* This function is called by the -bv and -bt code. It extracts a working
+address from a full RFC 822 address. This isn't really necessary per se, but it
+has the effect of collapsing source routes.
+
+Arguments:
+ s the address string
+ flags flag bits for verify_address()
+ exit_value to be set for failures
+
+Returns: nothing
+*/
+
+static void
+test_address(uschar *s, int flags, int *exit_value)
+{
+int start, end, domain;
+uschar *parse_error = NULL;
+uschar *address = parse_extract_address(s, &parse_error, &start, &end, &domain,
+ FALSE);
+if (!address)
+ {
+ fprintf(stdout, "syntax error: %s\n", parse_error);
+ *exit_value = 2;
+ }
+else
+ {
+ int rc = verify_address(deliver_make_addr(address,TRUE), stdout, flags, -1,
+ -1, -1, NULL, NULL, NULL);
+ if (rc == FAIL) *exit_value = 2;
+ else if (rc == DEFER && *exit_value == 0) *exit_value = 1;
+ }
+}
+
+
+
+/*************************************************
+* Show supported features *
+*************************************************/
+
+static void
+show_string(BOOL is_stdout, gstring * g)
+{
+const uschar * s = string_from_gstring(g);
+if (s)
+ if (is_stdout) fputs(CCS s, stdout);
+ else debug_printf("%s", s);
+}
+
+
+static gstring *
+show_db_version(gstring * g)
+{
+#ifdef DB_VERSION_STRING
+DEBUG(D_any)
+ {
+ g = string_fmt_append(g, "Library version: BDB: Compile: %s\n", DB_VERSION_STRING);
+ g = string_fmt_append(g, " Runtime: %s\n",
+ db_version(NULL, NULL, NULL));
+ }
+else
+ g = string_fmt_append(g, "Berkeley DB: %s\n", DB_VERSION_STRING);
+
+#elif defined(BTREEVERSION) && defined(HASHVERSION)
+# ifdef USE_DB
+ g = string_cat(g, US"Probably Berkeley DB version 1.8x (native mode)\n");
+# else
+ g = string_cat(g, US"Probably Berkeley DB version 1.8x (compatibility mode)\n");
+# endif
+
+#elif defined(_DBM_RDONLY) || defined(dbm_dirfno)
+g = string_cat(g, US"Probably ndbm\n");
+#elif defined(USE_TDB)
+g = string_cat(g, US"Using tdb\n");
+#else
+# ifdef USE_GDBM
+ g = string_cat(g, US"Probably GDBM (native mode)\n");
+# else
+ g = string_cat(g, US"Probably GDBM (compatibility mode)\n");
+# endif
+#endif
+return g;
+}
+
+
+/* This function is called for -bV/--version and for -d to output the optional
+features of the current Exim binary.
+
+Arguments: BOOL, true for stdout else debug channel
+Returns: nothing
+*/
+
+static void
+show_whats_supported(BOOL is_stdout)
+{
+rmark reset_point = store_mark();
+gstring * g = NULL;
+
+DEBUG(D_any) {} else g = show_db_version(g);
+
+g = string_cat(g, US"Support for:");
+#ifdef SUPPORT_CRYPTEQ
+ g = string_cat(g, US" crypteq");
+#endif
+#if HAVE_ICONV
+ g = string_cat(g, US" iconv()");
+#endif
+#if HAVE_IPV6
+ g = string_cat(g, US" IPv6");
+#endif
+#ifdef HAVE_SETCLASSRESOURCES
+ g = string_cat(g, US" use_setclassresources");
+#endif
+#ifdef SUPPORT_PAM
+ g = string_cat(g, US" PAM");
+#endif
+#ifdef EXIM_PERL
+ g = string_cat(g, US" Perl");
+#endif
+#ifdef EXPAND_DLFUNC
+ g = string_cat(g, US" Expand_dlfunc");
+#endif
+#ifdef USE_TCP_WRAPPERS
+ g = string_cat(g, US" TCPwrappers");
+#endif
+#ifdef USE_GNUTLS
+ g = string_cat(g, US" GnuTLS");
+#endif
+#ifdef USE_OPENSSL
+ g = string_cat(g, US" OpenSSL");
+#endif
+#ifndef DISABLE_TLS_RESUME
+ g = string_cat(g, US" TLS_resume");
+#endif
+#ifdef SUPPORT_TRANSLATE_IP_ADDRESS
+ g = string_cat(g, US" translate_ip_address");
+#endif
+#ifdef SUPPORT_MOVE_FROZEN_MESSAGES
+ g = string_cat(g, US" move_frozen_messages");
+#endif
+#ifdef WITH_CONTENT_SCAN
+ g = string_cat(g, US" Content_Scanning");
+#endif
+#ifdef SUPPORT_DANE
+ g = string_cat(g, US" DANE");
+#endif
+#ifndef DISABLE_DKIM
+ g = string_cat(g, US" DKIM");
+#endif
+#ifdef SUPPORT_DMARC
+ g = string_cat(g, US" DMARC");
+#endif
+#ifndef DISABLE_DNSSEC
+ g = string_cat(g, US" DNSSEC");
+#endif
+#ifndef DISABLE_EVENT
+ g = string_cat(g, US" Event");
+#endif
+#ifdef SUPPORT_I18N
+ g = string_cat(g, US" I18N");
+#endif
+#ifndef DISABLE_OCSP
+ g = string_cat(g, US" OCSP");
+#endif
+#ifndef DISABLE_PIPE_CONNECT
+ g = string_cat(g, US" PIPECONNECT");
+#endif
+#ifndef DISABLE_PRDR
+ g = string_cat(g, US" PRDR");
+#endif
+#ifdef SUPPORT_PROXY
+ g = string_cat(g, US" PROXY");
+#endif
+#ifndef DISABLE_QUEUE_RAMP
+ g = string_cat(g, US" Queue_Ramp");
+#endif
+#ifdef SUPPORT_SOCKS
+ g = string_cat(g, US" SOCKS");
+#endif
+#ifdef SUPPORT_SPF
+ g = string_cat(g, US" SPF");
+#endif
+#if defined(SUPPORT_SRS)
+ g = string_cat(g, US" SRS");
+#endif
+#ifdef TCP_FASTOPEN
+ tcp_init();
+ if (f.tcp_fastopen_ok) g = string_cat(g, US" TCP_Fast_Open");
+#endif
+#ifdef EXPERIMENTAL_ARC
+ g = string_cat(g, US" Experimental_ARC");
+#endif
+#ifdef EXPERIMENTAL_BRIGHTMAIL
+ g = string_cat(g, US" Experimental_Brightmail");
+#endif
+#ifdef EXPERIMENTAL_DCC
+ g = string_cat(g, US" Experimental_DCC");
+#endif
+#ifdef EXPERIMENTAL_DSN_INFO
+ g = string_cat(g, US" Experimental_DSN_info");
+#endif
+#ifdef EXPERIMENTAL_ESMTP_LIMITS
+ g = string_cat(g, US" Experimental_ESMTP_Limits");
+#endif
+#ifdef EXPERIMENTAL_QUEUEFILE
+ g = string_cat(g, US" Experimental_QUEUEFILE");
+#endif
+g = string_cat(g, US"\n");
+
+g = string_cat(g, US"Lookups (built-in):");
+#if defined(LOOKUP_LSEARCH) && LOOKUP_LSEARCH!=2
+ g = string_cat(g, US" lsearch wildlsearch nwildlsearch iplsearch");
+#endif
+#if defined(LOOKUP_CDB) && LOOKUP_CDB!=2
+ g = string_cat(g, US" cdb");
+#endif
+#if defined(LOOKUP_DBM) && LOOKUP_DBM!=2
+ g = string_cat(g, US" dbm dbmjz dbmnz");
+#endif
+#if defined(LOOKUP_DNSDB) && LOOKUP_DNSDB!=2
+ g = string_cat(g, US" dnsdb");
+#endif
+#if defined(LOOKUP_DSEARCH) && LOOKUP_DSEARCH!=2
+ g = string_cat(g, US" dsearch");
+#endif
+#if defined(LOOKUP_IBASE) && LOOKUP_IBASE!=2
+ g = string_cat(g, US" ibase");
+#endif
+#if defined(LOOKUP_JSON) && LOOKUP_JSON!=2
+ g = string_cat(g, US" json");
+#endif
+#if defined(LOOKUP_LDAP) && LOOKUP_LDAP!=2
+ g = string_cat(g, US" ldap ldapdn ldapm");
+#endif
+#ifdef LOOKUP_LMDB
+ g = string_cat(g, US" lmdb");
+#endif
+#if defined(LOOKUP_MYSQL) && LOOKUP_MYSQL!=2
+ g = string_cat(g, US" mysql");
+#endif
+#if defined(LOOKUP_NIS) && LOOKUP_NIS!=2
+ g = string_cat(g, US" nis nis0");
+#endif
+#if defined(LOOKUP_NISPLUS) && LOOKUP_NISPLUS!=2
+ g = string_cat(g, US" nisplus");
+#endif
+#if defined(LOOKUP_ORACLE) && LOOKUP_ORACLE!=2
+ g = string_cat(g, US" oracle");
+#endif
+#if defined(LOOKUP_PASSWD) && LOOKUP_PASSWD!=2
+ g = string_cat(g, US" passwd");
+#endif
+#if defined(LOOKUP_PGSQL) && LOOKUP_PGSQL!=2
+ g = string_cat(g, US" pgsql");
+#endif
+#if defined(LOOKUP_REDIS) && LOOKUP_REDIS!=2
+ g = string_cat(g, US" redis");
+#endif
+#if defined(LOOKUP_SQLITE) && LOOKUP_SQLITE!=2
+ g = string_cat(g, US" sqlite");
+#endif
+#if defined(LOOKUP_TESTDB) && LOOKUP_TESTDB!=2
+ g = string_cat(g, US" testdb");
+#endif
+#if defined(LOOKUP_WHOSON) && LOOKUP_WHOSON!=2
+ g = string_cat(g, US" whoson");
+#endif
+g = string_cat(g, US"\n");
+
+g = auth_show_supported(g);
+g = route_show_supported(g);
+g = transport_show_supported(g);
+
+#ifdef WITH_CONTENT_SCAN
+g = malware_show_supported(g);
+#endif
+show_string(is_stdout, g); g = NULL;
+
+if (fixed_never_users[0] > 0)
+ {
+ int i;
+ g = string_cat(g, US"Fixed never_users: ");
+ for (i = 1; i <= (int)fixed_never_users[0] - 1; i++)
+ string_fmt_append(g, "%u:", (unsigned)fixed_never_users[i]);
+ g = string_fmt_append(g, "%u\n", (unsigned)fixed_never_users[i]);
+ }
+
+g = string_fmt_append(g, "Configure owner: %d:%d\n", config_uid, config_gid);
+
+g = string_fmt_append(g, "Size of off_t: " SIZE_T_FMT "\n", sizeof(off_t));
+
+/* Everything else is details which are only worth reporting when debugging.
+Perhaps the tls_version_report should move into this too. */
+DEBUG(D_any)
+ {
+
+/* clang defines __GNUC__ (at least, for me) so test for it first */
+#if defined(__clang__)
+ g = string_fmt_append(g, "Compiler: CLang [%s]\n", __clang_version__);
+#elif defined(__GNUC__)
+ g = string_fmt_append(g, "Compiler: GCC [%s]\n",
+# ifdef __VERSION__
+ __VERSION__
+# else
+ "? unknown version ?"
+# endif
+ );
+#else
+ g = string_cat(g, US"Compiler: <unknown>\n");
+#endif
+
+#if defined(__GLIBC__) && !defined(__UCLIBC__)
+ g = string_fmt_append(g, "Library version: Glibc: Compile: %d.%d\n",
+ __GLIBC__, __GLIBC_MINOR__);
+ if (__GLIBC_PREREQ(2, 1))
+ g = string_fmt_append(g, " Runtime: %s\n",
+ gnu_get_libc_version());
+#endif
+
+g = show_db_version(g);
+
+#ifndef DISABLE_TLS
+ g = tls_version_report(g);
+#endif
+#ifdef SUPPORT_I18N
+ g = utf8_version_report(g);
+#endif
+#ifdef SUPPORT_DMARC
+ g = dmarc_version_report(g);
+#endif
+#ifdef SUPPORT_SPF
+ g = spf_lib_version_report(g);
+#endif
+
+show_string(is_stdout, g);
+g = NULL;
+
+for (auth_info * authi = auths_available; *authi->driver_name != '\0'; ++authi)
+ if (authi->version_report)
+ g = (*authi->version_report)(g);
+
+ /* 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)
+ {
+ uschar buf[24];
+ pcre2_config(PCRE2_CONFIG_VERSION, buf);
+ g = string_fmt_append(g, "Library version: PCRE2: Compile: %d.%d%s\n"
+ " Runtime: %s\n",
+ PCRE2_MAJOR, PCRE2_MINOR,
+ EXPAND_AND_QUOTE(PCRE2_PRERELEASE) "",
+ buf);
+ }
+#undef QUOTE
+#undef EXPAND_AND_QUOTE
+
+show_string(is_stdout, g);
+g = NULL;
+
+init_lookup_list();
+for (int i = 0; i < lookup_list_count; i++)
+ if (lookup_list[i]->version_report)
+ g = lookup_list[i]->version_report(g);
+show_string(is_stdout, g);
+g = NULL;
+
+#ifdef WHITELIST_D_MACROS
+ g = string_fmt_append(g, "WHITELIST_D_MACROS: \"%s\"\n", WHITELIST_D_MACROS);
+#else
+ g = string_cat(g, US"WHITELIST_D_MACROS unset\n");
+#endif
+#ifdef TRUSTED_CONFIG_LIST
+ g = string_fmt_append(g, "TRUSTED_CONFIG_LIST: \"%s\"\n", TRUSTED_CONFIG_LIST);
+#else
+ g = string_cat(g, US"TRUSTED_CONFIG_LIST unset\n");
+#endif
+ }
+
+show_string(is_stdout, g);
+store_reset(reset_point);
+}
+
+
+/*************************************************
+* Show auxiliary information about Exim *
+*************************************************/
+
+static void
+show_exim_information(enum commandline_info request, FILE *stream)
+{
+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 list of known dscp value keywords\n"
+" exim -bI:sieve list of supported sieve extensions\n"
+);
+ return;
+ case CMDINFO_SIEVE:
+ for (const uschar ** pp = exim_sieve_extension_list; *pp; ++pp)
+ fprintf(stream, "%s\n", *pp);
+ return;
+ case CMDINFO_DSCP:
+ dscp_list_to_stream(stream);
+ return;
+ }
+}
+
+
+/*************************************************
+* Quote a local part *
+*************************************************/
+
+/* This function is used when a sender address or a From: or Sender: header
+line is being created from the caller's login, or from an authenticated_id. It
+applies appropriate quoting rules for a local part.
+
+Argument: the local part
+Returns: the local part, quoted if necessary
+*/
+
+uschar *
+local_part_quote(uschar *lpart)
+{
+BOOL needs_quote = FALSE;
+gstring * g;
+
+for (uschar * t = lpart; !needs_quote && *t != 0; t++)
+ {
+ needs_quote = !isalnum(*t) && strchr("!#$%&'*+-/=?^_`{|}~", *t) == NULL &&
+ (*t != '.' || t == lpart || t[1] == 0);
+ }
+
+if (!needs_quote) return lpart;
+
+g = string_catn(NULL, US"\"", 1);
+
+for (;;)
+ {
+ uschar *nq = US Ustrpbrk(lpart, "\\\"");
+ if (nq == NULL)
+ {
+ g = string_cat(g, lpart);
+ break;
+ }
+ g = string_catn(g, lpart, nq - lpart);
+ g = string_catn(g, US"\\", 1);
+ g = string_catn(g, nq, 1);
+ lpart = nq + 1;
+ }
+
+g = string_catn(g, US"\"", 1);
+return string_from_gstring(g);
+}
+
+
+
+#ifdef USE_READLINE
+/*************************************************
+* Load readline() functions *
+*************************************************/
+
+/* This function is called from testing executions that read data from stdin,
+but only when running as the calling user. Currently, only -be does this. The
+function loads the readline() function library and passes back the functions.
+On some systems, it needs the curses library, so load that too, but try without
+it if loading fails. All this functionality has to be requested at build time.
+
+Arguments:
+ fn_readline_ptr pointer to where to put the readline pointer
+ fn_addhist_ptr pointer to where to put the addhistory function
+
+Returns: the dlopen handle or NULL on failure
+*/
+
+static void *
+set_readline(char * (**fn_readline_ptr)(const char *),
+ void (**fn_addhist_ptr)(const char *))
+{
+void *dlhandle;
+void *dlhandle_curses = dlopen("libcurses." DYNLIB_FN_EXT, RTLD_GLOBAL|RTLD_LAZY);
+
+dlhandle = dlopen("libreadline." DYNLIB_FN_EXT, RTLD_GLOBAL|RTLD_NOW);
+if (dlhandle_curses) dlclose(dlhandle_curses);
+
+if (dlhandle)
+ {
+ /* 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
+ DEBUG(D_any) debug_printf("failed to load readline: %s\n", dlerror());
+
+return dlhandle;
+}
+#endif
+
+
+
+/*************************************************
+* Get a line from stdin for testing things *
+*************************************************/
+
+/* This function is called when running tests that can take a number of lines
+of input (for example, -be and -bt). It handles continuations and trailing
+spaces. And prompting and a blank line output on eof. If readline() is in use,
+the arguments are non-NULL and provide the relevant functions.
+
+Arguments:
+ fn_readline readline function or NULL
+ fn_addhist addhist function or NULL
+
+Returns: pointer to dynamic memory, or NULL at end of file
+*/
+
+static uschar *
+get_stdinput(char *(*fn_readline)(const char *), void(*fn_addhist)(const char *))
+{
+gstring * g = NULL;
+BOOL had_input = FALSE;
+
+if (!fn_readline) { printf("> "); fflush(stdout); }
+
+for (int i = 0;; i++)
+ {
+ uschar buffer[1024];
+ uschar * p, * ss;
+
+#ifdef USE_READLINE
+ char *readline_line = NULL;
+ if (fn_readline)
+ {
+ if (!(readline_line = fn_readline((i > 0)? "":"> "))) break;
+ if (*readline_line && fn_addhist) fn_addhist(readline_line);
+ p = US readline_line;
+ }
+ else
+#endif
+
+ /* readline() not in use */
+
+ {
+ if (Ufgets(buffer, sizeof(buffer), stdin) == NULL) break; /*EOF*/
+ p = buffer;
+ }
+
+ /* Handle the line */
+
+ had_input = TRUE;
+ ss = p + Ustrlen(p);
+ while (ss > p && isspace(ss[-1])) ss--; /* strip trailing newline (and spaces) */
+
+ if (i > 0)
+ while (p < ss && isspace(*p)) p++; /* strip leading space after cont */
+
+ g = string_catn(g, p, ss - p);
+
+#ifdef USE_READLINE
+ if (fn_readline) free(readline_line);
+#endif
+
+ /* g can only be NULL if ss==p */
+ if (ss == p || g->s[g->ptr-1] != '\\') /* not continuation; done */
+ break;
+
+ --g->ptr; /* drop the \ */
+ }
+
+if (had_input) return g ? string_from_gstring(g) : US"";
+printf("\n");
+return NULL;
+}
+
+
+
+/*************************************************
+* Output usage information for the program *
+*************************************************/
+
+/* This function is called when there are no recipients
+ or a specific --help argument was added.
+
+Arguments:
+ progname information on what name we were called by
+
+Returns: DOES NOT RETURN
+*/
+
+static void
+exim_usage(uschar *progname)
+{
+
+/* Handle specific program invocation variants */
+if (Ustrcmp(progname, US"-mailq") == 0)
+ exim_fail(
+ "mailq - list the contents of the mail queue\n\n"
+ "For a list of options, see the Exim documentation.\n");
+
+/* Generic usage - we output this whatever happens */
+exim_fail(
+ "Exim is a Mail Transfer Agent. It is normally called by Mail User Agents,\n"
+ "not directly from a shell command line. Options and/or arguments control\n"
+ "what it does when called. For a list of options, see the Exim documentation.\n");
+}
+
+
+
+/*************************************************
+* Validate that the macros given are okay *
+*************************************************/
+
+/* Typically, Exim will drop privileges if macros are supplied. In some
+cases, we want to not do so.
+
+Arguments: opt_D_used - true if the commandline had a "-D" option
+Returns: true if trusted, false otherwise
+*/
+
+static BOOL
+macros_trusted(BOOL opt_D_used)
+{
+#ifdef WHITELIST_D_MACROS
+uschar *whitelisted, *end, *p, **whites;
+int white_count, i, n;
+size_t len;
+BOOL prev_char_item, found;
+#endif
+
+if (!opt_D_used)
+ return TRUE;
+#ifndef WHITELIST_D_MACROS
+return FALSE;
+#else
+
+/* We only trust -D overrides for some invoking users:
+root, the exim run-time user, the optional config owner user.
+I don't know why config-owner would be needed, but since they can own the
+config files anyway, there's no security risk to letting them override -D. */
+if ( ! ((real_uid == root_uid)
+ || (real_uid == exim_uid)
+#ifdef CONFIGURE_OWNER
+ || (real_uid == config_uid)
+#endif
+ ))
+ {
+ debug_printf("macros_trusted rejecting macros for uid %d\n", (int) real_uid);
+ return FALSE;
+ }
+
+/* Get a list of macros which are whitelisted */
+whitelisted = string_copy_perm(US WHITELIST_D_MACROS, FALSE);
+prev_char_item = FALSE;
+white_count = 0;
+for (p = whitelisted; *p != '\0'; ++p)
+ {
+ if (*p == ':' || isspace(*p))
+ {
+ *p = '\0';
+ if (prev_char_item)
+ ++white_count;
+ prev_char_item = FALSE;
+ continue;
+ }
+ if (!prev_char_item)
+ prev_char_item = TRUE;
+ }
+end = p;
+if (prev_char_item)
+ ++white_count;
+if (!white_count)
+ return FALSE;
+whites = store_malloc(sizeof(uschar *) * (white_count+1));
+for (p = whitelisted, i = 0; (p != end) && (i < white_count); ++p)
+ {
+ if (*p != '\0')
+ {
+ whites[i++] = p;
+ if (i == white_count)
+ break;
+ while (*p != '\0' && p < end)
+ ++p;
+ }
+ }
+whites[i] = NULL;
+
+/* The list of commandline macros should be very short.
+Accept the N*M complexity. */
+for (macro_item * m = macros_user; m; m = m->next) if (m->command_line)
+ {
+ found = FALSE;
+ for (uschar ** w = whites; *w; ++w)
+ if (Ustrcmp(*w, m->name) == 0)
+ {
+ found = TRUE;
+ break;
+ }
+ if (!found)
+ return FALSE;
+ if (!m->replacement)
+ continue;
+ if ((len = m->replen) == 0)
+ continue;
+ if (!regex_match(regex_whitelisted_macro, m->replacement, len, NULL))
+ return FALSE;
+ }
+DEBUG(D_any) debug_printf("macros_trusted overridden to true by whitelisting\n");
+return TRUE;
+#endif
+}
+
+
+/*************************************************
+* Expansion testing *
+*************************************************/
+
+/* Expand and print one item, doing macro-processing.
+
+Arguments:
+ item line for expansion
+*/
+
+static void
+expansion_test_line(const uschar * line)
+{
+int len;
+BOOL dummy_macexp;
+uschar * s;
+
+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 ((s = expand_string(big_buffer))) printf("%s\n", CS s);
+ else printf("Failed: %s\n", expand_string_message);
+}
+
+
+
+/*************************************************
+* Entry point and high-level code *
+*************************************************/
+
+/* Entry point for the Exim mailer. Analyse the arguments and arrange to take
+the appropriate action. All the necessary functions are present in the one
+binary. I originally thought one should split it up, but it turns out that so
+much of the apparatus is needed in each chunk that one might as well just have
+it all available all the time, which then makes the coding easier as well.
+
+Arguments:
+ argc count of entries in argv
+ argv argument strings, with argv[0] being the program name
+
+Returns: EXIT_SUCCESS if terminated successfully
+ EXIT_FAILURE otherwise, except when a message has been sent
+ to the sender, and -oee was given
+*/
+
+int
+main(int argc, char **cargv)
+{
+uschar **argv = USS cargv;
+int arg_receive_timeout = -1;
+int arg_smtp_receive_timeout = -1;
+int arg_error_handling = error_handling;
+int filter_sfd = -1;
+int filter_ufd = -1;
+int group_count;
+int i, rv;
+int list_queue_option = 0;
+int msg_action = 0;
+int msg_action_arg = -1;
+int namelen = argv[0] ? Ustrlen(argv[0]) : 0;
+int queue_only_reason = 0;
+#ifdef EXIM_PERL
+int perl_start_option = 0;
+#endif
+int recipients_arg = argc;
+int sender_address_domain = 0;
+int test_retry_arg = -1;
+int test_rewrite_arg = -1;
+gid_t original_egid;
+BOOL arg_queue_only = FALSE;
+BOOL bi_option = FALSE;
+BOOL checking = 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 list_queue = FALSE;
+BOOL list_options = FALSE;
+BOOL list_config = FALSE;
+BOOL local_queue_only;
+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 session_local_queue_only;
+BOOL unprivileged;
+BOOL removed_privilege = FALSE;
+BOOL usage_wanted = FALSE;
+BOOL verify_address_mode = FALSE;
+BOOL verify_as_sender = FALSE;
+BOOL rcpt_verify_quota = FALSE;
+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;
+const uschar *ftest_domain = NULL;
+const uschar *ftest_localpart = NULL;
+const uschar *ftest_prefix = NULL;
+const uschar *ftest_suffix = NULL;
+uschar *log_oneline = NULL;
+uschar *malware_test_file = NULL;
+uschar *real_sender_address;
+uschar *originator_home = US"/";
+size_t sz;
+
+struct passwd *pw;
+struct stat statbuf;
+pid_t passed_qr_pid = (pid_t)0;
+int passed_qr_pipe = -1;
+gid_t group_list[EXIM_GROUPLIST_SIZE];
+
+/* 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" };
+
+/* Need to define this in case we need to change the environment in order
+to get rid of a bogus time zone. We have to make it char rather than uschar
+because some OS define it in /usr/include/unistd.h. */
+
+extern char **environ;
+
+#ifdef MEASURE_TIMING
+(void)gettimeofday(&timestamp_startup, NULL);
+#endif
+
+store_init(); /* Initialise the memory allocation susbsystem */
+pcre_init(); /* Set up memory handling for pcre */
+
+/* If the Exim user and/or group and/or the configuration file owner/group were
+defined by ref:name at build time, we must now find the actual uid/gid values.
+This is a feature to make the lives of binary distributors easier. */
+
+#ifdef EXIM_USERNAME
+if (route_finduser(US EXIM_USERNAME, &pw, &exim_uid))
+ {
+ if (exim_uid == 0)
+ exim_fail("exim: refusing to run with uid 0 for \"%s\"\n", EXIM_USERNAME);
+
+ /* 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
+ exim_fail(
+ "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");
+#endif
+ }
+else
+ exim_fail("exim: failed to find uid for user name \"%s\"\n", EXIM_USERNAME);
+#endif
+
+#ifdef EXIM_GROUPNAME
+if (!route_findgroup(US EXIM_GROUPNAME, &exim_gid))
+ exim_fail("exim: failed to find gid for group name \"%s\"\n", EXIM_GROUPNAME);
+#endif
+
+#ifdef CONFIGURE_OWNERNAME
+if (!route_finduser(US CONFIGURE_OWNERNAME, NULL, &config_uid))
+ exim_fail("exim: failed to find uid for user name \"%s\"\n",
+ CONFIGURE_OWNERNAME);
+#endif
+
+/* We default the system_filter_user to be the Exim run-time user, as a
+sane non-root value. */
+system_filter_uid = exim_uid;
+
+#ifdef CONFIGURE_GROUPNAME
+if (!route_findgroup(US CONFIGURE_GROUPNAME, &config_gid))
+ exim_fail("exim: failed to find gid for group name \"%s\"\n",
+ CONFIGURE_GROUPNAME);
+#endif
+
+/* In the Cygwin environment, some initialization used to need doing.
+It was fudged in by means of this macro; now no longer but we'll leave
+it in case of others. */
+
+#ifdef OS_INIT
+OS_INIT
+#endif
+
+/* Check a field which is patched when we are running Exim within its
+testing harness; do a fast initial check, and then the whole thing. */
+
+f.running_in_test_harness =
+ *running_status == '<' && Ustrcmp(running_status, "<<<testing>>>") == 0;
+if (f.running_in_test_harness)
+ debug_store = TRUE;
+
+/* Protect against abusive argv[0] */
+if (!argv[0] || !argc) exim_fail("exim: executable name required\n");
+exim_str_fail_toolong(argv[0], PATH_MAX, "argv[0]");
+
+/* The C standard says that the equivalent of setlocale(LC_ALL, "C") is obeyed
+at the start of a program; however, it seems that some environments do not
+follow this. A "strange" locale can affect the formatting of timestamps, so we
+make quite sure. */
+
+setlocale(LC_ALL, "C");
+
+/* Get the offset between CLOCK_MONOTONIC/CLOCK_BOOTTIME and wallclock */
+
+#ifdef _POSIX_MONOTONIC_CLOCK
+exim_clock_init();
+#endif
+
+/* Set up the default handler for timing using alarm(). */
+
+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. */
+
+if (!(log_buffer = US malloc(LOG_BUFFER_SIZE)))
+ exim_fail("exim: failed to get store for log buffer\n");
+
+/* Initialize the default log options. */
+
+bits_set(log_selector, log_selector_size, log_default);
+
+/* Set log_stderr to stderr, provided that stderr exists. This gets reset to
+NULL when the daemon is run and the file is closed. We have to use this
+indirection, because some systems don't allow writing to the variable "stderr".
+*/
+
+if (fstat(fileno(stderr), &statbuf) >= 0) log_stderr = stderr;
+
+/* Ensure there is a big buffer for temporary use in several places. It is put
+in malloc store so that it can be freed for enlargement if necessary. */
+
+big_buffer = store_malloc(big_buffer_size);
+
+/* Set up the handler for the data request signal, and set the initial
+descriptive text. */
+
+process_info = store_get(PROCESS_INFO_SIZE, GET_TAINTED);
+set_process_info("initializing");
+os_restarting_signal(SIGUSR1, usr1_handler); /* exiwhat */
+#ifdef SA_SIGINFO
+ {
+ struct sigaction act = { .sa_sigaction = segv_handler, .sa_flags = SA_RESETHAND | SA_SIGINFO };
+ sigaction(SIGSEGV, &act, NULL);
+ }
+#else
+signal(SIGSEGV, segv_handler); /* log faults */
+#endif
+
+/* If running in a dockerized environment, the TERM signal is only
+delegated to the PID 1 if we request it by setting an signal handler */
+if (getpid() == 1) signal(SIGTERM, term_handler);
+
+/* SIGHUP is used to get the daemon to reconfigure. It gets set as appropriate
+in the daemon code. For the rest of Exim's uses, we ignore it. */
+
+signal(SIGHUP, SIG_IGN);
+
+/* We don't want to die on pipe errors as the code is written to handle
+the write error instead. */
+
+signal(SIGPIPE, SIG_IGN);
+
+/* Under some circumstance on some OS, Exim can get called with SIGCHLD
+set to SIG_IGN. This causes subprocesses that complete before the parent
+process waits for them not to hang around, so when Exim calls wait(), nothing
+is there. The wait() code has been made robust against this, but let's ensure
+that SIGCHLD is set to SIG_DFL, because it's tidier to wait and get a process
+ending status. We use sigaction rather than plain signal() on those OS where
+SA_NOCLDWAIT exists, because we want to be sure it is turned off. (There was a
+problem on AIX with this.) */
+
+#ifdef SA_NOCLDWAIT
+ {
+ struct sigaction act;
+ act.sa_handler = SIG_DFL;
+ sigemptyset(&(act.sa_mask));
+ act.sa_flags = 0;
+ sigaction(SIGCHLD, &act, NULL);
+ }
+#else
+signal(SIGCHLD, SIG_DFL);
+#endif
+
+/* Save the arguments for use if we re-exec exim as a daemon after receiving
+SIGHUP. */
+
+sighup_argv = argv;
+
+/* Set up the version number. Set up the leading 'E' for the external form of
+message ids, set the pointer to the internal form, and initialize it to
+indicate no message being processed. */
+
+version_init();
+message_id_option[0] = '-';
+message_id_external = message_id_option + 1;
+message_id_external[0] = 'E';
+message_id = message_id_external + 1;
+message_id[0] = 0;
+
+/* Set the umask to zero so that any files Exim creates using open() are
+created with the modes that it specifies. NOTE: Files created with fopen() have
+a problem, which was not recognized till rather late (February 2006). With this
+umask, such files will be world writeable. (They are all content scanning files
+in the spool directory, which isn't world-accessible, so this is not a
+disaster, but it's untidy.) I don't want to change this overall setting,
+however, because it will interact badly with the open() calls. Instead, there's
+now a function called modefopen() that fiddles with the umask while calling
+fopen(). */
+
+(void)umask(0);
+
+/* Precompile the regular expression for matching a message id. Keep this in
+step with the code that generates ids in the accept.c module. We need to do
+this here, because the -M options check their arguments for syntactic validity
+using mac_ismsgid, which uses this. */
+
+regex_ismsgid =
+ regex_must_compile(US"^(?:[^\\W_]{6}-){2}[^\\W_]{2}$", FALSE, TRUE);
+
+/* Precompile the regular expression that is used for matching an SMTP error
+code, possibly extended, at the start of an error message. Note that the
+terminating whitespace character is included. */
+
+regex_smtp_code =
+ regex_must_compile(US"^\\d\\d\\d\\s(?:\\d\\.\\d\\d?\\d?\\.\\d\\d?\\d?\\s)?",
+ FALSE, TRUE);
+
+#ifdef WHITELIST_D_MACROS
+/* Precompile the regular expression used to filter the content of macros
+given to -D for permissibility. */
+
+regex_whitelisted_macro =
+ regex_must_compile(US"^[A-Za-z0-9_/.-]*$", FALSE, TRUE);
+#endif
+
+for (i = 0; i < REGEX_VARS; i++) regex_vars[i] = NULL;
+
+/* If the program is called as "mailq" treat it as equivalent to "exim -bp";
+this seems to be a generally accepted convention, since one finds symbolic
+links called "mailq" in standard OS configurations. */
+
+if ((namelen == 5 && Ustrcmp(argv[0], "mailq") == 0) ||
+ (namelen > 5 && Ustrncmp(argv[0] + namelen - 6, "/mailq", 6) == 0))
+ {
+ list_queue = TRUE;
+ receiving_message = FALSE;
+ called_as = US"-mailq";
+ }
+
+/* If the program is called as "rmail" treat it as equivalent to
+"exim -i -oee", thus allowing UUCP messages to be input using non-SMTP mode,
+i.e. preventing a single dot on a line from terminating the message, and
+returning with zero return code, even in cases of error (provided an error
+message has been sent). */
+
+if ((namelen == 5 && Ustrcmp(argv[0], "rmail") == 0) ||
+ (namelen > 5 && Ustrncmp(argv[0] + namelen - 6, "/rmail", 6) == 0))
+ {
+ f.dot_ends = FALSE;
+ called_as = US"-rmail";
+ errors_sender_rc = EXIT_SUCCESS;
+ }
+
+/* If the program is called as "rsmtp" treat it as equivalent to "exim -bS";
+this is a smail convention. */
+
+if ((namelen == 5 && Ustrcmp(argv[0], "rsmtp") == 0) ||
+ (namelen > 5 && Ustrncmp(argv[0] + namelen - 6, "/rsmtp", 6) == 0))
+ {
+ smtp_input = smtp_batched_input = TRUE;
+ called_as = US"-rsmtp";
+ }
+
+/* If the program is called as "runq" treat it as equivalent to "exim -q";
+this is a smail convention. */
+
+if ((namelen == 4 && Ustrcmp(argv[0], "runq") == 0) ||
+ (namelen > 4 && Ustrncmp(argv[0] + namelen - 5, "/runq", 5) == 0))
+ {
+ queue_interval = 0;
+ receiving_message = FALSE;
+ called_as = US"-runq";
+ }
+
+/* If the program is called as "newaliases" treat it as equivalent to
+"exim -bi"; this is a sendmail convention. */
+
+if ((namelen == 10 && Ustrcmp(argv[0], "newaliases") == 0) ||
+ (namelen > 10 && Ustrncmp(argv[0] + namelen - 11, "/newaliases", 11) == 0))
+ {
+ bi_option = TRUE;
+ receiving_message = FALSE;
+ called_as = US"-newaliases";
+ }
+
+/* Save the original effective uid for a couple of uses later. It should
+normally be root, but in some esoteric environments it may not be. */
+
+original_euid = geteuid();
+original_egid = getegid();
+
+/* Get the real uid and gid. If the caller is root, force the effective uid/gid
+to be the same as the real ones. This makes a difference only if Exim is setuid
+(or setgid) to something other than root, which could be the case in some
+special configurations. */
+
+real_uid = getuid();
+real_gid = getgid();
+
+if (real_uid == root_uid)
+ {
+ if ((rv = setgid(real_gid)))
+ exim_fail("exim: setgid(%ld) failed: %s\n",
+ (long int)real_gid, strerror(errno));
+ if ((rv = setuid(real_uid)))
+ exim_fail("exim: setuid(%ld) failed: %s\n",
+ (long int)real_uid, strerror(errno));
+ }
+
+/* If neither the original real uid nor the original euid was root, Exim is
+running in an unprivileged state. */
+
+unprivileged = (real_uid != root_uid && original_euid != root_uid);
+
+/* For most of the args-parsing we need to use permanent pool memory */
+ {
+ int old_pool = store_pool;
+ store_pool = POOL_PERM;
+
+/* Scan the program's arguments. Some can be dealt with right away; others are
+simply recorded for checking and handling afterwards. Do a high-level switch
+on the second character (the one after '-'), to save some effort. */
+
+ for (i = 1; i < argc; i++)
+ {
+ BOOL badarg = FALSE;
+ uschar * arg = argv[i];
+ uschar * argrest;
+ int switchchar;
+
+ /* An argument not starting with '-' is the start of a recipients list;
+ break out of the options-scanning loop. */
+
+ if (arg[0] != '-')
+ {
+ recipients_arg = i;
+ break;
+ }
+
+ /* An option consisting of -- terminates the options */
+
+ if (Ustrcmp(arg, "--") == 0)
+ {
+ recipients_arg = i + 1;
+ break;
+ }
+
+ /* Handle flagged options */
+
+ switchchar = arg[1];
+ argrest = arg+2;
+
+ /* Make all -ex options synonymous with -oex arguments, since that
+ is assumed by various callers. Also make -qR options synonymous with -R
+ options, as that seems to be required as well. Allow for -qqR too, and
+ the same for -S options. */
+
+ if (Ustrncmp(arg+1, "oe", 2) == 0 ||
+ Ustrncmp(arg+1, "qR", 2) == 0 ||
+ Ustrncmp(arg+1, "qS", 2) == 0)
+ {
+ switchchar = arg[2];
+ argrest++;
+ }
+ else if (Ustrncmp(arg+1, "qqR", 3) == 0 || Ustrncmp(arg+1, "qqS", 3) == 0)
+ {
+ switchchar = arg[3];
+ argrest += 2;
+ f.queue_2stage = TRUE;
+ }
+
+ /* Make -r synonymous with -f, since it is a documented alias */
+
+ else if (arg[1] == 'r') switchchar = 'f';
+
+ /* Make -ov synonymous with -v */
+
+ else if (Ustrcmp(arg, "-ov") == 0)
+ {
+ switchchar = 'v';
+ argrest++;
+ }
+
+ /* deal with --option_aliases */
+ else if (switchchar == '-')
+ {
+ if (Ustrcmp(argrest, "help") == 0)
+ {
+ usage_wanted = TRUE;
+ break;
+ }
+ else if (Ustrcmp(argrest, "version") == 0)
+ {
+ switchchar = 'b';
+ argrest = US"V";
+ }
+ }
+
+ /* High-level switch on active initial letter */
+
+ switch(switchchar)
+ {
+
+ /* sendmail uses -Ac and -Am to control which .cf file is used;
+ we ignore them. */
+ case 'A':
+ if (!*argrest) { 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;
+
+ /* -Btype is a sendmail option for 7bit/8bit setting. Exim is 8-bit clean
+ so has no need of it. */
+
+ case 'B':
+ if (!*argrest) i++; /* Skip over the type */
+ break;
+
+
+ case 'b':
+ {
+ receiving_message = FALSE; /* Reset TRUE for -bm, -bS, -bs below */
+
+ switch (*argrest++)
+ {
+ /* -bd: Run in daemon mode, awaiting SMTP connections.
+ -bdf: Ditto, but in the foreground.
+ */
+ case 'd':
+ f.daemon_listen = TRUE;
+ if (*argrest == 'f') f.background_daemon = FALSE;
+ else if (*argrest) badarg = TRUE;
+ break;
+
+ /* -be: Run in expansion test mode
+ -bem: Ditto, but read a message from a file first
+ */
+ case 'e':
+ expansion_test = checking = TRUE;
+ if (*argrest == 'm')
+ {
+ if (++i >= argc) { badarg = TRUE; break; }
+ expansion_test_message = argv[i];
+ argrest++;
+ }
+ if (*argrest) badarg = TRUE;
+ break;
+
+ /* -bF: Run system filter test */
+ case 'F':
+ filter_test |= checking = FTEST_SYSTEM;
+ if (*argrest) badarg = TRUE;
+ else if (++i < argc) filter_test_sfile = argv[i];
+ else exim_fail("exim: file name expected after %s\n", argv[i-1]);
+ break;
+
+ /* -bf: Run user filter test
+ -bfd: Set domain for filter testing
+ -bfl: Set local part for filter testing
+ -bfp: Set prefix for filter testing
+ -bfs: Set suffix for filter testing
+ */
+ case 'f':
+ if (!*argrest)
+ {
+ filter_test |= checking = FTEST_USER;
+ if (++i < argc) filter_test_ufile = argv[i];
+ else exim_fail("exim: file name expected after %s\n", argv[i-1]);
+ }
+ else
+ {
+ if (++i >= argc)
+ exim_fail("exim: string expected after %s\n", arg);
+ if (Ustrcmp(argrest, "d") == 0) ftest_domain = exim_str_fail_toolong(argv[i], EXIM_DOMAINNAME_MAX, "-bfd");
+ else if (Ustrcmp(argrest, "l") == 0) ftest_localpart = exim_str_fail_toolong(argv[i], EXIM_LOCALPART_MAX, "-bfl");
+ else if (Ustrcmp(argrest, "p") == 0) ftest_prefix = exim_str_fail_toolong(argv[i], EXIM_LOCALPART_MAX, "-bfp");
+ else if (Ustrcmp(argrest, "s") == 0) ftest_suffix = exim_str_fail_toolong(argv[i], EXIM_LOCALPART_MAX, "-bfs");
+ else badarg = TRUE;
+ }
+ break;
+
+ /* -bh: Host checking - an IP address must follow. */
+ case 'h':
+ if (!*argrest || Ustrcmp(argrest, "c") == 0)
+ {
+ if (++i >= argc) { badarg = TRUE; break; }
+ sender_host_address = string_copy_taint(
+ exim_str_fail_toolong(argv[i], EXIM_IPADDR_MAX, "-bh"),
+ GET_TAINTED);
+ host_checking = checking = f.log_testing_mode = TRUE;
+ f.host_checking_callout = *argrest == 'c';
+ message_logs = FALSE;
+ }
+ else badarg = TRUE;
+ break;
+
+ /* -bi: This option is used by sendmail to initialize *the* alias file,
+ though it has the -oA option to specify a different file. Exim has no
+ concept of *the* alias file, but since Sun's YP make script calls
+ sendmail this way, some support must be provided. */
+ case 'i':
+ if (!*argrest) bi_option = TRUE;
+ else badarg = TRUE;
+ break;
+
+ /* -bI: provide information, of the type to follow after a colon.
+ This is an Exim flag. */
+ case 'I':
+ if (Ustrlen(argrest) >= 1 && *argrest == ':')
+ {
+ uschar *p = argrest+1;
+ 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;
+ }
+ else badarg = TRUE;
+ break;
+
+ /* -bm: Accept and deliver message - the default option. Reinstate
+ receiving_message, which got turned off for all -b options.
+ -bmalware: test the filename given for malware */
+ case 'm':
+ if (!*argrest) receiving_message = TRUE;
+ else if (Ustrcmp(argrest, "alware") == 0)
+ {
+ if (++i >= argc) { badarg = TRUE; break; }
+ checking = TRUE;
+ malware_test_file = argv[i];
+ }
+ else badarg = TRUE;
+ break;
+
+ /* -bnq: For locally originating messages, do not qualify unqualified
+ addresses. In the envelope, this causes errors; in header lines they
+ just get left. */
+ case 'n':
+ if (Ustrcmp(argrest, "q") == 0)
+ {
+ f.allow_unqualified_sender = FALSE;
+ f.allow_unqualified_recipient = FALSE;
+ }
+ else badarg = TRUE;
+ break;
+
+ /* -bpxx: List the contents of the mail queue, in various forms. If
+ the option is -bpc, just a queue count is needed. Otherwise, if the
+ first letter after p is r, then order is random. */
+ case 'p':
+ if (*argrest == 'c')
+ {
+ count_queue = TRUE;
+ if (*++argrest) badarg = TRUE;
+ break;
+ }
+
+ if (*argrest == 'r')
+ {
+ list_queue_option = 8;
+ argrest++;
+ }
+ else list_queue_option = 0;
+
+ list_queue = TRUE;
+
+ /* -bp: List the contents of the mail queue, top-level only */
+
+ if (!*argrest) {}
+
+ /* -bpu: List the contents of the mail queue, top-level undelivered */
+
+ else if (Ustrcmp(argrest, "u") == 0) list_queue_option += 1;
+
+ /* -bpa: List the contents of the mail queue, including all delivered */
+
+ else if (Ustrcmp(argrest, "a") == 0) list_queue_option += 2;
+
+ /* Unknown after -bp[r] */
+
+ else badarg = TRUE;
+ break;
+
+
+ /* -bP: List the configuration variables given as the address list.
+ Force -v, so configuration errors get displayed. */
+ case 'P':
+
+ /* -bP config: we need to setup here, because later,
+ when list_options is checked, the config is read already */
+ if (*argrest)
+ badarg = TRUE;
+ else if (argv[i+1] && Ustrcmp(argv[i+1], "config") == 0)
+ {
+ list_config = TRUE;
+ readconf_save_config(version_string);
+ }
+ else
+ {
+ list_options = TRUE;
+ debug_selector |= D_v;
+ debug_file = stderr;
+ }
+ break;
+
+ /* -brt: Test retry configuration lookup */
+ case 'r':
+ if (Ustrcmp(argrest, "t") == 0)
+ {
+ checking = TRUE;
+ test_retry_arg = i + 1;
+ goto END_ARG;
+ }
+
+ /* -brw: Test rewrite configuration */
+
+ else if (Ustrcmp(argrest, "w") == 0)
+ {
+ checking = TRUE;
+ test_rewrite_arg = i + 1;
+ goto END_ARG;
+ }
+ else badarg = TRUE;
+ break;
+
+ /* -bS: Read SMTP commands on standard input, but produce no replies -
+ all errors are reported by sending messages. */
+ case 'S':
+ if (!*argrest)
+ smtp_input = smtp_batched_input = receiving_message = TRUE;
+ else badarg = TRUE;
+ break;
+
+ /* -bs: Read SMTP commands on standard input and produce SMTP replies
+ on standard output. */
+ case 's':
+ if (!*argrest) smtp_input = receiving_message = TRUE;
+ else badarg = TRUE;
+ break;
+
+ /* -bt: address testing mode */
+ case 't':
+ if (!*argrest)
+ f.address_test_mode = checking = f.log_testing_mode = TRUE;
+ else badarg = TRUE;
+ break;
+
+ /* -bv: verify addresses */
+ case 'v':
+ if (!*argrest)
+ verify_address_mode = checking = f.log_testing_mode = TRUE;
+
+ /* -bvs: verify sender addresses */
+
+ else if (Ustrcmp(argrest, "s") == 0)
+ {
+ verify_address_mode = checking = f.log_testing_mode = TRUE;
+ verify_as_sender = TRUE;
+ }
+ else badarg = TRUE;
+ break;
+
+ /* -bV: Print version string and support details */
+ case 'V':
+ if (!*argrest)
+ {
+ printf("Exim version %s #%s built %s\n", version_string,
+ version_cnumber, version_date);
+ printf("%s\n", CS version_copyright);
+ version_printed = TRUE;
+ show_whats_supported(TRUE);
+ f.log_testing_mode = TRUE;
+ }
+ else badarg = TRUE;
+ break;
+
+ /* -bw: inetd wait mode, accept a listening socket as stdin */
+ case 'w':
+ f.inetd_wait_mode = TRUE;
+ f.background_daemon = FALSE;
+ f.daemon_listen = TRUE;
+ if (*argrest)
+ if ((inetd_wait_timeout = readconf_readtime(argrest, 0, FALSE)) <= 0)
+ exim_fail("exim: bad time value %s: abandoned\n", argv[i]);
+ break;
+
+ default:
+ badarg = TRUE;
+ break;
+ }
+ break;
+ }
+
+
+ /* -C: change configuration file list; ignore if it isn't really
+ a change! Enforce a prefix check if required. */
+
+ case 'C':
+ if (!*argrest)
+ if (++i < argc) argrest = argv[i]; else { badarg = TRUE; break; }
+ if (Ustrcmp(config_main_filelist, argrest) != 0)
+ {
+ #ifdef ALT_CONFIG_PREFIX
+ int sep = 0;
+ int len = Ustrlen(ALT_CONFIG_PREFIX);
+ const uschar *list = argrest;
+ uschar *filename;
+ /* The argv is untainted, so big_buffer (also untainted) is ok to use */
+ while((filename = string_nextinlist(&list, &sep, big_buffer,
+ big_buffer_size)))
+ if ( ( Ustrlen(filename) < len
+ || Ustrncmp(filename, ALT_CONFIG_PREFIX, len) != 0
+ || Ustrstr(filename, "/../") != NULL
+ )
+ && (Ustrcmp(filename, "/dev/null") != 0 || real_uid != root_uid)
+ )
+ exim_fail("-C Permission denied\n");
+ #endif
+ if (real_uid != root_uid)
+ {
+ #ifdef TRUSTED_CONFIG_LIST
+
+ if (real_uid != exim_uid
+ #ifdef CONFIGURE_OWNER
+ && real_uid != config_uid
+ #endif
+ )
+ f.trusted_config = FALSE;
+ else
+ {
+ FILE *trust_list = Ufopen(TRUSTED_CONFIG_LIST, "rb");
+ if (trust_list)
+ {
+ struct stat statbuf;
+
+ if (fstat(fileno(trust_list), &statbuf) != 0 ||
+ (statbuf.st_uid != root_uid /* owner not root */
+ #ifdef CONFIGURE_OWNER
+ && statbuf.st_uid != config_uid /* owner not the special one */
+ #endif
+ ) || /* or */
+ (statbuf.st_gid != root_gid /* group not root */
+ #ifdef CONFIGURE_GROUP
+ && statbuf.st_gid != config_gid /* group not the special one */
+ #endif
+ && (statbuf.st_mode & 020) != 0 /* group writeable */
+ ) || /* or */
+ (statbuf.st_mode & 2) != 0) /* world writeable */
+ {
+ f.trusted_config = FALSE;
+ fclose(trust_list);
+ }
+ else
+ {
+ /* Well, the trust list at least is up to scratch... */
+ rmark reset_point;
+ uschar *trusted_configs[32];
+ int nr_configs = 0;
+ int i = 0;
+ int old_pool = store_pool;
+ store_pool = POOL_MAIN;
+
+ reset_point = store_mark();
+ while (Ufgets(big_buffer, big_buffer_size, trust_list))
+ {
+ uschar *start = big_buffer, *nl;
+ while (*start && isspace(*start))
+ start++;
+ if (*start != '/')
+ continue;
+ nl = Ustrchr(start, '\n');
+ if (nl)
+ *nl = 0;
+ trusted_configs[nr_configs++] = string_copy(start);
+ if (nr_configs == nelem(trusted_configs))
+ break;
+ }
+ fclose(trust_list);
+
+ if (nr_configs)
+ {
+ int sep = 0;
+ const uschar *list = argrest;
+ uschar *filename;
+ while (f.trusted_config && (filename = string_nextinlist(&list,
+ &sep, big_buffer, big_buffer_size)))
+ {
+ for (i=0; i < nr_configs; i++)
+ if (Ustrcmp(filename, trusted_configs[i]) == 0)
+ break;
+ if (i == nr_configs)
+ {
+ f.trusted_config = FALSE;
+ break;
+ }
+ }
+ }
+ else /* No valid prefixes found in trust_list file. */
+ f.trusted_config = FALSE;
+ store_reset(reset_point);
+ store_pool = old_pool;
+ }
+ }
+ else /* Could not open trust_list file. */
+ f.trusted_config = FALSE;
+ }
+ #else
+ /* Not root; don't trust config */
+ f.trusted_config = FALSE;
+ #endif
+ }
+
+ config_main_filelist = argrest;
+ f.config_changed = TRUE;
+ }
+ break;
+
+
+ /* -D: set up a macro definition */
+
+ case 'D':
+#ifdef DISABLE_D_OPTION
+ exim_fail("exim: -D is not available in this Exim binary\n");
+#else
+ {
+ int ptr = 0;
+ macro_item *m;
+ uschar name[24];
+ uschar *s = argrest;
+
+ opt_D_used = TRUE;
+ while (isspace(*s)) s++;
+
+ if (*s < 'A' || *s > 'Z')
+ exim_fail("exim: macro name set by -D must start with "
+ "an upper case letter\n");
+
+ while (isalnum(*s) || *s == '_')
+ {
+ if (ptr < sizeof(name)-1) name[ptr++] = *s;
+ s++;
+ }
+ name[ptr] = 0;
+ if (ptr == 0) { badarg = TRUE; break; }
+ while (isspace(*s)) s++;
+ if (*s != 0)
+ {
+ if (*s++ != '=') { badarg = TRUE; break; }
+ while (isspace(*s)) s++;
+ }
+
+ for (m = macros_user; m; m = m->next)
+ if (Ustrcmp(m->name, name) == 0)
+ exim_fail("exim: duplicated -D in command line\n");
+
+ m = macro_create(name, s, TRUE);
+
+ if (clmacro_count >= MAX_CLMACROS)
+ exim_fail("exim: too many -D options on command line\n");
+ clmacros[clmacro_count++] =
+ string_sprintf("-D%s=%s", m->name, m->replacement);
+ }
+ #endif
+ break;
+
+ case 'd':
+
+ /* -dropcr: Set this option. Now a no-op, retained for compatibility only. */
+
+ if (Ustrcmp(argrest, "ropcr") == 0)
+ {
+ /* drop_cr = TRUE; */
+ }
+
+ /* -dp: Set up a debug pretrigger buffer with given size. */
+
+ else if (Ustrcmp(argrest, "p") == 0)
+ if (++i >= argc)
+ badarg = TRUE;
+ else
+ debug_pretrigger_setup(argv[i]);
+
+ /* -dt: Set a debug trigger selector */
+
+ else if (Ustrncmp(argrest, "t=", 2) == 0)
+ dtrigger_selector = (unsigned int) Ustrtol(argrest + 2, NULL, 0);
+
+ /* -d: Set debug level (see also -v below).
+ If -dd is used, debugging subprocesses of the daemon is disabled. */
+
+ else
+ {
+ /* Use an intermediate variable so that we don't set debugging while
+ decoding the debugging bits. */
+
+ unsigned int selector = D_default;
+ debug_selector = 0;
+ debug_file = NULL;
+ if (*argrest == 'd')
+ {
+ f.debug_daemon = TRUE;
+ argrest++;
+ }
+ if (*argrest)
+ decode_bits(&selector, 1, debug_notall, argrest,
+ debug_options, debug_options_count, US"debug", 0);
+ debug_selector = selector;
+ }
+ break;
+
+
+ /* -E: This is a local error message. This option is not intended for
+ external use at all, but is not restricted to trusted callers because it
+ does no harm (just suppresses certain error messages) and if Exim is run
+ not setuid root it won't always be trusted when it generates error
+ messages using this option. If there is a message id following -E, point
+ message_reference at it, for logging. */
+
+ case 'E':
+ f.local_error_message = TRUE;
+ if (mac_ismsgid(argrest)) message_reference = argrest;
+ break;
+
+
+ /* -ex: The vacation program calls sendmail with the undocumented "-eq"
+ option, so it looks as if historically the -oex options are also callable
+ without the leading -o. So we have to accept them. Before the switch,
+ anything starting -oe has been converted to -e. Exim does not support all
+ of the sendmail error options. */
+
+ case 'e':
+ if (Ustrcmp(argrest, "e") == 0)
+ {
+ arg_error_handling = ERRORS_SENDER;
+ errors_sender_rc = EXIT_SUCCESS;
+ }
+ else if (Ustrcmp(argrest, "m") == 0) arg_error_handling = ERRORS_SENDER;
+ else if (Ustrcmp(argrest, "p") == 0) arg_error_handling = ERRORS_STDERR;
+ else if (Ustrcmp(argrest, "q") == 0) arg_error_handling = ERRORS_STDERR;
+ else if (Ustrcmp(argrest, "w") == 0) arg_error_handling = ERRORS_SENDER;
+ else badarg = TRUE;
+ break;
+
+
+ /* -F: Set sender's full name, used instead of the gecos entry from
+ the password file. Since users can usually alter their gecos entries,
+ there's no security involved in using this instead. The data can follow
+ the -F or be in the next argument. */
+
+ case 'F':
+ if (!*argrest)
+ if (++i < argc) argrest = argv[i]; else { badarg = TRUE; break; }
+ originator_name = string_copy_taint(
+ exim_str_fail_toolong(argrest, EXIM_HUMANNAME_MAX, "-F"),
+ GET_TAINTED);
+ f.sender_name_forced = TRUE;
+ break;
+
+
+ /* -f: Set sender's address - this value is only actually used if Exim is
+ run by a trusted user, or if untrusted_set_sender is set and matches the
+ address, except that the null address can always be set by any user. The
+ test for this happens later, when the value given here is ignored when not
+ permitted. For an untrusted user, the actual sender is still put in Sender:
+ if it doesn't match the From: header (unless no_local_from_check is set).
+ The data can follow the -f or be in the next argument. The -r switch is an
+ obsolete form of -f but since there appear to be programs out there that
+ use anything that sendmail has ever supported, better accept it - the
+ synonymizing is done before the switch above.
+
+ At this stage, we must allow domain literal addresses, because we don't
+ know what the setting of allow_domain_literals is yet. Ditto for trailing
+ dots and strip_trailing_dot. */
+
+ case 'f':
+ {
+ int dummy_start, dummy_end;
+ uschar *errmess;
+ if (!*argrest)
+ if (i+1 < argc) argrest = argv[++i]; else { badarg = TRUE; break; }
+ (void) exim_str_fail_toolong(argrest, EXIM_DISPLAYMAIL_MAX, "-f");
+ if (!*argrest)
+ *(sender_address = store_get(1, GET_UNTAINTED)) = '\0'; /* Ensure writeable memory */
+ else
+ {
+ uschar * temp = argrest + Ustrlen(argrest) - 1;
+ while (temp >= argrest && isspace(*temp)) temp--;
+ if (temp >= argrest && *temp == '.') f_end_dot = TRUE;
+ allow_domain_literals = TRUE;
+ strip_trailing_dot = TRUE;
+#ifdef SUPPORT_I18N
+ allow_utf8_domains = TRUE;
+#endif
+ if (!(sender_address = parse_extract_address(argrest, &errmess,
+ &dummy_start, &dummy_end, &sender_address_domain, TRUE)))
+ exim_fail("exim: bad -f address \"%s\": %s\n", argrest, errmess);
+
+ sender_address = string_copy_taint(sender_address, GET_TAINTED);
+#ifdef SUPPORT_I18N
+ message_smtputf8 = string_is_utf8(sender_address);
+ allow_utf8_domains = FALSE;
+#endif
+ allow_domain_literals = FALSE;
+ strip_trailing_dot = FALSE;
+ }
+ f.sender_address_forced = TRUE;
+ }
+ break;
+
+ /* -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':
+ flag_G = TRUE;
+ break;
+
+ /* -h: Set the hop count for an incoming message. Exim does not currently
+ support this; it always computes it by counting the Received: headers.
+ To put it in will require a change to the spool header file format. */
+
+ case 'h':
+ if (!*argrest)
+ if (++i < argc) argrest = argv[i]; else { badarg = TRUE; break; }
+ if (!isdigit(*argrest)) badarg = TRUE;
+ break;
+
+
+ /* -i: Set flag so dot doesn't end non-SMTP input (same as -oi, seems
+ not to be documented for sendmail but mailx (at least) uses it) */
+
+ case 'i':
+ if (!*argrest) f.dot_ends = FALSE; else badarg = TRUE;
+ 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)
+ if (++i < argc) argrest = argv[i]; else { badarg = TRUE; break; }
+ if ((sz = Ustrlen(argrest)) > 32)
+ exim_fail("exim: the -L syslog name is too long: \"%s\"\n", argrest);
+ if (sz < 1)
+ exim_fail("exim: the -L syslog name is too short\n");
+ cmdline_syslog_name = string_copy_taint(argrest, GET_TAINTED);
+ break;
+
+ case 'M':
+ receiving_message = FALSE;
+
+ /* -MC: continue delivery of another message via an existing open
+ file descriptor. This option is used for an internal call by the
+ smtp transport when there is a pending message waiting to go to an
+ address to which it has got a connection. Five subsequent arguments are
+ required: transport name, host name, IP address, sequence number, and
+ message_id. Transports may decline to create new processes if the sequence
+ number gets too big. The channel is stdin. This (-MC) must be the last
+ argument. There's a subsequent check that the real-uid is privileged.
+
+ If we are running in the test harness. delay for a bit, to let the process
+ that set this one up complete. This makes for repeatability of the logging,
+ etc. output. */
+
+ if (Ustrcmp(argrest, "C") == 0)
+ {
+ union sockaddr_46 interface_sock;
+ EXIM_SOCKLEN_T size = sizeof(interface_sock);
+
+ if (argc != i + 6)
+ exim_fail("exim: too many or too few arguments after -MC\n");
+
+ if (msg_action_arg >= 0)
+ exim_fail("exim: incompatible arguments\n");
+
+ continue_transport = string_copy_taint(
+ exim_str_fail_toolong(argv[++i], EXIM_DRIVERNAME_MAX, "-C internal transport"),
+ GET_TAINTED);
+ continue_hostname = string_copy_taint(
+ exim_str_fail_toolong(argv[++i], EXIM_HOSTNAME_MAX, "-C internal hostname"),
+ GET_TAINTED);
+ continue_host_address = string_copy_taint(
+ exim_str_fail_toolong(argv[++i], EXIM_IPADDR_MAX, "-C internal hostaddr"),
+ GET_TAINTED);
+ continue_sequence = Uatoi(argv[++i]);
+ msg_action = MSG_DELIVER;
+ msg_action_arg = ++i;
+ forced_delivery = TRUE;
+ queue_run_pid = passed_qr_pid;
+ queue_run_pipe = passed_qr_pipe;
+
+ if (!mac_ismsgid(argv[i]))
+ exim_fail("exim: malformed message id %s after -MC option\n",
+ argv[i]);
+
+ /* Set up $sending_ip_address and $sending_port, unless proxied */
+
+ 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
+ exim_fail("exim: getsockname() failed after -MC option: %s\n",
+ strerror(errno));
+
+ testharness_pause_ms(500);
+ break;
+ }
+
+ else if (*argrest == 'C' && argrest[1] && !argrest[2])
+ {
+ 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
+ Exim is connected has accepted an AUTH sequence. */
+
+ case 'A': f.smtp_authenticated = TRUE; break;
+
+ /* -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 |= OPTION_DSN; break;
+
+ /* -MCd: for debug, set a process-purpose string */
+
+ case 'd': if (++i < argc)
+ process_purpose = string_copy_taint(
+ exim_str_fail_toolong(argv[i], EXIM_DRIVERNAME_MAX, "-MCd"),
+ GET_TAINTED);
+ else badarg = TRUE;
+ break;
+
+ /* -MCG: set the queue name, to a non-default value. Arguably, anything
+ from the commandline should be tainted - but we will need an untainted
+ value for the spoolfile when doing a -odi delivery process. */
+
+ case 'G': if (++i < argc) queue_name = string_copy_taint(
+ exim_str_fail_toolong(argv[i], EXIM_DRIVERNAME_MAX, "-MCG"),
+ GET_UNTAINTED);
+ else badarg = TRUE;
+ break;
+
+ /* -MCK: the peer offered CHUNKING. Must precede -MC */
+
+ case 'K': smtp_peer_options |= OPTION_CHUNKING; break;
+
+#ifdef EXPERIMENTAL_ESMTP_LIMITS
+ /* -MCL: peer used LIMITS RCPTMAX and/or RCPTDOMAINMAX */
+ case 'L': if (++i < argc) continue_limit_mail = Uatoi(argv[i]);
+ else badarg = TRUE;
+ if (++i < argc) continue_limit_rcpt = Uatoi(argv[i]);
+ else badarg = TRUE;
+ if (++i < argc) continue_limit_rcptdom = Uatoi(argv[i]);
+ else badarg = TRUE;
+ break;
+#endif
+
+ /* -MCP: set the smtp_use_pipelining flag; this is useful only when
+ it preceded -MC (see above) */
+
+ case 'P': smtp_peer_options |= OPTION_PIPE; break;
+
+#ifdef SUPPORT_SOCKS
+ /* -MCp: Socks proxy in use; nearside IP, port, external IP, port */
+ case 'p': proxy_session = TRUE;
+ if (++i < argc)
+ {
+ proxy_local_address = string_copy_taint(argv[i], GET_TAINTED);
+ if (++i < argc)
+ {
+ proxy_local_port = Uatoi(argv[i]);
+ if (++i < argc)
+ {
+ proxy_external_address = string_copy_taint(argv[i], GET_TAINTED);
+ if (++i < argc)
+ {
+ proxy_external_port = Uatoi(argv[i]);
+ break;
+ } } } }
+ badarg = TRUE;
+ break;
+#endif
+ /* -MCQ: pass on the pid of the queue-running process that started
+ this chain of deliveries and the fd of its synchronizing pipe; this
+ is useful only when it precedes -MC (see above) */
+
+ case 'Q': if (++i < argc) passed_qr_pid = (pid_t)(Uatol(argv[i]));
+ else badarg = TRUE;
+ if (++i < argc) passed_qr_pipe = (int)(Uatol(argv[i]));
+ else badarg = TRUE;
+ break;
+
+ /* -MCq: do a quota check on the given recipient for the given size
+ of message. Separate from -MC. */
+ case 'q': rcpt_verify_quota = TRUE;
+ if (++i < argc) message_size = Uatoi(argv[i]);
+ else badarg = TRUE;
+ break;
+
+ /* -MCS: set the smtp_use_size flag; this is useful only when it
+ precedes -MC (see above) */
+
+ case 'S': smtp_peer_options |= OPTION_SIZE; break;
+
+#ifndef DISABLE_TLS
+ /* -MCs: used with -MCt; SNI was sent */
+ /* -MCr: ditto, DANE */
+
+ case 'r':
+ case 's': if (++i < argc)
+ {
+ continue_proxy_sni = string_copy_taint(
+ exim_str_fail_toolong(argv[i], EXIM_HOSTNAME_MAX, "-MCr/-MCs"),
+ GET_TAINTED);
+ if (argrest[1] == 'r') continue_proxy_dane = TRUE;
+ }
+ else badarg = TRUE;
+ break;
+
+ /* -MCt: similar to -MCT below but the connection is still open
+ via a proxy process 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 = string_copy_taint(
+ exim_str_fail_toolong(argv[i], EXIM_IPADDR_MAX, "-MCt IP"),
+ GET_TAINTED);
+ else badarg = TRUE;
+ if (++i < argc)
+ sending_port = (int)(Uatol(argv[i]));
+ else badarg = TRUE;
+ if (++i < argc)
+ continue_proxy_cipher = string_copy_taint(
+ exim_str_fail_toolong(argv[i], EXIM_CIPHERNAME_MAX, "-MCt cipher"),
+ GET_TAINTED);
+ 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. */
+
+ case 'T': smtp_peer_options |= OPTION_TLS; break;
+#endif
+
+ default: badarg = TRUE; break;
+ }
+ break;
+ }
+
+ /* -M[x]: various operations on the following list of message ids:
+ -M deliver the messages, ignoring next retry times and thawing
+ -Mc deliver the messages, checking next retry times, no thawing
+ -Mf freeze the messages
+ -Mg give up on the messages
+ -Mt thaw the messages
+ -Mrm remove the messages
+ In the above cases, this must be the last option. There are also the
+ following options which are followed by a single message id, and which
+ act on that message. Some of them use the "recipient" addresses as well.
+ -Mar add recipient(s)
+ -MG move to a different queue
+ -Mmad mark all recipients delivered
+ -Mmd mark recipients(s) delivered
+ -Mes edit sender
+ -Mset load a message for use with -be
+ -Mvb show body
+ -Mvc show copy (of whole message, in RFC 2822 format)
+ -Mvh show header
+ -Mvl show log
+ */
+
+ else if (!*argrest)
+ {
+ msg_action = MSG_DELIVER;
+ forced_delivery = f.deliver_force_thaw = TRUE;
+ }
+ else if (Ustrcmp(argrest, "ar") == 0)
+ {
+ msg_action = MSG_ADD_RECIPIENT;
+ one_msg_action = TRUE;
+ }
+ else if (Ustrcmp(argrest, "c") == 0) msg_action = MSG_DELIVER;
+ else if (Ustrcmp(argrest, "es") == 0)
+ {
+ msg_action = MSG_EDIT_SENDER;
+ one_msg_action = TRUE;
+ }
+ else if (Ustrcmp(argrest, "f") == 0) msg_action = MSG_FREEZE;
+ else if (Ustrcmp(argrest, "g") == 0)
+ {
+ msg_action = MSG_DELIVER;
+ deliver_give_up = TRUE;
+ }
+ else if (Ustrcmp(argrest, "G") == 0)
+ {
+ msg_action = MSG_SETQUEUE;
+ queue_name_dest = string_copy_taint(
+ exim_str_fail_toolong(argv[++i], EXIM_DRIVERNAME_MAX, "-MG"),
+ GET_TAINTED);
+ }
+ else if (Ustrcmp(argrest, "mad") == 0) msg_action = MSG_MARK_ALL_DELIVERED;
+ else if (Ustrcmp(argrest, "md") == 0)
+ {
+ msg_action = MSG_MARK_DELIVERED;
+ one_msg_action = TRUE;
+ }
+ else if (Ustrcmp(argrest, "rm") == 0) msg_action = MSG_REMOVE;
+ else if (Ustrcmp(argrest, "set") == 0)
+ {
+ msg_action = MSG_LOAD;
+ one_msg_action = TRUE;
+ }
+ else if (Ustrcmp(argrest, "t") == 0) msg_action = MSG_THAW;
+ else if (Ustrcmp(argrest, "vb") == 0)
+ {
+ msg_action = MSG_SHOW_BODY;
+ one_msg_action = TRUE;
+ }
+ else if (Ustrcmp(argrest, "vc") == 0)
+ {
+ msg_action = MSG_SHOW_COPY;
+ one_msg_action = TRUE;
+ }
+ else if (Ustrcmp(argrest, "vh") == 0)
+ {
+ msg_action = MSG_SHOW_HEADER;
+ one_msg_action = TRUE;
+ }
+ else if (Ustrcmp(argrest, "vl") == 0)
+ {
+ msg_action = MSG_SHOW_LOG;
+ one_msg_action = TRUE;
+ }
+ else { badarg = TRUE; break; }
+
+ /* All the -Mxx options require at least one message id. */
+
+ msg_action_arg = i + 1;
+ if (msg_action_arg >= argc)
+ exim_fail("exim: no message ids given after %s option\n", arg);
+
+ /* Some require only message ids to follow */
+
+ if (!one_msg_action)
+ {
+ for (int j = msg_action_arg; j < argc; j++) if (!mac_ismsgid(argv[j]))
+ exim_fail("exim: malformed message id %s after %s option\n",
+ argv[j], arg);
+ goto END_ARG; /* Remaining args are ids */
+ }
+
+ /* Others require only one message id, possibly followed by addresses,
+ which will be handled as normal arguments. */
+
+ else
+ {
+ if (!mac_ismsgid(argv[msg_action_arg]))
+ exim_fail("exim: malformed message id %s after %s option\n",
+ argv[msg_action_arg], arg);
+ i++;
+ }
+ break;
+
+
+ /* Some programs seem to call the -om option without the leading o;
+ for sendmail it askes for "me too". Exim always does this. */
+
+ case 'm':
+ if (*argrest) badarg = TRUE;
+ break;
+
+
+ /* -N: don't do delivery - a debugging option that stops transports doing
+ their thing. It implies debugging at the D_v level. */
+
+ case 'N':
+ if (!*argrest)
+ {
+ f.dont_deliver = TRUE;
+ debug_selector |= D_v;
+ debug_file = stderr;
+ }
+ else badarg = TRUE;
+ break;
+
+
+ /* -n: This means "don't alias" in sendmail, apparently.
+ For normal invocations, it has no effect.
+ It may affect some other options. */
+
+ case 'n':
+ flag_n = TRUE;
+ break;
+
+ /* -O: Just ignore it. In sendmail, apparently -O option=value means set
+ option to the specified value. This form uses long names. We need to handle
+ -O option=value and -Ooption=value. */
+
+ case 'O':
+ if (!*argrest)
+ if (++i >= argc)
+ exim_fail("exim: string expected after -O\n");
+ break;
+
+ case 'o':
+ switch (*argrest++)
+ {
+ /* -oA: Set an argument for the bi command (sendmail's "alternate alias
+ file" option). */
+ case 'A':
+ if (!*(alias_arg = argrest))
+ if (i+1 < argc) alias_arg = argv[++i];
+ else exim_fail("exim: string expected after -oA\n");
+ break;
+
+ /* -oB: Set a connection message max value for remote deliveries */
+ case 'B':
+ {
+ uschar * p = argrest;
+ if (!*p)
+ if (i+1 < argc && isdigit((argv[i+1][0])))
+ p = argv[++i];
+ else
+ {
+ connection_max_messages = 1;
+ p = NULL;
+ }
+
+ if (p)
+ {
+ if (!isdigit(*p))
+ exim_fail("exim: number expected after -oB\n");
+ connection_max_messages = Uatoi(p);
+ }
+ }
+ break;
+
+ /* -odb: background delivery */
+
+ case 'd':
+ if (Ustrcmp(argrest, "b") == 0)
+ {
+ f.synchronous_delivery = FALSE;
+ arg_queue_only = FALSE;
+ queue_only_set = TRUE;
+ }
+
+ /* -odd: testsuite-only: add no inter-process delays */
+
+ else if (Ustrcmp(argrest, "d") == 0)
+ f.testsuite_delays = FALSE;
+
+ /* -odf: foreground delivery (smail-compatible option); same effect as
+ -odi: interactive (synchronous) delivery (sendmail-compatible option)
+ */
+
+ else if (Ustrcmp(argrest, "f") == 0 || Ustrcmp(argrest, "i") == 0)
+ {
+ f.synchronous_delivery = TRUE;
+ arg_queue_only = FALSE;
+ queue_only_set = TRUE;
+ }
+
+ /* -odq: queue only */
+
+ else if (Ustrcmp(argrest, "q") == 0)
+ {
+ f.synchronous_delivery = FALSE;
+ arg_queue_only = TRUE;
+ queue_only_set = TRUE;
+ }
+
+ /* -odqs: queue SMTP only - do local deliveries and remote routing,
+ but no remote delivery */
+
+ else if (Ustrcmp(argrest, "qs") == 0)
+ {
+ f.queue_smtp = TRUE;
+ arg_queue_only = FALSE;
+ queue_only_set = TRUE;
+ }
+ else badarg = TRUE;
+ break;
+
+ /* -oex: Sendmail error flags. As these are also accepted without the
+ leading -o prefix, for compatibility with vacation and other callers,
+ they are handled with -e above. */
+
+ /* -oi: Set flag so dot doesn't end non-SMTP input (same as -i)
+ -oitrue: Another sendmail syntax for the same */
+
+ case 'i':
+ if (!*argrest || Ustrcmp(argrest, "true") == 0)
+ f.dot_ends = FALSE;
+ else badarg = TRUE;
+ break;
+
+ /* -oM*: Set various characteristics for an incoming message; actually
+ acted on for trusted callers only. */
+
+ case 'M':
+ {
+ if (i+1 >= argc)
+ exim_fail("exim: data expected after -oM%s\n", argrest);
+
+ /* -oMa: Set sender host address */
+
+ if (Ustrcmp(argrest, "a") == 0)
+ sender_host_address = string_copy_taint(
+ exim_str_fail_toolong(argv[++i], EXIM_IPADDR_MAX, "-oMa"),
+ GET_TAINTED);
+
+ /* -oMaa: Set authenticator name */
+
+ else if (Ustrcmp(argrest, "aa") == 0)
+ sender_host_authenticated = string_copy_taint(
+ exim_str_fail_toolong(argv[++i], EXIM_DRIVERNAME_MAX, "-oMaa"),
+ GET_TAINTED);
+
+ /* -oMas: setting authenticated sender */
+
+ else if (Ustrcmp(argrest, "as") == 0)
+ authenticated_sender = string_copy_taint(
+ exim_str_fail_toolong(argv[++i], EXIM_EMAILADDR_MAX, "-oMas"),
+ GET_TAINTED);
+
+ /* -oMai: setting authenticated id */
+
+ else if (Ustrcmp(argrest, "ai") == 0)
+ authenticated_id = string_copy_taint(
+ exim_str_fail_toolong(argv[++i], EXIM_EMAILADDR_MAX, "-oMas"),
+ GET_TAINTED);
+
+ /* -oMi: Set incoming interface address */
+
+ else if (Ustrcmp(argrest, "i") == 0)
+ interface_address = string_copy_taint(
+ exim_str_fail_toolong(argv[++i], EXIM_IPADDR_MAX, "-oMi"),
+ GET_TAINTED);
+
+ /* -oMm: Message reference */
+
+ else if (Ustrcmp(argrest, "m") == 0)
+ {
+ if (!mac_ismsgid(argv[i+1]))
+ exim_fail("-oMm must be a valid message ID\n");
+ if (!f.trusted_config)
+ exim_fail("-oMm must be called by a trusted user/config\n");
+ message_reference = argv[++i];
+ }
+
+ /* -oMr: Received protocol */
+
+ else if (Ustrcmp(argrest, "r") == 0)
+
+ if (received_protocol)
+ exim_fail("received_protocol is set already\n");
+ else
+ received_protocol = string_copy_taint(
+ exim_str_fail_toolong(argv[++i], EXIM_DRIVERNAME_MAX, "-oMr"),
+ GET_TAINTED);
+
+ /* -oMs: Set sender host name */
+
+ else if (Ustrcmp(argrest, "s") == 0)
+ sender_host_name = string_copy_taint(
+ exim_str_fail_toolong(argv[++i], EXIM_HOSTNAME_MAX, "-oMs"),
+ GET_TAINTED);
+
+ /* -oMt: Set sender ident */
+
+ else if (Ustrcmp(argrest, "t") == 0)
+ {
+ sender_ident_set = TRUE;
+ sender_ident = string_copy_taint(
+ exim_str_fail_toolong(argv[++i], EXIM_IDENTUSER_MAX, "-oMt"),
+ GET_TAINTED);
+ }
+
+ /* Else a bad argument */
+
+ else
+ badarg = TRUE;
+ }
+ break;
+
+ /* -om: Me-too flag for aliases. Exim always does this. Some programs
+ seem to call this as -m (undocumented), so that is also accepted (see
+ above). */
+ /* -oo: An ancient flag for old-style addresses which still seems to
+ crop up in some calls (see in SCO). */
+
+ case 'm':
+ case 'o':
+ if (*argrest) badarg = TRUE;
+ break;
+
+ /* -oP <name>: set pid file path for daemon
+ -oPX: delete pid file of daemon */
+
+ case 'P':
+ if (!f.running_in_test_harness && real_uid != root_uid && real_uid != exim_uid)
+ exim_fail("exim: only uid=%d or uid=%d can use -oP and -oPX "
+ "(uid=%d euid=%d | %d)\n",
+ root_uid, exim_uid, getuid(), geteuid(), real_uid);
+ if (!*argrest) override_pid_file_path = argv[++i];
+ else if (Ustrcmp(argrest, "X") == 0) delete_pid_file();
+ else badarg = TRUE;
+ break;
+
+
+ /* -or <n>: set timeout for non-SMTP acceptance
+ -os <n>: set timeout for SMTP acceptance */
+
+ case 'r':
+ case 's':
+ {
+ int * tp = argrest[-1] == 'r'
+ ? &arg_receive_timeout : &arg_smtp_receive_timeout;
+ if (*argrest)
+ *tp = readconf_readtime(argrest, 0, FALSE);
+ else if (i+1 < argc)
+ *tp = readconf_readtime(argv[++i], 0, FALSE);
+
+ if (*tp < 0)
+ exim_fail("exim: bad time value %s: abandoned\n", argv[i]);
+ }
+ break;
+
+ /* -oX <list>: Override local_interfaces and/or default daemon ports */
+ /* Limits: Is there a real limit we want here? 1024 is very arbitrary. */
+
+ case 'X':
+ if (*argrest) badarg = TRUE;
+ else override_local_interfaces = string_copy_taint(
+ exim_str_fail_toolong(argv[++i], 1024, "-oX"),
+ GET_TAINTED);
+ break;
+
+ /* -oY: Override creation of daemon notifier socket */
+
+ case 'Y':
+ if (*argrest) badarg = TRUE;
+ else notifier_socket = NULL;
+ break;
+
+ /* Unknown -o argument */
+
+ default:
+ badarg = TRUE;
+ }
+ break;
+
+
+ /* -ps: force Perl startup; -pd force delayed Perl startup */
+
+ case 'p':
+ #ifdef EXIM_PERL
+ if (*argrest == 's' && argrest[1] == 0)
+ {
+ perl_start_option = 1;
+ break;
+ }
+ if (*argrest == 'd' && argrest[1] == 0)
+ {
+ perl_start_option = -1;
+ break;
+ }
+ #endif
+
+ /* -panythingelse is taken as the Sendmail-compatible argument -prval:sval,
+ which sets the host protocol and host name */
+
+ if (!*argrest)
+ if (i+1 < argc) argrest = argv[++i]; else { badarg = TRUE; break; }
+
+ if (*argrest)
+ {
+ uschar * hn = Ustrchr(argrest, ':');
+
+ if (received_protocol)
+ exim_fail("received_protocol is set already\n");
+
+ if (!hn)
+ received_protocol = string_copy_taint(
+ exim_str_fail_toolong(argrest, EXIM_DRIVERNAME_MAX, "-p<protocol>"),
+ GET_TAINTED);
+ else
+ {
+ (void) exim_str_fail_toolong(argrest, (EXIM_DRIVERNAME_MAX+1+EXIM_HOSTNAME_MAX), "-p<protocol>:<host>");
+ received_protocol = string_copyn_taint(argrest, hn - argrest, GET_TAINTED);
+ sender_host_name = string_copy_taint(hn + 1, GET_TAINTED);
+ }
+ }
+ break;
+
+
+ case 'q':
+ receiving_message = FALSE;
+ if (queue_interval >= 0)
+ exim_fail("exim: -q specified more than once\n");
+
+ /* -qq...: Do queue runs in a 2-stage manner */
+
+ if (*argrest == 'q')
+ {
+ f.queue_2stage = TRUE;
+ argrest++;
+ }
+
+ /* -qi...: Do only first (initial) deliveries */
+
+ if (*argrest == 'i')
+ {
+ f.queue_run_first_delivery = TRUE;
+ argrest++;
+ }
+
+ /* -qf...: Run the queue, forcing deliveries
+ -qff..: Ditto, forcing thawing as well */
+
+ if (*argrest == 'f')
+ {
+ f.queue_run_force = TRUE;
+ if (*++argrest == 'f')
+ {
+ f.deliver_force_thaw = TRUE;
+ argrest++;
+ }
+ }
+
+ /* -q[f][f]l...: Run the queue only on local deliveries */
+
+ if (*argrest == 'l')
+ {
+ f.queue_run_local = TRUE;
+ argrest++;
+ }
+
+ /* -q[f][f][l][G<name>]... Work on the named queue */
+
+ if (*argrest == 'G')
+ {
+ int i;
+ for (argrest++, i = 0; argrest[i] && argrest[i] != '/'; ) i++;
+ exim_len_fail_toolong(i, EXIM_DRIVERNAME_MAX, "-q*G<name>");
+ queue_name = string_copyn(argrest, i);
+ argrest += i;
+ if (*argrest == '/') argrest++;
+ }
+
+ /* -q[f][f][l][G<name>]: Run the queue, optionally forced, optionally local
+ only, optionally named, optionally starting from a given message id. */
+
+ if (!(list_queue || count_queue))
+ if ( !*argrest
+ && (i + 1 >= argc || argv[i+1][0] == '-' || mac_ismsgid(argv[i+1])))
+ {
+ queue_interval = 0;
+ if (i+1 < argc && mac_ismsgid(argv[i+1]))
+ start_queue_run_id = string_copy_taint(argv[++i], GET_TAINTED);
+ if (i+1 < argc && mac_ismsgid(argv[i+1]))
+ stop_queue_run_id = string_copy_taint(argv[++i], GET_TAINTED);
+ }
+
+ /* -q[f][f][l][G<name>/]<n>: Run the queue at regular intervals, optionally
+ forced, optionally local only, optionally named. */
+
+ else if ((queue_interval = readconf_readtime(*argrest ? argrest : argv[++i],
+ 0, FALSE)) <= 0)
+ exim_fail("exim: bad time value %s: abandoned\n", argv[i]);
+ break;
+
+
+ case 'R': /* Synonymous with -qR... */
+ {
+ const uschar *tainted_selectstr;
+
+ receiving_message = FALSE;
+
+ /* -Rf: As -R (below) but force all deliveries,
+ -Rff: Ditto, but also thaw all frozen messages,
+ -Rr: String is regex
+ -Rrf: Regex and force
+ -Rrff: Regex and force and thaw
+
+ in all cases provided there are no further characters in this
+ argument. */
+
+ if (*argrest)
+ for (int i = 0; i < nelem(rsopts); i++)
+ if (Ustrcmp(argrest, rsopts[i]) == 0)
+ {
+ if (i != 2) f.queue_run_force = TRUE;
+ if (i >= 2) f.deliver_selectstring_regex = TRUE;
+ if (i == 1 || i == 4) f.deliver_force_thaw = TRUE;
+ argrest += Ustrlen(rsopts[i]);
+ }
+
+ /* -R: Set string to match in addresses for forced queue run to
+ pick out particular messages. */
+
+ /* Avoid attacks from people providing very long strings, and do so before
+ we make copies. */
+ if (*argrest)
+ tainted_selectstr = argrest;
+ else if (i+1 < argc)
+ tainted_selectstr = argv[++i];
+ else
+ exim_fail("exim: string expected after -R\n");
+ deliver_selectstring = string_copy_taint(
+ exim_str_fail_toolong(tainted_selectstr, EXIM_EMAILADDR_MAX, "-R"),
+ GET_TAINTED);
+ }
+ break;
+
+ /* -r: an obsolete synonym for -f (see above) */
+
+
+ /* -S: Like -R but works on sender. */
+
+ case 'S': /* Synonymous with -qS... */
+ {
+ const uschar *tainted_selectstr;
+
+ receiving_message = FALSE;
+
+ /* -Sf: As -S (below) but force all deliveries,
+ -Sff: Ditto, but also thaw all frozen messages,
+ -Sr: String is regex
+ -Srf: Regex and force
+ -Srff: Regex and force and thaw
+
+ in all cases provided there are no further characters in this
+ argument. */
+
+ if (*argrest)
+ for (int i = 0; i < nelem(rsopts); i++)
+ if (Ustrcmp(argrest, rsopts[i]) == 0)
+ {
+ if (i != 2) f.queue_run_force = TRUE;
+ if (i >= 2) f.deliver_selectstring_sender_regex = TRUE;
+ if (i == 1 || i == 4) f.deliver_force_thaw = TRUE;
+ argrest += Ustrlen(rsopts[i]);
+ }
+
+ /* -S: Set string to match in addresses for forced queue run to
+ pick out particular messages. */
+
+ if (*argrest)
+ tainted_selectstr = argrest;
+ else if (i+1 < argc)
+ tainted_selectstr = argv[++i];
+ else
+ exim_fail("exim: string expected after -S\n");
+ deliver_selectstring_sender = string_copy_taint(
+ exim_str_fail_toolong(tainted_selectstr, EXIM_EMAILADDR_MAX, "-S"),
+ GET_TAINTED);
+ }
+ break;
+
+ /* -Tqt is an option that is exclusively for use by the testing suite.
+ It is not recognized in other circumstances. It allows for the setting up
+ of explicit "queue times" so that various warning/retry things can be
+ tested. Otherwise variability of clock ticks etc. cause problems. */
+
+ case 'T':
+ if (f.running_in_test_harness && Ustrcmp(argrest, "qt") == 0)
+ fudged_queue_times = string_copy_taint(argv[++i], GET_TAINTED);
+ else badarg = TRUE;
+ break;
+
+
+ /* -t: Set flag to extract recipients from body of message. */
+
+ case 't':
+ if (!*argrest) extract_recipients = TRUE;
+
+ /* -ti: Set flag to extract recipients from body of message, and also
+ specify that dot does not end the message. */
+
+ else if (Ustrcmp(argrest, "i") == 0)
+ {
+ extract_recipients = TRUE;
+ f.dot_ends = FALSE;
+ }
+
+ /* -tls-on-connect: don't wait for STARTTLS (for old clients) */
+
+ #ifndef DISABLE_TLS
+ else if (Ustrcmp(argrest, "ls-on-connect") == 0) tls_in.on_connect = TRUE;
+ #endif
+
+ else badarg = TRUE;
+ break;
+
+
+ /* -U: This means "initial user submission" in sendmail, apparently. The
+ doc claims that in future sendmail may refuse syntactically invalid
+ messages instead of fixing them. For the moment, we just ignore it. */
+
+ case 'U':
+ break;
+
+
+ /* -v: verify things - this is a very low-level debugging */
+
+ case 'v':
+ if (!*argrest)
+ {
+ debug_selector |= D_v;
+ debug_file = stderr;
+ }
+ else badarg = TRUE;
+ break;
+
+
+ /* -x: AIX uses this to indicate some fancy 8-bit character stuff:
+
+ The -x flag tells the sendmail command that mail from a local
+ mail program has National Language Support (NLS) extended characters
+ in the body of the mail item. The sendmail command can send mail with
+ extended NLS characters across networks that normally corrupts these
+ 8-bit characters.
+
+ As Exim is 8-bit clean, it just ignores this flag. */
+
+ case 'x':
+ if (*argrest) 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)
+ if (++i >= argc)
+ exim_fail("exim: string expected after -X\n");
+ break;
+
+ /* -z: a line of text to log */
+
+ case 'z':
+ if (!*argrest)
+ if (++i < argc)
+ log_oneline = string_copy_taint(
+ exim_str_fail_toolong(argv[i], 2048, "-z logtext"),
+ GET_TAINTED);
+ else
+ exim_fail("exim: file name expected after %s\n", argv[i-1]);
+ break;
+
+ /* All other initial characters are errors */
+
+ default:
+ badarg = TRUE;
+ break;
+ } /* End of high-level switch statement */
+
+ /* Failed to recognize the option, or syntax error */
+
+ if (badarg)
+ exim_fail("exim abandoned: unknown, malformed, or incomplete "
+ "option %s\n", arg);
+ }
+
+
+/* If -R or -S have been specified without -q, assume a single queue run. */
+
+ if ( (deliver_selectstring || deliver_selectstring_sender)
+ && queue_interval < 0)
+ queue_interval = 0;
+
+
+END_ARG:
+ store_pool = old_pool;
+ }
+
+/* If usage_wanted is set we call the usage function - which never returns */
+if (usage_wanted) exim_usage(called_as);
+
+/* Arguments have been processed. Check for incompatibilities. */
+if ( ( (smtp_input || extract_recipients || recipients_arg < argc)
+ && ( f.daemon_listen || queue_interval >= 0 || bi_option
+ || test_retry_arg >= 0 || test_rewrite_arg >= 0
+ || filter_test != FTEST_NONE
+ || msg_action_arg > 0 && !one_msg_action
+ ) )
+ || ( msg_action_arg > 0
+ && ( f.daemon_listen || queue_interval > 0 || list_options
+ || checking && msg_action != MSG_LOAD
+ || bi_option || test_retry_arg >= 0 || test_rewrite_arg >= 0
+ ) )
+ || ( (f.daemon_listen || queue_interval > 0)
+ && ( sender_address || list_options || list_queue || checking
+ || bi_option
+ ) )
+ || f.daemon_listen && queue_interval == 0
+ || f.inetd_wait_mode && queue_interval >= 0
+ || ( list_options
+ && ( checking || smtp_input || extract_recipients
+ || filter_test != FTEST_NONE || bi_option
+ ) )
+ || ( verify_address_mode
+ && ( f.address_test_mode || smtp_input || extract_recipients
+ || filter_test != FTEST_NONE || bi_option
+ ) )
+ || ( f.address_test_mode
+ && ( smtp_input || extract_recipients || filter_test != FTEST_NONE
+ || bi_option
+ ) )
+ || ( smtp_input
+ && (sender_address || filter_test != FTEST_NONE || extract_recipients)
+ )
+ || deliver_selectstring && queue_interval < 0
+ || msg_action == MSG_LOAD && (!expansion_test || expansion_test_message)
+ )
+ exim_fail("exim: incompatible command-line options or arguments\n");
+
+/* If debugging is set up, set the file and the file descriptor to pass on to
+child processes. It should, of course, be 2 for stderr. Also, force the daemon
+to run in the foreground. */
+
+if (debug_selector != 0)
+ {
+ debug_file = stderr;
+ debug_fd = fileno(debug_file);
+ f.background_daemon = FALSE;
+ testharness_pause_ms(100); /* lets caller finish */
+ if (debug_selector != D_v) /* -v only doesn't show this */
+ {
+ 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);
+ if (!version_printed)
+ show_whats_supported(FALSE);
+ }
+ }
+
+/* When started with root privilege, ensure that the limits on the number of
+open files and the number of processes (where that is accessible) are
+sufficiently large, or are unset, in case Exim has been called from an
+environment where the limits are screwed down. Not all OS have the ability to
+change some of these limits. */
+
+if (unprivileged)
+ {
+ DEBUG(D_any) debug_print_ids(US"Exim has no root privilege:");
+ }
+else
+ {
+ struct rlimit rlp;
+
+#ifdef RLIMIT_NOFILE
+ if (getrlimit(RLIMIT_NOFILE, &rlp) < 0)
+ {
+ log_write(0, LOG_MAIN|LOG_PANIC, "getrlimit(RLIMIT_NOFILE) failed: %s",
+ strerror(errno));
+ rlp.rlim_cur = rlp.rlim_max = 0;
+ }
+
+ /* I originally chose 1000 as a nice big number that was unlikely to
+ be exceeded. It turns out that some older OS have a fixed upper limit of
+ 256. */
+
+ if (rlp.rlim_cur < 1000)
+ {
+ rlp.rlim_cur = rlp.rlim_max = 1000;
+ if (setrlimit(RLIMIT_NOFILE, &rlp) < 0)
+ {
+ rlp.rlim_cur = rlp.rlim_max = 256;
+ if (setrlimit(RLIMIT_NOFILE, &rlp) < 0)
+ log_write(0, LOG_MAIN|LOG_PANIC, "setrlimit(RLIMIT_NOFILE) failed: %s",
+ strerror(errno));
+ }
+ }
+#endif
+
+#ifdef RLIMIT_NPROC
+ if (getrlimit(RLIMIT_NPROC, &rlp) < 0)
+ {
+ log_write(0, LOG_MAIN|LOG_PANIC, "getrlimit(RLIMIT_NPROC) failed: %s",
+ strerror(errno));
+ rlp.rlim_cur = rlp.rlim_max = 0;
+ }
+
+# ifdef RLIM_INFINITY
+ if (rlp.rlim_cur != RLIM_INFINITY && rlp.rlim_cur < 1000)
+ {
+ rlp.rlim_cur = rlp.rlim_max = RLIM_INFINITY;
+# else
+ if (rlp.rlim_cur < 1000)
+ {
+ rlp.rlim_cur = rlp.rlim_max = 1000;
+# endif
+ if (setrlimit(RLIMIT_NPROC, &rlp) < 0)
+ log_write(0, LOG_MAIN|LOG_PANIC, "setrlimit(RLIMIT_NPROC) failed: %s",
+ strerror(errno));
+ }
+#endif
+ }
+
+/* Exim is normally entered as root (but some special configurations are
+possible that don't do this). However, it always spins off sub-processes that
+set their uid and gid as required for local delivery. We don't want to pass on
+any extra groups that root may belong to, so we want to get rid of them all at
+this point.
+
+We need to obey setgroups() at this stage, before possibly giving up root
+privilege for a changed configuration file, but later on we might need to
+check on the additional groups for the admin user privilege - can't do that
+till after reading the config, which might specify the exim gid. Therefore,
+save the group list here first. */
+
+if ((group_count = getgroups(nelem(group_list), group_list)) < 0)
+ exim_fail("exim: getgroups() failed: %s\n", strerror(errno));
+
+/* 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
+known not to be different. On the "different" systems there is a single group
+list, and the first entry in it is the current group. On all other versions of
+Unix there is a supplementary group list, which is in *addition* to the current
+group. Consequently, to get rid of all extraneous groups on a "standard" system
+you pass over 0 groups to setgroups(), while on a "different" system you pass
+over a single group - the current group, which is always the first group in the
+list. Calling setgroups() with zero groups on a "different" system results in
+an error return. The following code should cope with both types of system.
+
+ Unfortunately, recent MacOS, which should be a FreeBSD, "helpfully" succeeds
+ the "setgroups() with zero groups" - and changes the egid.
+ Thanks to that we had to stash the original_egid above, for use below
+ in the call to exim_setugid().
+
+However, if this process isn't running as root, setgroups() can't be used
+since you have to be root to run it, even if throwing away groups.
+Except, sigh, for Hurd - where you can.
+Not being root here happens only in some unusual configurations. */
+
+if ( !unprivileged
+#ifndef OS_SETGROUPS_ZERO_DROPS_ALL
+ && setgroups(0, NULL) != 0
+#endif
+ && setgroups(1, group_list) != 0)
+ exim_fail("exim: setgroups() failed: %s\n", strerror(errno));
+
+/* If the configuration file name has been altered by an argument on the
+command line (either a new file name or a macro definition) and the caller is
+not root, or if this is a filter testing run, remove any setuid privilege the
+program has and run as the underlying user.
+
+The exim user is locked out of this, which severely restricts the use of -C
+for some purposes.
+
+Otherwise, set the real ids to the effective values (should be root unless run
+from inetd, which it can either be root or the exim uid, if one is configured).
+
+There is a private mechanism for bypassing some of this, in order to make it
+possible to test lots of configurations automatically, without having either to
+recompile each time, or to patch in an actual configuration file name and other
+values (such as the path name). If running in the test harness, pretend that
+configuration file changes and macro definitions haven't happened. */
+
+if (( /* EITHER */
+ (!f.trusted_config || /* Config changed, or */
+ !macros_trusted(opt_D_used)) && /* impermissible macros and */
+ real_uid != root_uid && /* Not root, and */
+ !f.running_in_test_harness /* Not fudged */
+ ) || /* OR */
+ expansion_test /* expansion testing */
+ || /* OR */
+ filter_test != FTEST_NONE) /* Filter testing */
+ {
+ setgroups(group_count, group_list);
+ exim_setugid(real_uid, real_gid, FALSE,
+ US"-C, -D, -be or -bf forces real uid");
+ removed_privilege = TRUE;
+
+ /* In the normal case when Exim is called like this, stderr is available
+ 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).
+
+ Note that if the invoker is Exim, the logs remain available. Messing with
+ this causes unlogged successful deliveries. */
+
+ if (log_stderr && real_uid != exim_uid)
+ f.really_exim = FALSE;
+ }
+
+/* Privilege is to be retained for the moment. It may be dropped later,
+depending on the job that this Exim process has been asked to do. For now, set
+the real uid to the effective so that subsequent re-execs of Exim are done by a
+privileged user. */
+
+else
+ exim_setugid(geteuid(), original_egid, FALSE, US"forcing real = effective");
+
+/* If testing a filter, open the file(s) now, before wasting time doing other
+setups and reading the message. */
+
+if (filter_test & FTEST_SYSTEM)
+ if ((filter_sfd = Uopen(filter_test_sfile, O_RDONLY, 0)) < 0)
+ exim_fail("exim: failed to open %s: %s\n", filter_test_sfile,
+ strerror(errno));
+
+if (filter_test & FTEST_USER)
+ if ((filter_ufd = Uopen(filter_test_ufile, O_RDONLY, 0)) < 0)
+ exim_fail("exim: failed to open %s: %s\n", filter_test_ufile,
+ strerror(errno));
+
+/* 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();
+
+/*XXX this excrescence could move to the testsuite standard config setup file */
+#ifdef SUPPORT_I18N
+if (f.running_in_test_harness) smtputf8_advertise_hosts = NULL;
+#endif
+
+/* 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.
+
+NOTE: immediately 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. */
+
+/* Store the initial cwd before we change directories. Can be NULL if the
+dir has already been unlinked. */
+initial_cwd = os_getcwd(NULL, 0);
+if (!initial_cwd && errno)
+ exim_fail("exim: getting initial cwd failed: %s\n", strerror(errno));
+
+if (initial_cwd && (strlen(CCS initial_cwd) >= BIG_BUFFER_SIZE))
+ exim_fail("exim: initial cwd is far too long (%d)\n", Ustrlen(CCS initial_cwd));
+
+/* checking:
+ -be[m] expansion test -
+ -b[fF] filter test new
+ -bh[c] host test -
+ -bmalware malware_test_file new
+ -brt retry test new
+ -brw rewrite test new
+ -bt address test -
+ -bv[s] address verify -
+ list_options:
+ -bP <option> (except -bP config, which sets list_config)
+
+If any of these options is set, we suppress warnings about configuration
+issues (currently about tls_advertise_hosts and keep_environment not being
+defined) */
+
+ {
+ int old_pool = store_pool;
+#ifdef MEASURE_TIMING
+ struct timeval t0;
+ (void)gettimeofday(&t0, NULL);
+#endif
+
+ store_pool = POOL_CONFIG;
+ readconf_main(checking || list_options);
+ store_pool = old_pool;
+
+#ifdef MEASURE_TIMING
+ report_time_since(&t0, US"readconf_main (delta)");
+#endif
+ }
+
+/* Now in directory "/" */
+
+if (cleanup_environment() == FALSE)
+ log_write(0, LOG_PANIC_DIE, "Can't cleanup environment");
+
+
+/* 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)
+ f.admin_user = TRUE;
+else
+ for (int i = 0; i < group_count && !f.admin_user; i++)
+ if (group_list[i] == exim_gid)
+ f.admin_user = TRUE;
+ else if (admin_groups)
+ for (int j = 1; j <= (int)admin_groups[0] && !f.admin_user; j++)
+ if (admin_groups[j] == group_list[i])
+ f.admin_user = TRUE;
+
+/* 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)
+ f.trusted_caller = TRUE;
+else
+ {
+ if (trusted_users)
+ for (int i = 1; i <= (int)trusted_users[0] && !f.trusted_caller; i++)
+ if (trusted_users[i] == real_uid)
+ f.trusted_caller = TRUE;
+
+ if (trusted_groups)
+ for (int i = 1; i <= (int)trusted_groups[0] && !f.trusted_caller; i++)
+ if (trusted_groups[i] == real_gid)
+ f.trusted_caller = TRUE;
+ else for (int j = 0; j < group_count && !f.trusted_caller; j++)
+ if (trusted_groups[i] == group_list[j])
+ f.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 && !f.admin_user)
+ exim_fail("exim: those command-line flags are set to require admin\n");
+
+/* Handle the decoding of logging options. */
+
+decode_bits(log_selector, log_selector_size, log_notall,
+ log_selector_string, log_options, log_options_count, US"log", 0);
+
+DEBUG(D_any)
+ {
+ debug_printf("configuration file is %s\n", config_main_filename);
+ debug_printf("log selectors =");
+ for (int i = 0; i < log_selector_size; i++)
+ debug_printf(" %08x", log_selector[i]);
+ debug_printf("\n");
+ }
+
+/* If domain literals are not allowed, check the sender address that was
+supplied with -f. Ditto for a stripped trailing dot. */
+
+if (sender_address)
+ {
+ if (sender_address[sender_address_domain] == '[' && !allow_domain_literals)
+ exim_fail("exim: bad -f address \"%s\": domain literals not "
+ "allowed\n", sender_address);
+ if (f_end_dot && !strip_trailing_dot)
+ exim_fail("exim: bad -f address \"%s.\": domain is malformed "
+ "(trailing dot not allowed)\n", sender_address);
+ }
+
+/* See if an admin user overrode our logging. */
+
+if (cmdline_syslog_name)
+ if (f.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 */
+ exim_fail(
+ "exim: you lack sufficient privilege to specify syslog process name\n");
+
+/* 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
+get this far but the string is very long, it is better to stop now than to
+carry on and (e.g.) receive a message and then have to collapse. The call to
+log_write() from here will cause the ultimate panic collapse if the complete
+file name exceeds the buffer length. */
+
+if (Ustrlen(log_file_path) > 200)
+ log_write(0, LOG_MAIN|LOG_PANIC_DIE,
+ "log_file_path is longer than 200 chars: aborting");
+
+if (Ustrlen(pid_file_path) > 200)
+ log_write(0, LOG_MAIN|LOG_PANIC_DIE,
+ "pid_file_path is longer than 200 chars: aborting");
+
+if (Ustrlen(spool_directory) > 200)
+ log_write(0, LOG_MAIN|LOG_PANIC_DIE,
+ "spool_directory is longer than 200 chars: aborting");
+
+/* Length check on the process name given to syslog for its TAG field,
+which is only permitted to be 32 characters or less. See RFC 3164. */
+
+if (Ustrlen(syslog_processname) > 32)
+ log_write(0, LOG_MAIN|LOG_PANIC_DIE,
+ "syslog_processname is longer than 32 chars: aborting");
+
+if (log_oneline)
+ if (f.admin_user)
+ {
+ log_write(0, LOG_MAIN, "%s", log_oneline);
+ return EXIT_SUCCESS;
+ }
+ 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
+to MBX mailboxes), but called libraries such as DBM libraries may require them.
+If TMPDIR is found in the environment, reset it to the value defined in the
+EXIM_TMPDIR macro, if this macro is defined. For backward compatibility this
+macro may be called TMPDIR in old "Local/Makefile"s. It's converted to
+EXIM_TMPDIR by the build scripts.
+*/
+
+#ifdef EXIM_TMPDIR
+ if (environ) for (uschar ** p = USS environ; *p; p++)
+ if (Ustrncmp(*p, "TMPDIR=", 7) == 0 && Ustrcmp(*p+7, EXIM_TMPDIR) != 0)
+ {
+ 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);
+ }
+#endif
+
+/* Timezone handling. If timezone_string is "utc", set a flag to cause all
+timestamps to be in UTC (gmtime() is used instead of localtime()). Otherwise,
+we may need to get rid of a bogus timezone setting. This can arise when Exim is
+called by a user who has set the TZ variable. This then affects the timestamps
+in log files and in Received: headers, and any created Date: header lines. The
+required timezone is settable in the configuration file, so nothing can be done
+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. */
+
+if (timezone_string && strcmpic(timezone_string, US"UTC") == 0)
+ f.timestamps_utc = TRUE;
+else
+ {
+ uschar *envtz = US getenv("TZ");
+ if (envtz
+ ? !timezone_string || Ustrcmp(timezone_string, envtz) != 0
+ : timezone_string != NULL
+ )
+ {
+ uschar **p = USS environ;
+ uschar **new;
+ uschar **newp;
+ int count = 0;
+ 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 = store_malloc(Ustrlen(timezone_string) + 4);
+ sprintf(CS *newp++, "TZ=%s", timezone_string);
+ }
+ *newp = NULL;
+ environ = CSS new;
+ tzset();
+ DEBUG(D_any) debug_printf("Reset TZ to %s: time is %s\n", timezone_string,
+ tod_stamp(tod_log));
+ }
+ }
+
+/* Handle the case when we have removed the setuid privilege because of -C or
+-D. This means that the caller of Exim was not root.
+
+There is a problem if we were running as the Exim user. The sysadmin may
+expect this case to retain privilege because "the binary was called by the
+Exim user", but it hasn't, because either the -D option set macros, or the
+-C option set a non-trusted configuration file. There are two possibilities:
+
+ (1) If deliver_drop_privilege is set, Exim is not going to re-exec in order
+ to do message deliveries. Thus, the fact that it is running as a
+ non-privileged user is plausible, and might be wanted in some special
+ configurations. However, really_exim will have been set false when
+ privilege was dropped, to stop Exim trying to write to its normal log
+ files. Therefore, re-enable normal log processing, assuming the sysadmin
+ has set up the log directory correctly.
+
+ (2) If deliver_drop_privilege is not set, the configuration won't work as
+ apparently intended, and so we log a panic message. In order to retain
+ 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
+ && (!f.trusted_config || opt_D_used)
+ && real_uid == exim_uid)
+ if (deliver_drop_privilege)
+ f.really_exim = TRUE; /* let logging work normally */
+ else
+ log_write(0, LOG_MAIN|LOG_PANIC,
+ "exim user lost privilege for using %s option",
+ f.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
+initializing starting. Note that the global variables are actually called
+opt_perl_xxx to avoid clashing with perl's namespace (perl_*). */
+
+#ifdef EXIM_PERL
+if (perl_start_option != 0)
+ opt_perl_at_start = (perl_start_option > 0);
+if (opt_perl_at_start && opt_perl_startup != NULL)
+ {
+ uschar *errstr;
+ DEBUG(D_any) debug_printf("Starting Perl interpreter\n");
+ if ((errstr = init_perl(opt_perl_startup)))
+ exim_fail("exim: error in perl_startup code: %s\n", errstr);
+ opt_perl_started = TRUE;
+ }
+#endif /* EXIM_PERL */
+
+/* Log the arguments of the call if the configuration file said so. This is
+a debugging feature for finding out what arguments certain MUAs actually use.
+Don't attempt it if logging is disabled, or if listing variables or if
+verifying/testing addresses or expansions. */
+
+if ( (debug_selector & D_any || LOGGING(arguments))
+ && f.really_exim && !list_options && !checking)
+ {
+ uschar *p = big_buffer;
+ Ustrcpy(p, US"cwd= (failed)");
+
+ if (!initial_cwd)
+ p += 13;
+ else
+ {
+ p += 4;
+ snprintf(CS p, big_buffer_size - (p - big_buffer), "%s", CCS initial_cwd);
+ p += Ustrlen(CCS p);
+ }
+
+ (void)string_format(p, big_buffer_size - (p - big_buffer), " %d args:", argc);
+ while (*p) p++;
+ for (int i = 0; i < argc; i++)
+ {
+ int len = Ustrlen(argv[i]);
+ const uschar *printing;
+ uschar *quote;
+ if (p + len + 8 >= big_buffer + big_buffer_size)
+ {
+ Ustrcpy(p, US" ...");
+ log_write(0, LOG_MAIN, "%s", big_buffer);
+ Ustrcpy(big_buffer, US"...");
+ p = big_buffer + 3;
+ }
+ printing = string_printing(argv[i]);
+ if (!*printing) quote = US"\"";
+ else
+ {
+ const uschar *pp = printing;
+ quote = US"";
+ while (*pp) if (isspace(*pp++)) { quote = US"\""; break; }
+ }
+ p += sprintf(CS p, " %s%.*s%s", quote, (int)(big_buffer_size -
+ (p - big_buffer) - 4), printing, quote);
+ }
+
+ if (LOGGING(arguments))
+ log_write(0, LOG_MAIN, "%s", big_buffer);
+ else
+ debug_printf("%s\n", big_buffer);
+ }
+
+/* Set the working directory to be the top-level spool directory. We don't rely
+on this in the code, which always uses fully qualified names, but it's useful
+for core dumps etc. Don't complain if it fails - the spool directory might not
+be generally accessible and calls with the -C option (and others) have lost
+privilege by now. Before the chdir, we try to ensure that the directory exists.
+*/
+
+if (Uchdir(spool_directory) != 0)
+ {
+ (void) directory_make(spool_directory, US"", SPOOL_DIRECTORY_MODE, FALSE);
+ (void) Uchdir(spool_directory);
+ }
+
+/* Handle calls with the -bi option. This is a sendmail option to rebuild *the*
+alias file. Exim doesn't have such a concept, but this call is screwed into
+Sun's YP makefiles. Handle this by calling a configured script, as the real
+user who called Exim. The -oA option can be used to pass an argument to the
+script. */
+
+if (bi_option)
+ {
+ (void) fclose(config_file);
+ if (bi_command && *bi_command)
+ {
+ int i = 0;
+ uschar *argv[3];
+ argv[i++] = bi_command; /* nonexpanded option so assume untainted */
+ if (alias_arg) argv[i++] = alias_arg;
+ argv[i++] = NULL;
+
+ setgroups(group_count, group_list);
+ exim_setugid(real_uid, real_gid, FALSE, US"running bi_command");
+
+ DEBUG(D_exec) debug_printf("exec '%.256s' %s%.256s%s\n", argv[0],
+ argv[1] ? "'" : "", argv[1] ? argv[1] : US"", argv[1] ? "'" : "");
+
+ execv(CS argv[0], (char *const *)argv);
+ exim_fail("exim: exec '%s' failed: %s\n", argv[0], strerror(errno));
+ }
+ else
+ {
+ DEBUG(D_any) debug_printf("-bi used but bi_command not set; exiting\n");
+ exit(EXIT_SUCCESS);
+ }
+ }
+
+/* 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 (f.trusted_caller) DEBUG(D_any) debug_printf("trusted user\n");
+if (f.admin_user) DEBUG(D_any) debug_printf("admin user\n");
+
+/* Only an admin user may start the daemon or force a queue run in the default
+configuration, but the queue run restriction can be relaxed. Only an admin
+user may request that a message be returned to its sender forthwith. Only an
+admin user may specify a debug level greater than D_v (because it might show
+passwords, etc. in lookup queries). Only an admin user may request a queue
+count. Only an admin user can use the test interface to scan for email
+(because Exim will be in the spool dir and able to look at mails). */
+
+if (!f.admin_user)
+ {
+ BOOL debugset = (debug_selector & ~D_v) != 0;
+ if ( deliver_give_up || f.daemon_listen || malware_test_file
+ || count_queue && queue_list_requires_admin
+ || list_queue && queue_list_requires_admin
+ || queue_interval >= 0 && prod_requires_admin
+ || queue_name_dest && prod_requires_admin
+ || debugset && !f.running_in_test_harness
+ )
+ exim_fail("exim:%s permission denied\n", debugset ? " debugging" : "");
+ }
+
+/* If the real user is not root or the exim uid, the argument for passing
+in an open TCP/IP connection for another message is not permitted, nor is
+running with the -N option for any delivery action, unless this call to exim is
+one that supplied an input message, or we are using a patched exim for
+regression testing. */
+
+if ( real_uid != root_uid && real_uid != exim_uid
+ && ( continue_hostname
+ || ( f.dont_deliver
+ && (queue_interval >= 0 || f.daemon_listen || msg_action_arg > 0)
+ ) )
+ && !f.running_in_test_harness
+ )
+ exim_fail("exim: Permission denied\n");
+
+/* If the caller is not trusted, certain arguments are ignored when running for
+real, but are permitted when checking things (-be, -bv, -bt, -bh, -bf, -bF).
+Note that authority for performing certain actions on messages is tested in the
+queue_action() function. */
+
+if (!f.trusted_caller && !checking)
+ {
+ sender_host_name = sender_host_address = interface_address =
+ sender_ident = received_protocol = NULL;
+ sender_host_port = interface_port = 0;
+ sender_host_authenticated = authenticated_sender = authenticated_id = NULL;
+ }
+
+/* If a sender host address is set, extract the optional port number off the
+end of it and check its syntax. Do the same thing for the interface address.
+Exim exits if the syntax is bad. */
+
+else
+ {
+ if (sender_host_address)
+ sender_host_port = check_port(sender_host_address);
+ if (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 (f.trusted_caller)
+ {
+ f.suppress_local_fixups = f.suppress_local_fixups_default = TRUE;
+ DEBUG(D_acl) debug_printf("suppress_local_fixups forced on by -G\n");
+ }
+ else
+ exim_fail("exim: permission denied (-G requires a trusted user)\n");
+ }
+
+/* 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,
+barf. */
+
+if (smtp_input)
+ {
+ union sockaddr_46 inetd_sock;
+ EXIM_SOCKLEN_T size = sizeof(inetd_sock);
+ if (getpeername(0, (struct sockaddr *)(&inetd_sock), &size) == 0)
+ {
+ int family = ((struct sockaddr *)(&inetd_sock))->sa_family;
+ if (family == AF_INET || family == AF_INET6)
+ {
+ union sockaddr_46 interface_sock;
+ size = sizeof(interface_sock);
+
+ if (getsockname(0, (struct sockaddr *)(&interface_sock), &size) == 0)
+ interface_address = host_ntoa(-1, &interface_sock, NULL,
+ &interface_port);
+
+ 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)
+ {
+ f.is_inetd = TRUE;
+ sender_host_address = host_ntoa(-1, (struct sockaddr *)(&inetd_sock),
+ NULL, &sender_host_port);
+ if (mua_wrapper) log_write(0, LOG_MAIN|LOG_PANIC_DIE, "Input from "
+ "inetd is not supported when mua_wrapper is set");
+ }
+ else
+ exim_fail(
+ "exim: Permission denied (unprivileged user, unprivileged port)\n");
+ }
+ }
+ }
+
+/* If the load average is going to be needed while receiving a message, get it
+now for those OS that require the first call to os_getloadavg() to be done as
+root. There will be further calls later for each message received. */
+
+#ifdef LOAD_AVG_NEEDS_ROOT
+if ( receiving_message
+ && (queue_only_load >= 0 || (f.is_inetd && smtp_load_reserve >= 0)))
+ load_average = OS_GETLOADAVG();
+#endif
+
+/* The queue_only configuration option can be overridden by -odx on the command
+line, except that if queue_only_override is false, queue_only cannot be unset
+from the command line. */
+
+if (queue_only_set && (queue_only_override || arg_queue_only))
+ queue_only = arg_queue_only;
+
+/* The receive_timeout and smtp_receive_timeout options can be overridden by
+-or and -os. */
+
+if (arg_receive_timeout >= 0) receive_timeout = arg_receive_timeout;
+if (arg_smtp_receive_timeout >= 0)
+ smtp_receive_timeout = arg_smtp_receive_timeout;
+
+/* If Exim was started with root privilege, unless we have already removed the
+root privilege above as a result of -C, -D, -be, -bf or -bF, remove it now
+except when starting the daemon or doing some kind of delivery or address
+testing (-bt). These are the only cases when root need to be retained. We run
+as exim for -bv and -bh. However, if deliver_drop_privilege is set, root is
+retained only for starting the daemon. We always do the initgroups() in this
+situation (controlled by the TRUE below), in order to be as close as possible
+to the state Exim usually runs in. */
+
+if ( !unprivileged /* originally had root AND */
+ && !removed_privilege /* still got root AND */
+ && !f.daemon_listen /* not starting the daemon */
+ && queue_interval <= 0 /* (either kind of daemon) */
+ && ( /* AND EITHER */
+ deliver_drop_privilege /* requested unprivileged */
+ || ( /* OR */
+ queue_interval < 0 /* not running the queue */
+ && ( msg_action_arg < 0 /* and */
+ || msg_action != MSG_DELIVER /* not delivering */
+ ) /* and */
+ && (!checking || !f.address_test_mode) /* not address checking */
+ && !rcpt_verify_quota /* and not quota checking */
+ ) ) )
+ 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. */
+
+else
+ {
+ int rv;
+ DEBUG(D_any) debug_printf("dropping to exim gid; retaining priv uid\n");
+ 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))
+ exim_fail("exim: changing group failed: %s\n", 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 */
+if (malware_test_file)
+ {
+#ifdef WITH_CONTENT_SCAN
+ int result;
+ set_process_info("scanning file for malware");
+ if ((result = malware_in_file(malware_test_file)) == FAIL)
+ {
+ printf("No malware found.\n");
+ exit(EXIT_SUCCESS);
+ }
+ if (result != OK)
+ {
+ printf("Malware lookup returned non-okay/fail: %d\n", result);
+ exit(EXIT_FAILURE);
+ }
+ if (malware_name)
+ printf("Malware found: %s\n", malware_name);
+ else
+ printf("Malware scan detected malware of unknown name.\n");
+#else
+ printf("Malware scanning not enabled at compile time.\n");
+#endif
+ exit(EXIT_FAILURE);
+ }
+
+/* Handle a request to list the delivery queue */
+
+if (list_queue)
+ {
+ set_process_info("listing the queue");
+ queue_list(list_queue_option, argv + recipients_arg, argc - recipients_arg);
+ exit(EXIT_SUCCESS);
+ }
+
+/* Handle a request to count the delivery queue */
+
+if (count_queue)
+ {
+ set_process_info("counting the queue");
+ fprintf(stdout, "%u\n", queue_count());
+ exit(EXIT_SUCCESS);
+ }
+
+/* Handle actions on specific messages, except for the force delivery and
+message load actions, which are done below. Some actions take a whole list of
+message ids, which are known to continue up to the end of the arguments. Others
+take a single message id and then operate on the recipients list. */
+
+if (msg_action_arg > 0 && msg_action != MSG_DELIVER && msg_action != MSG_LOAD)
+ {
+ int yield = EXIT_SUCCESS;
+ set_process_info("acting on specified messages");
+
+ /* ACL definitions may be needed when removing a message (-Mrm) because
+ event_action gets expanded */
+
+ if (msg_action == MSG_REMOVE)
+ {
+ int old_pool = store_pool;
+ store_pool = POOL_CONFIG;
+ readconf_rest();
+ store_pool = old_pool;
+ store_writeprotect(POOL_CONFIG);
+ }
+
+ if (!one_msg_action)
+ {
+ for (i = msg_action_arg; i < argc; i++)
+ if (!queue_action(argv[i], msg_action, NULL, 0, 0))
+ yield = EXIT_FAILURE;
+ switch (msg_action)
+ {
+ case MSG_REMOVE: case MSG_FREEZE: case MSG_THAW: break;
+ default: printf("\n"); break;
+ }
+ }
+
+ else if (!queue_action(argv[msg_action_arg], msg_action, argv, argc,
+ recipients_arg)) yield = EXIT_FAILURE;
+ exit(yield);
+ }
+
+/* We used to set up here to skip reading the ACL section, on
+ (msg_action_arg > 0 || (queue_interval == 0 && !f.daemon_listen)
+Now, since the intro of the ${acl } expansion, ACL definitions may be
+needed in transports so we lost the optimisation. */
+
+ {
+ int old_pool = store_pool;
+#ifdef MEASURE_TIMING
+ struct timeval t0;
+ (void)gettimeofday(&t0, NULL);
+#endif
+
+ store_pool = POOL_CONFIG;
+ readconf_rest();
+ store_pool = old_pool;
+
+ /* -be can add macro definitions, needing to link to the macro structure
+ chain. Otherwise, make the memory used for config data readonly. */
+
+ if (!expansion_test)
+ store_writeprotect(POOL_CONFIG);
+
+#ifdef MEASURE_TIMING
+ report_time_since(&t0, US"readconf_rest (delta)");
+#endif
+ }
+
+/* Handle a request to check quota */
+if (rcpt_verify_quota)
+ if (real_uid != root_uid && real_uid != exim_uid)
+ exim_fail("exim: Permission denied\n");
+ else if (recipients_arg >= argc)
+ exim_fail("exim: missing recipient for quota check\n");
+ else
+ {
+ verify_quota(argv[recipients_arg]);
+ exim_exit(EXIT_SUCCESS);
+ }
+
+/* Handle the -brt option. This is for checking out retry configurations.
+The next three arguments are a domain name or a complete address, and
+optionally two error numbers. All it does is to call the function that
+scans the retry configuration data. */
+
+if (test_retry_arg >= 0)
+ {
+ retry_config *yield;
+ int basic_errno = 0;
+ int more_errno = 0;
+ const uschar *s1, *s2;
+
+ if (test_retry_arg >= argc)
+ {
+ printf("-brt needs a domain or address argument\n");
+ exim_exit(EXIT_FAILURE);
+ }
+ s1 = exim_str_fail_toolong(argv[test_retry_arg++], EXIM_EMAILADDR_MAX, "-brt");
+ s2 = NULL;
+
+ /* If the first argument contains no @ and no . it might be a local user
+ or it might be a single-component name. Treat as a domain. */
+
+ if (Ustrchr(s1, '@') == NULL && Ustrchr(s1, '.') == NULL)
+ {
+ printf("Warning: \"%s\" contains no '@' and no '.' characters. It is "
+ "being \ntreated as a one-component domain, not as a local part.\n\n",
+ s1);
+ }
+
+ /* There may be an optional second domain arg. */
+
+ if (test_retry_arg < argc && Ustrchr(argv[test_retry_arg], '.') != NULL)
+ s2 = exim_str_fail_toolong(argv[test_retry_arg++], EXIM_DOMAINNAME_MAX, "-brt 2nd");
+
+ /* The final arg is an error name */
+
+ if (test_retry_arg < argc)
+ {
+ const uschar *ss = exim_str_fail_toolong(argv[test_retry_arg], EXIM_DRIVERNAME_MAX, "-brt 3rd");
+ uschar *error =
+ readconf_retry_error(ss, ss + Ustrlen(ss), &basic_errno, &more_errno);
+ if (error != NULL)
+ {
+ printf("%s\n", CS error);
+ return EXIT_FAILURE;
+ }
+
+ /* For the {MAIL,RCPT,DATA}_4xx errors, a value of 255 means "any", and a
+ code > 100 as an error is for matching codes to the decade. Turn them into
+ a real error code, off the decade. */
+
+ if (basic_errno == ERRNO_MAIL4XX ||
+ basic_errno == ERRNO_RCPT4XX ||
+ basic_errno == ERRNO_DATA4XX)
+ {
+ int code = (more_errno >> 8) & 255;
+ if (code == 255)
+ more_errno = (more_errno & 0xffff00ff) | (21 << 8);
+ else if (code > 100)
+ more_errno = (more_errno & 0xffff00ff) | ((code - 96) << 8);
+ }
+ }
+
+ if (!(yield = retry_find_config(s1, s2, basic_errno, more_errno)))
+ printf("No retry information found\n");
+ else
+ {
+ more_errno = yield->more_errno;
+ printf("Retry rule: %s ", yield->pattern);
+
+ if (yield->basic_errno == ERRNO_EXIMQUOTA)
+ {
+ printf("quota%s%s ",
+ (more_errno > 0)? "_" : "",
+ (more_errno > 0)? readconf_printtime(more_errno) : US"");
+ }
+ else if (yield->basic_errno == ECONNREFUSED)
+ {
+ printf("refused%s%s ",
+ (more_errno > 0)? "_" : "",
+ (more_errno == 'M')? "MX" :
+ (more_errno == 'A')? "A" : "");
+ }
+ else if (yield->basic_errno == ETIMEDOUT)
+ {
+ printf("timeout");
+ if ((more_errno & RTEF_CTOUT) != 0) printf("_connect");
+ more_errno &= 255;
+ if (more_errno != 0) printf("_%s",
+ (more_errno == 'M')? "MX" : "A");
+ printf(" ");
+ }
+ else if (yield->basic_errno == ERRNO_AUTHFAIL)
+ printf("auth_failed ");
+ else printf("* ");
+
+ for (retry_rule * 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 */
+ if (r->rule == 'G')
+ {
+ int x = r->p2;
+ int f = x % 1000;
+ int d = 100;
+ printf(",%d.", x/1000);
+ do
+ {
+ printf("%d", f/d);
+ f %= d;
+ d /= 10;
+ }
+ while (f != 0);
+ }
+ printf("; ");
+ }
+
+ printf("\n");
+ }
+ exim_exit(EXIT_SUCCESS);
+ }
+
+/* Handle a request to list one or more configuration options */
+/* If -n was set, we suppress some information */
+
+if (list_options)
+ {
+ BOOL fail = FALSE;
+ set_process_info("listing variables");
+ if (recipients_arg >= argc)
+ fail = !readconf_print(US"all", NULL, flag_n);
+ else for (i = recipients_arg; i < argc; i++)
+ {
+ if (i < argc - 1 &&
+ (Ustrcmp(argv[i], "router") == 0 ||
+ Ustrcmp(argv[i], "transport") == 0 ||
+ Ustrcmp(argv[i], "authenticator") == 0 ||
+ Ustrcmp(argv[i], "macro") == 0 ||
+ Ustrcmp(argv[i], "environment") == 0))
+ {
+ fail |= !readconf_print(exim_str_fail_toolong(argv[i+1], EXIM_DRIVERNAME_MAX, "-bP name"), argv[i], flag_n);
+ i++;
+ }
+ else
+ fail = !readconf_print(exim_str_fail_toolong(argv[i], EXIM_DRIVERNAME_MAX, "-bP item"), NULL, flag_n);
+ }
+ exim_exit(fail ? EXIT_FAILURE : EXIT_SUCCESS);
+ }
+
+if (list_config)
+ {
+ set_process_info("listing config");
+ exim_exit(readconf_print(US"config", NULL, flag_n)
+ ? EXIT_SUCCESS : EXIT_FAILURE);
+ }
+
+
+/* Initialise subsystems as required. */
+
+tcp_init();
+
+/* Handle a request to deliver one or more messages that are already on the
+queue. Values of msg_action other than MSG_DELIVER and MSG_LOAD are dealt with
+above. MSG_LOAD is handled with -be (which is the only time it applies) below.
+
+Delivery of specific messages is typically used for a small number when
+prodding by hand (when the option forced_delivery will be set) or when
+re-execing to regain root privilege. Each message delivery must happen in a
+separate process, so we fork a process for each one, and run them sequentially
+so that debugging output doesn't get intertwined, and to avoid spawning too
+many processes if a long list is given. However, don't fork for the last one;
+this saves a process in the common case when Exim is called to deliver just one
+message. */
+
+if (msg_action_arg > 0 && msg_action != MSG_LOAD)
+ {
+ if (prod_requires_admin && !f.admin_user)
+ {
+ fprintf(stderr, "exim: Permission denied\n");
+ exim_exit(EXIT_FAILURE);
+ }
+ set_process_info("delivering specified messages");
+ if (deliver_give_up) forced_delivery = f.deliver_force_thaw = TRUE;
+ for (i = msg_action_arg; i < argc; i++)
+ {
+ int status;
+ pid_t pid;
+ /*XXX This use of argv[i] for msg_id should really be tainted, but doing
+ that runs into a later copy into the untainted global message_id[] */
+ /*XXX Do we need a length limit check here? */
+ if (i == argc - 1)
+ (void)deliver_message(argv[i], forced_delivery, deliver_give_up);
+ else if ((pid = exim_fork(US"cmdline-delivery")) == 0)
+ {
+ (void)deliver_message(argv[i], forced_delivery, deliver_give_up);
+ exim_underbar_exit(EXIT_SUCCESS);
+ }
+ else if (pid < 0)
+ {
+ fprintf(stderr, "failed to fork delivery process for %s: %s\n", argv[i],
+ strerror(errno));
+ exim_exit(EXIT_FAILURE);
+ }
+ else wait(&status);
+ }
+ exim_exit(EXIT_SUCCESS);
+ }
+
+
+/* If only a single queue run is requested, without SMTP listening, we can just
+turn into a queue runner, with an optional starting message id. */
+
+if (queue_interval == 0 && !f.daemon_listen)
+ {
+ DEBUG(D_queue_run) debug_printf("Single queue run%s%s%s%s\n",
+ start_queue_run_id ? US" starting at " : US"",
+ start_queue_run_id ? start_queue_run_id: US"",
+ stop_queue_run_id ? US" stopping at " : US"",
+ stop_queue_run_id ? stop_queue_run_id : US"");
+ if (*queue_name)
+ set_process_info("running the '%s' queue (single queue run)", queue_name);
+ 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);
+ }
+
+
+/* Find the login name of the real user running this process. This is always
+needed when receiving a message, because it is written into the spool file. It
+may also be used to construct a from: or a sender: header, and in this case we
+need the user's full name as well, so save a copy of it, checked for RFC822
+syntax and munged if necessary, if it hasn't previously been set by the -F
+argument. We may try to get the passwd entry more than once, in case NIS or
+other delays are in evidence. Save the home directory for use in filter testing
+(only). */
+
+for (i = 0;;)
+ {
+ if ((pw = getpwuid(real_uid)) != NULL)
+ {
+ originator_login = string_copy(US pw->pw_name);
+ originator_home = string_copy(US pw->pw_dir);
+
+ /* 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)
+ {
+ if (!sender_address || (!f.trusted_caller && filter_test == FTEST_NONE))
+ {
+ uschar *name = US pw->pw_gecos;
+ uschar *amp = Ustrchr(name, '&');
+ uschar buffer[256];
+
+ /* Most Unix specify that a '&' character in the gecos field is
+ 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)
+ {
+ int loffset;
+ string_format(buffer, sizeof(buffer), "%.*s%n%s%s",
+ (int)(amp - name), name, &loffset, originator_login, amp + 1);
+ buffer[loffset] = toupper(buffer[loffset]);
+ name = buffer;
+ }
+
+ /* If a pattern for matching the gecos field was supplied, apply
+ it and then expand the name string. */
+
+ if (gecos_pattern && gecos_name)
+ {
+ const pcre2_code *re;
+ re = regex_must_compile(gecos_pattern, FALSE, TRUE); /* Use malloc */
+
+ if (regex_match_and_setup(re, name, 0, -1))
+ {
+ uschar *new_name = expand_string(gecos_name);
+ expand_nmax = -1;
+ if (new_name)
+ {
+ DEBUG(D_receive) debug_printf("user name \"%s\" extracted from "
+ "gecos field \"%s\"\n", new_name, name);
+ name = new_name;
+ }
+ else DEBUG(D_receive) debug_printf("failed to expand gecos_name string "
+ "\"%s\": %s\n", gecos_name, expand_string_message);
+ }
+ else DEBUG(D_receive) debug_printf("gecos_pattern \"%s\" did not match "
+ "gecos field \"%s\"\n", gecos_pattern, name);
+ store_free((void *)re);
+ }
+ originator_name = string_copy(name);
+ }
+
+ /* A trusted caller has used -f but not -F */
+
+ else originator_name = US"";
+ }
+
+ /* Break the retry loop */
+
+ break;
+ }
+
+ if (++i > finduser_retries) break;
+ sleep(1);
+ }
+
+/* If we cannot get a user login, log the incident and give up, unless the
+configuration specifies something to use. When running in the test harness,
+any setting of unknown_login overrides the actual name. */
+
+if (!originator_login || f.running_in_test_harness)
+ {
+ if (unknown_login)
+ {
+ originator_login = expand_string(unknown_login);
+ if (!originator_name && unknown_username)
+ originator_name = expand_string(unknown_username);
+ if (!originator_name) originator_name = US"";
+ }
+ if (!originator_login)
+ log_write(0, LOG_MAIN|LOG_PANIC_DIE, "Failed to get user name for uid %d",
+ (int)real_uid);
+ }
+
+/* Ensure that the user name is in a suitable form for use as a "phrase" in an
+RFC822 address.*/
+
+originator_name = US parse_fix_phrase(originator_name, Ustrlen(originator_name));
+
+/* If a message is created by this call of Exim, the uid/gid of its originator
+are those of the caller. These values are overridden if an existing message is
+read in from the spool. */
+
+originator_uid = real_uid;
+originator_gid = real_gid;
+
+DEBUG(D_receive) debug_printf("originator: uid=%d gid=%d login=%s name=%s\n",
+ (int)originator_uid, (int)originator_gid, originator_login, originator_name);
+
+/* Run in daemon and/or queue-running mode. The function daemon_go() never
+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. */
+
+if (f.daemon_listen || f.inetd_wait_mode || queue_interval > 0)
+ {
+ if (mua_wrapper)
+ {
+ fprintf(stderr, "Daemon cannot be run when mua_wrapper is set\n");
+ log_write(0, LOG_MAIN|LOG_PANIC_DIE, "Daemon cannot be run when "
+ "mua_wrapper is set");
+ }
+
+# ifndef DISABLE_TLS
+ /* This also checks that the library linkage is working and we can call
+ routines in it, so call even if tls_require_ciphers is unset */
+ {
+# ifdef MEASURE_TIMING
+ struct timeval t0;
+ (void)gettimeofday(&t0, NULL);
+# endif
+ if (!tls_dropprivs_validate_require_cipher(FALSE))
+ exit(1);
+# ifdef MEASURE_TIMING
+ report_time_since(&t0, US"validate_ciphers (delta)");
+# endif
+ }
+#endif
+
+ daemon_go();
+ }
+
+/* If the sender ident has not been set (by a trusted caller) set it to
+the caller. This will get overwritten below for an inetd call. If a trusted
+caller has set it empty, unset it. */
+
+if (!sender_ident) sender_ident = originator_login;
+else if (!*sender_ident) sender_ident = NULL;
+
+/* Handle the -brw option, which is for checking out rewriting rules. Cause log
+writes (on errors) to go to stderr instead. Can't do this earlier, as want the
+originator_* variables set. */
+
+if (test_rewrite_arg >= 0)
+ {
+ f.really_exim = FALSE;
+ if (test_rewrite_arg >= argc)
+ {
+ printf("-brw needs an address argument\n");
+ exim_exit(EXIT_FAILURE);
+ }
+ rewrite_test(exim_str_fail_toolong(argv[test_rewrite_arg], EXIM_EMAILADDR_MAX, "-brw"));
+ exim_exit(EXIT_SUCCESS);
+ }
+
+/* A locally-supplied message is considered to be coming from a local user
+unless a trusted caller supplies a sender address with -f, or is passing in the
+message via SMTP (inetd invocation or otherwise). */
+
+if ( !sender_address && !smtp_input
+ || !f.trusted_caller && filter_test == FTEST_NONE)
+ {
+ f.sender_local = TRUE;
+
+ /* A trusted caller can supply authenticated_sender and authenticated_id
+ via -oMas and -oMai and if so, they will already be set. Otherwise, force
+ defaults except when host checking. */
+
+ if (!authenticated_sender && !host_checking)
+ authenticated_sender = string_sprintf("%s@%s", originator_login,
+ qualify_domain_sender);
+ if (!authenticated_id && !host_checking)
+ authenticated_id = originator_login;
+ }
+
+/* Trusted callers are always permitted to specify the sender address.
+Untrusted callers may specify it if it matches untrusted_set_sender, or if what
+is specified is the empty address. However, if a trusted caller does not
+specify a sender address for SMTP input, we leave sender_address unset. This
+causes the MAIL commands to be honoured. */
+
+if ( !smtp_input && !sender_address
+ || !receive_check_set_sender(sender_address))
+ {
+ /* Either the caller is not permitted to set a general sender, or this is
+ non-SMTP input and the trusted caller has not set a sender. If there is no
+ sender, or if a sender other than <> is set, override with the originator's
+ login (which will get qualified below), except when checking things. */
+
+ if ( !sender_address /* No sender_address set */
+ || /* OR */
+ (sender_address[0] != 0 && /* Non-empty sender address, AND */
+ !checking)) /* Not running tests, including filter tests */
+ {
+ sender_address = originator_login;
+ f.sender_address_forced = FALSE;
+ sender_address_domain = 0;
+ }
+ }
+
+/* Remember whether an untrusted caller set the sender address */
+
+f.sender_set_untrusted = sender_address != originator_login && !f.trusted_caller;
+
+/* Ensure that the sender address is fully qualified unless it is the empty
+address, which indicates an error message, or doesn't exist (root caller, smtp
+interface, no -f argument). */
+
+if (sender_address && *sender_address && sender_address_domain == 0)
+ sender_address = string_sprintf("%s@%s", local_part_quote(sender_address),
+ qualify_domain_sender);
+
+DEBUG(D_receive) debug_printf("sender address = %s\n", sender_address);
+
+/* Handle a request to verify a list of addresses, or test them for delivery.
+This must follow the setting of the sender address, since routers can be
+predicated upon the sender. If no arguments are given, read addresses from
+stdin. Set debug_level to at least D_v to get full output for address testing.
+*/
+
+if (verify_address_mode || f.address_test_mode)
+ {
+ int exit_value = 0;
+ int flags = vopt_qualify;
+
+ if (verify_address_mode)
+ {
+ if (!verify_as_sender) flags |= vopt_is_recipient;
+ DEBUG(D_verify) debug_print_ids(US"Verifying:");
+ }
+
+ else
+ {
+ flags |= vopt_is_recipient;
+ debug_selector |= D_v;
+ debug_file = stderr;
+ debug_fd = fileno(debug_file);
+ DEBUG(D_verify) debug_print_ids(US"Address testing:");
+ }
+
+ if (recipients_arg < argc)
+ while (recipients_arg < argc)
+ {
+ /* Supplied addresses are tainted since they come from a user */
+ uschar * s = string_copy_taint(
+ exim_str_fail_toolong(argv[recipients_arg++], EXIM_DISPLAYMAIL_MAX, "address verification"),
+ GET_TAINTED);
+ while (*s)
+ {
+ BOOL finished = FALSE;
+ uschar *ss = parse_find_address_end(s, FALSE);
+ if (*ss == ',') *ss = 0; else finished = TRUE;
+ test_address(s, flags, &exit_value);
+ s = ss;
+ if (!finished)
+ while (*++s == ',' || isspace(*s)) ;
+ }
+ }
+
+ else for (;;)
+ {
+ uschar * s = get_stdinput(NULL, NULL);
+ if (!s) break;
+ test_address(string_copy_taint(
+ exim_str_fail_toolong(s, EXIM_DISPLAYMAIL_MAX, "address verification (stdin)"),
+ GET_TAINTED),
+ flags, &exit_value);
+ }
+
+ route_tidyup();
+ exim_exit(exit_value);
+ }
+
+/* Handle expansion checking. Either expand items on the command line, or read
+from stdin if there aren't any. If -Mset was specified, load the message so
+that its variables can be used, but restrict this facility to admin users.
+Otherwise, if -bem was used, read a message from stdin. */
+
+if (expansion_test)
+ {
+ dns_init(FALSE, FALSE, FALSE);
+ if (msg_action_arg > 0 && msg_action == MSG_LOAD)
+ {
+ uschar * spoolname;
+ if (!f.admin_user)
+ exim_fail("exim: permission denied\n");
+ message_id = US exim_str_fail_toolong(argv[msg_action_arg], MESSAGE_ID_LENGTH, "message-id");
+ /* Checking the length of the ID is sufficient to validate it.
+ Get an untainted version so file opens can be done. */
+ message_id = string_copy_taint(message_id, GET_UNTAINTED);
+
+ spoolname = string_sprintf("%s-H", message_id);
+ if ((deliver_datafile = spool_open_datafile(message_id)) < 0)
+ printf ("Failed to load message datafile %s\n", message_id);
+ if (spool_read_header(spoolname, TRUE, FALSE) != spool_read_OK)
+ printf ("Failed to load message %s\n", message_id);
+ }
+
+ /* 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)
+ {
+ int save_stdin = dup(0);
+ int fd = Uopen(expansion_test_message, O_RDONLY, 0);
+ if (fd < 0)
+ exim_fail("exim: failed to open %s: %s\n", expansion_test_message,
+ strerror(errno));
+ (void) dup2(fd, 0);
+ filter_test = FTEST_USER; /* Fudge to make it look like filter test */
+ message_ended = END_NOTENDED;
+ read_message_body(receive_msg(extract_recipients));
+ message_linecount += body_linecount;
+ (void)dup2(save_stdin, 0);
+ (void)close(save_stdin);
+ clearerr(stdin); /* Required by Darwin */
+ }
+
+ /* Only admin users may see config-file macros this way */
+
+ if (!f.admin_user) macros_user = macros = mlast = NULL;
+
+ /* Allow $recipients for this testing */
+
+ f.enable_dollar_recipients = TRUE;
+
+ /* Expand command line items */
+
+ if (recipients_arg < argc)
+ while (recipients_arg < argc)
+ expansion_test_line(exim_str_fail_toolong(argv[recipients_arg++], EXIM_EMAILADDR_MAX, "recipient"));
+
+ /* Read stdin */
+
+ else
+ {
+ char *(*fn_readline)(const char *) = NULL;
+ void (*fn_addhist)(const char *) = NULL;
+ uschar * s;
+
+#ifdef USE_READLINE
+ void *dlhandle = set_readline(&fn_readline, &fn_addhist);
+#endif
+
+ while (s = get_stdinput(fn_readline, fn_addhist))
+ expansion_test_line(s);
+
+#ifdef USE_READLINE
+ if (dlhandle) dlclose(dlhandle);
+#endif
+ }
+
+ /* The data file will be open after -Mset */
+
+ if (deliver_datafile >= 0)
+ {
+ (void)close(deliver_datafile);
+ deliver_datafile = -1;
+ }
+
+ exim_exit(EXIT_SUCCESS);
+ }
+
+
+/* The active host name is normally the primary host name, but it can be varied
+for hosts that want to play several parts at once. We need to ensure that it is
+set for host checking, and for receiving messages. */
+
+smtp_active_hostname = primary_hostname;
+if (raw_active_hostname != NULL)
+ {
+ uschar *nah = expand_string(raw_active_hostname);
+ if (nah == NULL)
+ {
+ if (!f.expand_string_forcedfail)
+ log_write(0, LOG_MAIN|LOG_PANIC_DIE, "failed to expand \"%s\" "
+ "(smtp_active_hostname): %s", raw_active_hostname,
+ expand_string_message);
+ }
+ else if (nah[0] != 0) smtp_active_hostname = nah;
+ }
+
+/* Handle host checking: this facility mocks up an incoming SMTP call from a
+given IP address so that the blocking and relay configuration can be tested.
+Unless a sender_ident was set by -oMt, we discard it (the default is the
+caller's login name). An RFC 1413 call is made only if we are running in the
+test harness and an incoming interface and both ports are specified, because
+there is no TCP/IP call to find the ident for. */
+
+if (host_checking)
+ {
+ int x[4];
+ int size;
+
+ if (!sender_ident_set)
+ {
+ sender_ident = NULL;
+ if (f.running_in_test_harness && sender_host_port
+ && interface_address && interface_port)
+ verify_get_ident(1223); /* note hardwired port number */
+ }
+
+ /* 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);
+ sender_host_address = store_get(48, GET_UNTAINTED); /* large enough for full IPv6 */
+ (void)host_nmtoa(size, x, -1, sender_host_address, ':');
+
+ /* Now set up for testing */
+
+ host_build_sender_fullhost();
+ smtp_input = TRUE;
+ smtp_in = stdin;
+ smtp_out = stdout;
+ f.sender_local = FALSE;
+ f.sender_host_notsocket = TRUE;
+ debug_file = stderr;
+ debug_fd = fileno(debug_file);
+ fprintf(stdout, "\n**** SMTP testing session as if from host %s\n"
+ "**** but without any ident (RFC 1413) callback.\n"
+ "**** This is not for real!\n\n",
+ sender_host_address);
+
+ memset(sender_host_cache, 0, sizeof(sender_host_cache));
+ if (verify_check_host(&hosts_connection_nolog) == OK)
+ BIT_CLEAR(log_selector, log_selector_size, Li_smtp_connection);
+ log_write(L_smtp_connection, LOG_MAIN, "%s", smtp_get_connection_info());
+
+ /* NOTE: We do *not* call smtp_log_no_mail() if smtp_start_session() fails,
+ because a log line has already been written for all its failure exists
+ (usually "connection refused: <reason>") and writing another one is
+ unnecessary clutter. */
+
+ if (smtp_start_session())
+ {
+ rmark reset_point;
+ for (; (reset_point = store_mark()); store_reset(reset_point))
+ {
+ 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;
+ deliver_localpart_data = deliver_domain_data =
+ recipient_data = sender_data = NULL;
+ sender_rate = sender_rate_limit = sender_rate_period = NULL;
+ }
+ smtp_log_no_mail();
+ }
+ exim_exit(EXIT_SUCCESS);
+ }
+
+
+/* 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 or info dump.
+In the former case, show the configuration file name. */
+
+if (recipients_arg >= argc && !extract_recipients && !smtp_input)
+ {
+ if (version_printed)
+ {
+ if (Ustrchr(config_main_filelist, ':'))
+ printf("Configuration file search path is %s\n", config_main_filelist);
+ printf("Configuration file is %s\n", config_main_filename);
+ 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 mua_wrapper is set, Exim is being used to turn an MUA that submits on the
+standard input into an MUA that submits to a smarthost over TCP/IP. We know
+that we are not called from inetd, because that is rejected above. The
+following configuration settings are forced here:
+
+ (1) Synchronous delivery (-odi)
+ (2) Errors to stderr (-oep == -oeq)
+ (3) No parallel remote delivery
+ (4) Unprivileged delivery
+
+We don't force overall queueing options because there are several of them;
+instead, queueing is avoided below when mua_wrapper is set. However, we do need
+to override any SMTP queueing. */
+
+if (mua_wrapper)
+ {
+ f.synchronous_delivery = TRUE;
+ arg_error_handling = ERRORS_STDERR;
+ remote_max_parallel = 1;
+ deliver_drop_privilege = TRUE;
+ f.queue_smtp = FALSE;
+ queue_smtp_domains = NULL;
+#ifdef SUPPORT_I18N
+ message_utf8_downconvert = -1; /* convert-if-needed */
+#endif
+ }
+
+
+/* Prepare to accept one or more new messages on the standard input. When a
+message has been read, its id is returned in message_id[]. If doing immediate
+delivery, we fork a delivery process for each received message, except for the
+last one, where we can save a process switch.
+
+It is only in non-smtp mode that error_handling is allowed to be changed from
+its default of ERRORS_SENDER by argument. (Idle thought: are any of the
+sendmail error modes other than -oem ever actually used? Later: yes.) */
+
+if (!smtp_input) error_handling = arg_error_handling;
+
+/* If this is an inetd call, ensure that stderr is closed to prevent panic
+logging being sent down the socket and make an identd call to get the
+sender_ident. */
+
+else if (f.is_inetd)
+ {
+ (void)fclose(stderr);
+ exim_nullstd(); /* Re-open to /dev/null */
+ verify_get_ident(IDENT_PORT);
+ host_build_sender_fullhost();
+ set_process_info("handling incoming connection from %s via inetd",
+ sender_fullhost);
+ }
+
+/* If the sender host address has been set, build sender_fullhost if it hasn't
+already been done (which it will have been for inetd). This caters for the
+case when it is forced by -oMa. However, we must flag that it isn't a socket,
+so that the test for IP options is skipped for -bs input. */
+
+if (sender_host_address && !sender_fullhost)
+ {
+ host_build_sender_fullhost();
+ set_process_info("handling incoming connection from %s via -oMa",
+ sender_fullhost);
+ f.sender_host_notsocket = TRUE;
+ }
+
+/* Otherwise, set the sender host as unknown except for inetd calls. This
+prevents host checking in the case of -bs not from inetd and also for -bS. */
+
+else if (!f.is_inetd) f.sender_host_unknown = TRUE;
+
+/* If stdout does not exist, then dup stdin to stdout. This can happen
+if exim is started from inetd. In this case fd 0 will be set to the socket,
+but fd 1 will not be set. This also happens for passed SMTP channels. */
+
+if (fstat(1, &statbuf) < 0) (void)dup2(0, 1);
+
+/* Set up the incoming protocol name and the state of the program. Root is
+allowed to force received protocol via the -oMr option above. If we have come
+via inetd, the process info has already been set up. We don't set
+received_protocol here for smtp input, as it varies according to
+batch/HELO/EHLO/AUTH/TLS. */
+
+if (smtp_input)
+ {
+ if (!f.is_inetd) set_process_info("accepting a local %sSMTP message from <%s>",
+ smtp_batched_input? "batched " : "",
+ sender_address ? sender_address : originator_login);
+ }
+else
+ {
+ int old_pool = store_pool;
+ store_pool = POOL_PERM;
+ if (!received_protocol)
+ 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);
+ }
+
+/* Initialize the session_local_queue-only flag (this will be ignored if
+mua_wrapper is set) */
+
+queue_check_only();
+session_local_queue_only = queue_only;
+
+/* For non-SMTP and for batched SMTP input, check that there is enough space on
+the spool if so configured. On failure, we must not attempt to send an error
+message! (For interactive SMTP, the check happens at MAIL FROM and an SMTP
+error code is given.) */
+
+if ((!smtp_input || smtp_batched_input) && !receive_check_fs(0))
+ exim_fail("exim: insufficient disk space\n");
+
+/* If this is smtp input of any kind, real or batched, handle the start of the
+SMTP session.
+
+NOTE: We do *not* call smtp_log_no_mail() if smtp_start_session() fails,
+because a log line has already been written for all its failure exists
+(usually "connection refused: <reason>") and writing another one is
+unnecessary clutter. */
+
+if (smtp_input)
+ {
+ smtp_in = stdin;
+ smtp_out = stdout;
+ memset(sender_host_cache, 0, sizeof(sender_host_cache));
+ if (verify_check_host(&hosts_connection_nolog) == OK)
+ BIT_CLEAR(log_selector, log_selector_size, Li_smtp_connection);
+ log_write(L_smtp_connection, LOG_MAIN, "%s", smtp_get_connection_info());
+ if (!smtp_start_session())
+ {
+ mac_smtp_fflush();
+ exim_exit(EXIT_SUCCESS);
+ }
+ }
+
+/* Otherwise, set up the input size limit here and set no stdin stdio buffer
+(we handle buferring so as to have visibility of fill level). */
+
+else
+ {
+ thismessage_size_limit = expand_string_integer(message_size_limit, TRUE);
+ 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);
+
+ setvbuf(stdin, NULL, _IONBF, 0);
+ }
+
+/* Loop for several messages when reading SMTP input. If we fork any child
+processes, we don't want to wait for them unless synchronous delivery is
+requested, so set SIGCHLD to SIG_IGN in that case. This is not necessarily the
+same as SIG_DFL, despite the fact that documentation often lists the default as
+"ignore". This is a confusing area. This is what I know:
+
+At least on some systems (e.g. Solaris), just setting SIG_IGN causes child
+processes that complete simply to go away without ever becoming defunct. You
+can't then wait for them - but we don't want to wait for them in the
+non-synchronous delivery case. However, this behaviour of SIG_IGN doesn't
+happen for all OS (e.g. *BSD is different).
+
+But that's not the end of the story. Some (many? all?) systems have the
+SA_NOCLDWAIT option for sigaction(). This requests the behaviour that Solaris
+has by default, so it seems that the difference is merely one of default
+(compare restarting vs non-restarting signals).
+
+To cover all cases, Exim sets SIG_IGN with SA_NOCLDWAIT here if it can. If not,
+it just sets SIG_IGN. To be on the safe side it also calls waitpid() at the end
+of the loop below. Paranoia rules.
+
+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.
+As a consequence of this, the waitpid() below is now excluded if we are sure
+that SIG_IGN works. */
+
+if (!f.synchronous_delivery)
+ {
+#ifdef SA_NOCLDWAIT
+ struct sigaction act;
+ act.sa_handler = SIG_IGN;
+ sigemptyset(&(act.sa_mask));
+ act.sa_flags = SA_NOCLDWAIT;
+ sigaction(SIGCHLD, &act, NULL);
+#else
+ signal(SIGCHLD, SIG_IGN);
+#endif
+ }
+
+/* Save the current store pool point, for resetting at the start of
+each message, and save the real sender address, if any. */
+
+real_sender_address = sender_address;
+
+/* Loop to receive messages; receive_msg() returns TRUE if there are more
+messages to be read (SMTP input), or FALSE otherwise (not SMTP, or SMTP channel
+collapsed). */
+
+for (BOOL more = TRUE; more; )
+ {
+ rmark reset_point = store_mark();
+ message_id[0] = 0;
+
+ /* Handle the SMTP case; call smtp_setup_mst() to deal with the initial SMTP
+ input and build the recipients list, before calling receive_msg() to read the
+ message proper. Whatever sender address is given in the SMTP transaction is
+ often ignored for local senders - we use the actual sender, which is normally
+ either the underlying user running this process or a -f argument provided by
+ a trusted caller. It is saved in real_sender_address. The test for whether to
+ accept the SMTP sender is encapsulated in receive_check_set_sender(). */
+
+ if (smtp_input)
+ {
+ int rc;
+ if ((rc = smtp_setup_msg()) > 0)
+ {
+ if (real_sender_address != NULL &&
+ !receive_check_set_sender(sender_address))
+ {
+ sender_address = raw_sender = real_sender_address;
+ sender_address_unrewritten = NULL;
+ }
+
+ /* For batched SMTP, we have to run the acl_not_smtp_start ACL, since it
+ isn't really SMTP, so no other ACL will run until the acl_not_smtp one at
+ the very end. The result of the ACL is ignored (as for other non-SMTP
+ messages). It is run for its potential side effects. */
+
+ if (smtp_batched_input && acl_not_smtp_start != NULL)
+ {
+ uschar *user_msg, *log_msg;
+ f.enable_dollar_recipients = TRUE;
+ (void)acl_check(ACL_WHERE_NOTSMTP_START, NULL, acl_not_smtp_start,
+ &user_msg, &log_msg);
+ f.enable_dollar_recipients = FALSE;
+ }
+
+ /* Now get the data for the message */
+
+ more = receive_msg(extract_recipients);
+ if (!message_id[0])
+ {
+ cancel_cutthrough_connection(TRUE, US"receive dropped");
+ if (more) goto MORELOOP;
+ smtp_log_no_mail(); /* Log no mail if configured */
+ exim_exit(EXIT_FAILURE);
+ }
+ }
+ else
+ {
+ cancel_cutthrough_connection(TRUE, US"message setup dropped");
+ smtp_log_no_mail(); /* Log no mail if configured */
+ exim_exit(rc ? EXIT_FAILURE : EXIT_SUCCESS);
+ }
+ }
+
+ /* In the non-SMTP case, we have all the information from the command
+ line, but must process it in case it is in the more general RFC822
+ format, and in any case, to detect syntax errors. Also, it appears that
+ the use of comma-separated lists as single arguments is common, so we
+ had better support them. */
+
+ else
+ {
+ int rcount = 0;
+ int count = argc - recipients_arg;
+ uschar **list = argv + recipients_arg;
+
+ /* These options cannot be changed dynamically for non-SMTP messages */
+
+ f.active_local_sender_retain = local_sender_retain;
+ f.active_local_from_check = local_from_check;
+
+ /* Save before any rewriting */
+
+ raw_sender = string_copy(sender_address);
+
+ /* Loop for each argument (supplied by user hence tainted) */
+
+ for (int i = 0; i < count; i++)
+ {
+ int start, end, domain;
+ uschar * errmess;
+ /* There can be multiple addresses, so EXIM_DISPLAYMAIL_MAX (tuned for 1) is too short.
+ * We'll still want to cap it to something, just in case. */
+ uschar * s = string_copy_taint(
+ exim_str_fail_toolong(list[i], BIG_BUFFER_SIZE, "address argument"),
+ GET_TAINTED);
+
+ /* Loop for each comma-separated address */
+
+ while (*s)
+ {
+ BOOL finished = FALSE;
+ uschar *recipient;
+ uschar *ss = parse_find_address_end(s, FALSE);
+
+ if (*ss == ',') *ss = 0; else finished = TRUE;
+
+ /* Check max recipients - if -t was used, these aren't recipients */
+
+ if (recipients_max > 0 && ++rcount > recipients_max &&
+ !extract_recipients)
+ if (error_handling == ERRORS_STDERR)
+ {
+ fprintf(stderr, "exim: too many recipients\n");
+ exim_exit(EXIT_FAILURE);
+ }
+ else
+ return
+ moan_to_sender(ERRMESS_TOOMANYRECIP, NULL, NULL, stdin, TRUE)?
+ errors_sender_rc : EXIT_FAILURE;
+
+#ifdef SUPPORT_I18N
+ {
+ BOOL b = allow_utf8_domains;
+ allow_utf8_domains = TRUE;
+#endif
+ recipient =
+ parse_extract_address(s, &errmess, &start, &end, &domain, FALSE);
+
+#ifdef SUPPORT_I18N
+ if (recipient)
+ if (string_is_utf8(recipient)) message_smtputf8 = TRUE;
+ else allow_utf8_domains = b;
+ }
+#else
+ ;
+#endif
+ if (domain == 0 && !f.allow_unqualified_recipient)
+ {
+ recipient = NULL;
+ errmess = US"unqualified recipient address not allowed";
+ }
+
+ if (!recipient)
+ if (error_handling == ERRORS_STDERR)
+ {
+ fprintf(stderr, "exim: bad recipient address \"%s\": %s\n",
+ string_printing(list[i]), errmess);
+ exim_exit(EXIT_FAILURE);
+ }
+ else
+ {
+ error_block eblock;
+ eblock.next = NULL;
+ eblock.text1 = string_printing(list[i]);
+ eblock.text2 = errmess;
+ return
+ moan_to_sender(ERRMESS_BADARGADDRESS, &eblock, NULL, stdin, TRUE)?
+ errors_sender_rc : EXIT_FAILURE;
+ }
+
+ receive_add_recipient(string_copy_taint(recipient, GET_TAINTED), -1);
+ s = ss;
+ if (!finished)
+ while (*(++s) != 0 && (*s == ',' || isspace(*s)));
+ }
+ }
+
+ /* Show the recipients when debugging */
+
+ DEBUG(D_receive)
+ {
+ if (sender_address) debug_printf("Sender: %s\n", sender_address);
+ if (recipients_list)
+ {
+ debug_printf("Recipients:\n");
+ for (int i = 0; i < recipients_count; i++)
+ debug_printf(" %s\n", recipients_list[i].address);
+ }
+ }
+
+ /* Run the acl_not_smtp_start ACL if required. The result of the ACL is
+ 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)
+ {
+ uschar *user_msg, *log_msg;
+ f.enable_dollar_recipients = TRUE;
+ (void)acl_check(ACL_WHERE_NOTSMTP_START, NULL, acl_not_smtp_start,
+ &user_msg, &log_msg);
+ f.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)
+ if (poll_one_fd(0, POLLIN, 30*60*1000) == 0) /* 30 minutes */
+ 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. */
+
+ message_ended = END_NOTENDED;
+ more = receive_msg(extract_recipients);
+
+ /* more is always FALSE here (not SMTP message) when reading a message
+ 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]) exim_exit(EXIT_FAILURE);
+ } /* Non-SMTP message reception */
+
+ /* If this is a filter testing run, there are headers in store, but
+ no message on the spool. Run the filtering code in testing mode, setting
+ the domain to the qualify domain and the local part to the current user,
+ unless they have been set by options. The prefix and suffix are left unset
+ unless specified. The the return path is set to to the sender unless it has
+ already been set from a return-path header in the message. */
+
+ if (filter_test != FTEST_NONE)
+ {
+ deliver_domain = ftest_domain ? ftest_domain : qualify_domain_recipient;
+ deliver_domain_orig = deliver_domain;
+ deliver_localpart = ftest_localpart ? US ftest_localpart : originator_login;
+ deliver_localpart_orig = deliver_localpart;
+ deliver_localpart_prefix = US ftest_prefix;
+ deliver_localpart_suffix = US ftest_suffix;
+ deliver_home = originator_home;
+
+ if (!return_path)
+ {
+ printf("Return-path copied from sender\n");
+ return_path = string_copy(sender_address);
+ }
+ else
+ printf("Return-path = %s\n", (return_path[0] == 0)? US"<>" : return_path);
+ printf("Sender = %s\n", (sender_address[0] == 0)? US"<>" : sender_address);
+
+ receive_add_recipient(
+ string_sprintf("%s%s%s@%s",
+ ftest_prefix ? ftest_prefix : US"",
+ deliver_localpart,
+ ftest_suffix ? ftest_suffix : US"",
+ deliver_domain), -1);
+
+ printf("Recipient = %s\n", recipients_list[0].address);
+ if (ftest_prefix) printf("Prefix = %s\n", ftest_prefix);
+ if (ftest_suffix) printf("Suffix = %s\n", ftest_suffix);
+
+ if (chdir("/")) /* Get away from wherever the user is running this from */
+ {
+ DEBUG(D_receive) debug_printf("chdir(\"/\") failed\n");
+ exim_exit(EXIT_FAILURE);
+ }
+
+ /* Now we run either a system filter test, or a user filter test, or both.
+ In the latter case, headers added by the system filter will persist and be
+ available to the user filter. We need to copy the filter variables
+ explicitly. */
+
+ if (filter_test & FTEST_SYSTEM)
+ if (!filter_runtest(filter_sfd, filter_test_sfile, TRUE, more))
+ exim_exit(EXIT_FAILURE);
+
+ memcpy(filter_sn, filter_n, sizeof(filter_sn));
+
+ if (filter_test & FTEST_USER)
+ if (!filter_runtest(filter_ufd, filter_test_ufile, FALSE, more))
+ exim_exit(EXIT_FAILURE);
+
+ exim_exit(EXIT_SUCCESS);
+ }
+
+ /* Else act on the result of message reception. We should not get here unless
+ message_id[0] is non-zero. If queue_only is set, session_local_queue_only
+ will be TRUE. If it is not, check on the number of messages received in this
+ connection. */
+
+ if ( !session_local_queue_only
+ && smtp_accept_queue_per_connection > 0
+ && receive_messagecount > smtp_accept_queue_per_connection)
+ {
+ session_local_queue_only = TRUE;
+ queue_only_reason = 2;
+ }
+
+ /* Initialize local_queue_only from session_local_queue_only. If it is false,
+ and queue_only_load is set, check that the load average is below it. If it is
+ not, set local_queue_only TRUE. If queue_only_load_latch is true (the
+ default), we put the whole session into queue_only mode. It then remains this
+ way for any subsequent messages on the same SMTP connection. This is a
+ deliberate choice; even though the load average may fall, it doesn't seem
+ right to deliver later messages on the same call when not delivering earlier
+ ones. However, there are odd cases where this is not wanted, so this can be
+ changed by setting queue_only_load_latch false. */
+
+ if (!(local_queue_only = session_local_queue_only) && queue_only_load >= 0)
+ if ((local_queue_only = (load_average = OS_GETLOADAVG()) > queue_only_load))
+ {
+ queue_only_reason = 3;
+ if (queue_only_load_latch) session_local_queue_only = TRUE;
+ }
+
+ /* If running as an MUA wrapper, all queueing options and freezing options
+ are ignored. */
+
+ if (mua_wrapper)
+ local_queue_only = f.queue_only_policy = f.deliver_freeze = FALSE;
+
+ /* Log the queueing here, when it will get a message id attached, but
+ not if queue_only is set (case 0). Case 1 doesn't happen here (too many
+ connections). */
+
+ if (local_queue_only)
+ {
+ 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;
+ }
+ }
+
+ else if (f.queue_only_policy || f.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
+ run. The search cache must be tidied before the fork, as the parent will
+ do it before exiting. The child will trigger a lookup failure and
+ thereby defer the delivery if it tries to use (for example) a cached ldap
+ connection that the parent has called unbind on. */
+
+ else
+ {
+ pid_t pid;
+ search_tidyup();
+
+ if ((pid = exim_fork(US"local-accept-delivery")) == 0)
+ {
+ int rc;
+ close_unwanted(); /* Close unwanted file descriptors and TLS */
+ exim_nullstd(); /* Ensure std{in,out,err} exist */
+
+ /* Re-exec Exim if we need to regain privilege (note: in mua_wrapper
+ mode, deliver_drop_privilege is forced TRUE). */
+
+ if (geteuid() != root_uid && !deliver_drop_privilege && !unprivileged)
+ {
+ delivery_re_exec(CEE_EXEC_EXIT);
+ /* Control does not return here. */
+ }
+
+ /* No need to re-exec */
+
+ rc = deliver_message(message_id, FALSE, FALSE);
+ search_tidyup();
+ exim_underbar_exit(!mua_wrapper || rc == DELIVER_MUA_SUCCEEDED
+ ? EXIT_SUCCESS : EXIT_FAILURE);
+ }
+
+ 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));
+ }
+ 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. */
+
+ if (f.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);
+ }
+ }
+ }
+
+ /* The loop will repeat if more is TRUE. If we do not know know that the OS
+ automatically reaps children (see comments above the loop), clear away any
+ finished subprocesses here, in case there are lots of messages coming in
+ from the same source. */
+
+#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;
+ deliver_localpart_data = deliver_domain_data =
+ recipient_data = sender_data = NULL;
+ acl_var_m = NULL;
+ for(int i = 0; i < REGEX_VARS; i++) regex_vars[i] = NULL;
+
+ store_reset(reset_point);
+ }
+
+exim_exit(EXIT_SUCCESS); /* Never returns */
+return 0; /* To stop compiler warning */
+}
+
+
+/* End of exim.c */