-/* $Cambridge: exim/src/src/exim.c,v 1.45 2006/10/23 13:24:21 ph10 Exp $ */
-
/*************************************************
* Exim - an Internet mail transport agent *
*************************************************/
-/* Copyright (c) University of Cambridge 1995 - 2006 */
+/* Copyright (c) University of Cambridge 1995 - 2015 */
/* See the file NOTICE for conditions of use and distribution. */
#include "exim.h"
+#ifdef USE_GNUTLS
+# include <gnutls/gnutls.h>
+# if GNUTLS_VERSION_NUMBER < 0x030103 && !defined(DISABLE_OCSP)
+# define DISABLE_OCSP
+# endif
+#endif
+
+extern void init_lookup_list(void);
+
/*************************************************
+/*************************************************
+* Enums for cmdline interface *
+*************************************************/
+
+enum commandline_info { CMDINFO_NONE=0,
+ CMDINFO_HELP, CMDINFO_SIEVE, CMDINFO_DSCP };
+
+
+
+
/*************************************************
* Compile regular expression and panic on fail *
*************************************************/
*/
const pcre *
-regex_must_compile(uschar *pattern, BOOL caseless, BOOL use_malloc)
+regex_must_compile(const uschar *pattern, BOOL caseless, BOOL use_malloc)
{
int offset;
int options = PCRE_COPT;
pcre_free = function_store_free;
}
if (caseless) options |= PCRE_CASELESS;
-yield = pcre_compile(CS pattern, options, (const char **)&error, &offset, NULL);
+yield = pcre_compile(CCS pattern, options, (const char **)&error, &offset, NULL);
pcre_malloc = function_store_get;
pcre_free = function_dummy_free;
if (yield == NULL)
*/
BOOL
-regex_match_and_setup(const pcre *re, uschar *subject, int options, int setup)
+regex_match_and_setup(const pcre *re, const uschar *subject, int options, int setup)
{
int ovector[3*(EXPAND_MAXN+1)];
-int n = pcre_exec(re, NULL, CS subject, Ustrlen(subject), 0,
+uschar * s = string_copy(subject); /* de-constifying */
+int n = pcre_exec(re, NULL, CS s, Ustrlen(s), 0,
PCRE_EOPT | options, ovector, sizeof(ovector)/sizeof(int));
BOOL yield = n >= 0;
if (n == 0) n = EXPAND_MAXN + 1;
expand_nmax = (setup < 0)? 0 : setup + 1;
for (nn = (setup < 0)? 0 : 2; nn < n*2; nn += 2)
{
- expand_nstring[expand_nmax] = subject + ovector[nn];
+ expand_nstring[expand_nmax] = s + ovector[nn];
expand_nlength[expand_nmax++] = ovector[nn+1] - ovector[nn];
}
expand_nmax--;
+/*************************************************
+* Set up processing details *
+*************************************************/
+
+/* Save a text string for dumping when SIGUSR1 is received.
+Do checks for overruns.
+
+Arguments: format and arguments, as for printf()
+Returns: nothing
+*/
+
+void
+set_process_info(const char *format, ...)
+{
+int len;
+va_list ap;
+sprintf(CS process_info, "%5d ", (int)getpid());
+len = Ustrlen(process_info);
+va_start(ap, format);
+if (!string_vformat(process_info + len, PROCESS_INFO_SIZE - len - 2, format, ap))
+ Ustrcpy(process_info + len, "**** string overflowed buffer ****");
+len = Ustrlen(process_info);
+process_info[len+0] = '\n';
+process_info[len+1] = '\0';
+process_info_len = len + 1;
+DEBUG(D_process_info) debug_printf("set_process_info: %s", process_info);
+va_end(ap);
+}
+
+
+
+
/*************************************************
* Handler for SIGUSR1 *
*************************************************/
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)
{
-sig = sig; /* Keep picky compilers happy */
-log_write(0, LOG_PROCESS, "%s", process_info);
-log_close_all();
-os_restarting_signal(SIGUSR1, usr1_handler);
+int fd;
+
+os_restarting_signal(sig, usr1_handler);
+
+fd = Uopen(process_log_path, O_APPEND|O_WRONLY, LOG_MODE);
+if (fd < 0)
+ {
+ /* If we are already running as the Exim user, try to create it in the
+ current process (assuming spool_directory exists). Otherwise, if we are
+ root, do the creation in an exim:exim subprocess. */
+
+ int euid = geteuid();
+ if (euid == exim_uid)
+ fd = Uopen(process_log_path, O_CREAT|O_APPEND|O_WRONLY, LOG_MODE);
+ else if (euid == root_uid)
+ fd = log_create_as_exim(process_log_path);
+ }
+
+/* If we are neither exim nor root, or if we failed to create the log file,
+give up. There is not much useful we can do with errors, since we don't want
+to disrupt whatever is going on outside the signal handler. */
+
+if (fd < 0) return;
+
+(void)write(fd, process_info, process_info_len);
+(void)close(fd);
}
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 100us; 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
*/
{
sigset_t sigmask;
sigset_t old_sigmask;
+
+if (itval->it_value.tv_usec < 100 && 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 (!running_in_test_harness)
{
- debug_printf("tick check: %lu.%06lu %lu.%06lu\n",
- then_tv->tv_sec, then_tv->tv_usec, now_tv.tv_sec, now_tv.tv_usec);
- debug_printf("waiting %lu.%06lu\n", itval.it_value.tv_sec,
- itval.it_value.tv_usec);
+ debug_printf("tick check: " TIME_T_FMT ".%06lu " TIME_T_FMT ".%06lu\n",
+ then_tv->tv_sec, (long) then_tv->tv_usec,
+ now_tv.tv_sec, (long) now_tv.tv_usec);
+ debug_printf("waiting " TIME_T_FMT ".%06lu\n",
+ itval.it_value.tv_sec, (long) itval.it_value.tv_usec);
}
}
-/*************************************************
-* Set up processing details *
-*************************************************/
-
-/* Save a text string for dumping when SIGUSR1 is received.
-Do checks for overruns.
-
-Arguments: format and arguments, as for printf()
-Returns: nothing
-*/
-
-void
-set_process_info(char *format, ...)
-{
-int len;
-va_list ap;
-sprintf(CS process_info, "%5d ", (int)getpid());
-len = Ustrlen(process_info);
-va_start(ap, format);
-if (!string_vformat(process_info + len, PROCESS_INFO_SIZE - len, format, ap))
- Ustrcpy(process_info + len, "**** string overflowed buffer ****");
-DEBUG(D_process_info) debug_printf("set_process_info: %s\n", process_info);
-va_end(ap);
-}
-
-
-
-
-
/*************************************************
* Call fopen() with umask 777 and adjust mode *
*************************************************/
*/
FILE *
-modefopen(uschar *filename, char *options, mode_t mode)
+modefopen(const uschar *filename, const char *options, mode_t mode)
{
mode_t saved_umask = umask(0777);
FILE *f = Ufopen(filename, options);
if (smtp_input)
{
#ifdef SUPPORT_TLS
- tls_close(FALSE); /* Shut down the TLS library */
+ tls_close(TRUE, FALSE); /* Shut down the TLS library */
#endif
(void)close(fileno(smtp_in));
(void)close(fileno(smtp_out));
DEBUG(D_uid)
{
- int group_count;
+ int group_count, save_errno;
gid_t group_list[NGROUPS_MAX];
debug_printf("changed uid/gid: %s\n uid=%ld gid=%ld pid=%ld\n", msg,
(long int)geteuid(), (long int)getegid(), (long int)getpid());
group_count = getgroups(NGROUPS_MAX, group_list);
+ save_errno = errno;
debug_printf(" auxiliary group list:");
if (group_count > 0)
{
int i;
for (i = 0; i < group_count; i++) debug_printf(" %d", (int)group_list[i]);
}
+ else if (group_count < 0)
+ debug_printf(" <error: %s>", strerror(save_errno));
else debug_printf(" <none>");
debug_printf("\n");
}
-/*************************************************
-* Decode bit settings for log/debug *
-*************************************************/
-
-/* This function decodes a string containing bit settings in the form of +name
-and/or -name sequences, and sets/unsets bits in a bit string accordingly. It
-also recognizes a numeric setting of the form =<number>, but this is not
-intended for user use. It's an easy way for Exim to pass the debug settings
-when it is re-exec'ed.
-
-The log options are held in two unsigned ints (because there became too many
-for one). The top bit in the table means "put in 2nd selector". This does not
-yet apply to debug options, so the "=" facility sets only the first selector.
-
-The "all" selector, which must be equal to 0xffffffff, is recognized specially.
-It sets all the bits in both selectors. However, there is a facility for then
-unsetting certain bits, because we want to turn off "memory" in the debug case.
-
-A bad value for a debug setting is treated as an unknown option - error message
-to stderr and die. For log settings, which come from the configuration file,
-we write to the log on the way out...
-
-Arguments:
- selector1 address of the first bit string
- selector2 address of the second bit string, or NULL
- notall1 bits to exclude from "all" for selector1
- notall2 bits to exclude from "all" for selector2
- string the configured string
- options the table of option names
- count size of table
- which "log" or "debug"
-
-Returns: nothing on success - bomb out on failure
-*/
-
-static void
-decode_bits(unsigned int *selector1, unsigned int *selector2, int notall1,
- int notall2, uschar *string, bit_table *options, int count, uschar *which)
-{
-uschar *errmsg;
-if (string == NULL) return;
-
-if (*string == '=')
- {
- char *end; /* Not uschar */
- *selector1 = strtoul(CS string+1, &end, 0);
- if (*end == 0) return;
- errmsg = string_sprintf("malformed numeric %s_selector setting: %s", which,
- string);
- goto ERROR_RETURN;
- }
-
-/* Handle symbolic setting */
-
-else for(;;)
- {
- BOOL adding;
- uschar *s;
- int len;
- bit_table *start, *end;
-
- while (isspace(*string)) string++;
- if (*string == 0) return;
-
- if (*string != '+' && *string != '-')
- {
- errmsg = string_sprintf("malformed %s_selector setting: "
- "+ or - expected but found \"%s\"", which, string);
- goto ERROR_RETURN;
- }
-
- adding = *string++ == '+';
- s = string;
- while (isalnum(*string) || *string == '_') string++;
- len = string - s;
-
- start = options;
- end = options + count;
-
- while (start < end)
- {
- bit_table *middle = start + (end - start)/2;
- int c = Ustrncmp(s, middle->name, len);
- if (c == 0)
- {
- if (middle->name[len] != 0) c = -1; else
- {
- unsigned int bit = middle->bit;
- unsigned int *selector;
-
- /* The value with all bits set means "force all bits in both selectors"
- in the case where two are being handled. However, the top bit in the
- second selector is never set. When setting, some bits can be excluded.
- */
-
- if (bit == 0xffffffff)
- {
- if (adding)
- {
- *selector1 = 0xffffffff ^ notall1;
- if (selector2 != NULL) *selector2 = 0x7fffffff ^ notall2;
- }
- else
- {
- *selector1 = 0;
- if (selector2 != NULL) *selector2 = 0;
- }
- }
-
- /* Otherwise, the 0x80000000 bit means "this value, without the top
- bit, belongs in the second selector". */
-
- else
- {
- if ((bit & 0x80000000) != 0)
- {
- selector = selector2;
- bit &= 0x7fffffff;
- }
- else selector = selector1;
- if (adding) *selector |= bit; else *selector &= ~bit;
- }
- break; /* Out of loop to match selector name */
- }
- }
- if (c < 0) end = middle; else start = middle + 1;
- } /* Loop to match selector name */
-
- if (start >= end)
- {
- errmsg = string_sprintf("unknown %s_selector setting: %c%.*s", which,
- adding? '+' : '-', len, s);
- goto ERROR_RETURN;
- }
- } /* Loop for selector names */
-
-/* Handle disasters */
-
-ERROR_RETURN:
-if (Ustrcmp(which, "debug") == 0)
- {
- fprintf(stderr, "exim: %s\n", errmsg);
- exit(EXIT_FAILURE);
- }
-else log_write(0, LOG_CONFIG|LOG_PANIC_DIE, "%s", errmsg);
-}
-
-
-
/*************************************************
* Show supported features *
*************************************************/
-/* This function is called for -bV and for -d to output the optional features
-of the current Exim binary.
+/* This function is called for -bV/--version and for -d to output the optional
+features of the current Exim binary.
Arguments: a FILE for printing
Returns: nothing
static void
show_whats_supported(FILE *f)
{
+ auth_info *authi;
+
#ifdef DB_VERSION_STRING
fprintf(f, "Berkeley DB: %s\n", DB_VERSION_STRING);
#elif defined(BTREEVERSION) && defined(HASHVERSION)
#ifdef WITH_OLD_DEMIME
fprintf(f, " Old_Demime");
#endif
+#ifndef DISABLE_DKIM
+ fprintf(f, " DKIM");
+#endif
+#ifndef DISABLE_DNSSEC
+ fprintf(f, " DNSSEC");
+#endif
+#ifndef DISABLE_EVENT
+ fprintf(f, " Event");
+#endif
+#ifdef SUPPORT_I18N
+ fprintf(f, " I18N");
+#endif
+#ifndef DISABLE_OCSP
+ fprintf(f, " OCSP");
+#endif
+#ifndef DISABLE_PRDR
+ fprintf(f, " PRDR");
+#endif
+#ifdef SUPPORT_PROXY
+ fprintf(f, " PROXY");
+#endif
+#ifdef SUPPORT_SOCKS
+ fprintf(f, " SOCKS");
+#endif
#ifdef EXPERIMENTAL_SPF
fprintf(f, " Experimental_SPF");
#endif
#ifdef EXPERIMENTAL_BRIGHTMAIL
fprintf(f, " Experimental_Brightmail");
#endif
-#ifdef EXPERIMENTAL_DOMAINKEYS
- fprintf(f, " Experimental_DomainKeys");
+#ifdef EXPERIMENTAL_DANE
+ fprintf(f, " Experimental_DANE");
+#endif
+#ifdef EXPERIMENTAL_DCC
+ fprintf(f, " Experimental_DCC");
+#endif
+#ifdef EXPERIMENTAL_DMARC
+ fprintf(f, " Experimental_DMARC");
+#endif
+#ifdef EXPERIMENTAL_DSN_INFO
+ fprintf(f, " Experimental_DSN_info");
#endif
fprintf(f, "\n");
-fprintf(f, "Lookups:");
-#ifdef LOOKUP_LSEARCH
+fprintf(f, "Lookups (built-in):");
+#if defined(LOOKUP_LSEARCH) && LOOKUP_LSEARCH!=2
fprintf(f, " lsearch wildlsearch nwildlsearch iplsearch");
#endif
-#ifdef LOOKUP_CDB
+#if defined(LOOKUP_CDB) && LOOKUP_CDB!=2
fprintf(f, " cdb");
#endif
-#ifdef LOOKUP_DBM
- fprintf(f, " dbm dbmnz");
+#if defined(LOOKUP_DBM) && LOOKUP_DBM!=2
+ fprintf(f, " dbm dbmjz dbmnz");
#endif
-#ifdef LOOKUP_DNSDB
+#if defined(LOOKUP_DNSDB) && LOOKUP_DNSDB!=2
fprintf(f, " dnsdb");
#endif
-#ifdef LOOKUP_DSEARCH
+#if defined(LOOKUP_DSEARCH) && LOOKUP_DSEARCH!=2
fprintf(f, " dsearch");
#endif
-#ifdef LOOKUP_IBASE
+#if defined(LOOKUP_IBASE) && LOOKUP_IBASE!=2
fprintf(f, " ibase");
#endif
-#ifdef LOOKUP_LDAP
+#if defined(LOOKUP_LDAP) && LOOKUP_LDAP!=2
fprintf(f, " ldap ldapdn ldapm");
#endif
-#ifdef LOOKUP_MYSQL
+#if defined(LOOKUP_MYSQL) && LOOKUP_MYSQL!=2
fprintf(f, " mysql");
#endif
-#ifdef LOOKUP_NIS
+#if defined(LOOKUP_NIS) && LOOKUP_NIS!=2
fprintf(f, " nis nis0");
#endif
-#ifdef LOOKUP_NISPLUS
+#if defined(LOOKUP_NISPLUS) && LOOKUP_NISPLUS!=2
fprintf(f, " nisplus");
#endif
-#ifdef LOOKUP_ORACLE
+#if defined(LOOKUP_ORACLE) && LOOKUP_ORACLE!=2
fprintf(f, " oracle");
#endif
-#ifdef LOOKUP_PASSWD
+#if defined(LOOKUP_PASSWD) && LOOKUP_PASSWD!=2
fprintf(f, " passwd");
#endif
-#ifdef LOOKUP_PGSQL
+#if defined(LOOKUP_PGSQL) && LOOKUP_PGSQL!=2
fprintf(f, " pgsql");
#endif
-#ifdef LOOKUP_SQLITE
+#if defined(LOOKUP_REDIS) && LOOKUP_REDIS!=2
+ fprintf(f, " redis");
+#endif
+#if defined(LOOKUP_SQLITE) && LOOKUP_SQLITE!=2
fprintf(f, " sqlite");
#endif
-#ifdef LOOKUP_TESTDB
+#if defined(LOOKUP_TESTDB) && LOOKUP_TESTDB!=2
fprintf(f, " testdb");
#endif
-#ifdef LOOKUP_WHOSON
+#if defined(LOOKUP_WHOSON) && LOOKUP_WHOSON!=2
fprintf(f, " whoson");
#endif
fprintf(f, "\n");
#ifdef AUTH_DOVECOT
fprintf(f, " dovecot");
#endif
+#ifdef AUTH_GSASL
+ fprintf(f, " gsasl");
+#endif
+#ifdef AUTH_HEIMDAL_GSSAPI
+ fprintf(f, " heimdal_gssapi");
+#endif
#ifdef AUTH_PLAINTEXT
fprintf(f, " plaintext");
#endif
#ifdef AUTH_SPA
fprintf(f, " spa");
#endif
+#ifdef AUTH_TLS
+ fprintf(f, " tls");
+#endif
fprintf(f, "\n");
fprintf(f, "Routers:");
fprintf(f, "%d\n", (unsigned int)fixed_never_users[i]);
}
-fprintf(f, "Size of off_t: %d\n", sizeof(off_t));
+fprintf(f, "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) do {
+
+ int i;
+
+/* clang defines __GNUC__ (at least, for me) so test for it first */
+#if defined(__clang__)
+ fprintf(f, "Compiler: CLang [%s]\n", __clang_version__);
+#elif defined(__GNUC__)
+ fprintf(f, "Compiler: GCC [%s]\n",
+# ifdef __VERSION__
+ __VERSION__
+# else
+ "? unknown version ?"
+# endif
+ );
+#else
+ fprintf(f, "Compiler: <unknown>\n");
+#endif
+
+#ifdef SUPPORT_TLS
+ tls_version_report(f);
+#endif
+#ifdef SUPPORT_I18N
+ utf8_version_report(f);
+#endif
+
+ for (authi = auths_available; *authi->driver_name != '\0'; ++authi)
+ if (authi->version_report)
+ (*authi->version_report)(f);
+
+ /* PCRE_PRERELEASE is either defined and empty or a bare sequence of
+ characters; unless it's an ancient version of PCRE in which case it
+ is not defined. */
+#ifndef PCRE_PRERELEASE
+#define PCRE_PRERELEASE
+#endif
+#define QUOTE(X) #X
+#define EXPAND_AND_QUOTE(X) QUOTE(X)
+ fprintf(f, "Library version: PCRE: Compile: %d.%d%s\n"
+ " Runtime: %s\n",
+ PCRE_MAJOR, PCRE_MINOR,
+ EXPAND_AND_QUOTE(PCRE_PRERELEASE) "",
+ pcre_version());
+#undef QUOTE
+#undef EXPAND_AND_QUOTE
+
+ init_lookup_list();
+ for (i = 0; i < lookup_list_count; i++)
+ if (lookup_list[i]->version_report)
+ lookup_list[i]->version_report(f);
+
+#ifdef WHITELIST_D_MACROS
+ fprintf(f, "WHITELIST_D_MACROS: \"%s\"\n", WHITELIST_D_MACROS);
+#else
+ fprintf(f, "WHITELIST_D_MACROS unset\n");
+#endif
+#ifdef TRUSTED_CONFIG_LIST
+ fprintf(f, "TRUSTED_CONFIG_LIST: \"%s\"\n", TRUSTED_CONFIG_LIST);
+#else
+ fprintf(f, "TRUSTED_CONFIG_LIST unset\n");
+#endif
+
+} while (0);
}
+/*************************************************
+* Show auxiliary information about Exim *
+*************************************************/
+
+static void
+show_exim_information(enum commandline_info request, FILE *stream)
+{
+const uschar **pp;
+
+switch(request)
+ {
+ case CMDINFO_NONE:
+ fprintf(stream, "Oops, something went wrong.\n");
+ return;
+ case CMDINFO_HELP:
+ fprintf(stream,
+"The -bI: flag takes a string indicating which information to provide.\n"
+"If the string is not recognised, you'll get this help (on stderr).\n"
+"\n"
+" exim -bI:help this information\n"
+" exim -bI:dscp dscp value keywords known\n"
+" exim -bI:sieve list of supported sieve extensions, one per line.\n"
+);
+ return;
+ case CMDINFO_SIEVE:
+ for (pp = exim_sieve_extension_list; *pp; ++pp)
+ fprintf(stream, "%s\n", *pp);
+ return;
+ case CMDINFO_DSCP:
+ dscp_list_to_stream(stream);
+ return;
+ }
+}
/*************************************************
*/
static void *
-set_readline(char * (**fn_readline_ptr)(char *),
- char * (**fn_addhist_ptr)(char *))
+set_readline(char * (**fn_readline_ptr)(const char *),
+ void (**fn_addhist_ptr)(const char *))
{
void *dlhandle;
-void *dlhandle_curses = dlopen("libcurses.so", RTLD_GLOBAL|RTLD_LAZY);
+void *dlhandle_curses = dlopen("libcurses." DYNLIB_FN_EXT, RTLD_GLOBAL|RTLD_LAZY);
-dlhandle = dlopen("libreadline.so", RTLD_GLOBAL|RTLD_NOW);
+dlhandle = dlopen("libreadline." DYNLIB_FN_EXT, RTLD_GLOBAL|RTLD_NOW);
if (dlhandle_curses != NULL) dlclose(dlhandle_curses);
if (dlhandle != NULL)
{
- *fn_readline_ptr = (char *(*)(char*))dlsym(dlhandle, "readline");
- *fn_addhist_ptr = (char *(*)(char*))dlsym(dlhandle, "add_history");
+ /* Checked manual pages; at least in GNU Readline 6.1, the prototypes are:
+ * char * readline (const char *prompt);
+ * void add_history (const char *string);
+ */
+ *fn_readline_ptr = (char *(*)(const char*))dlsym(dlhandle, "readline");
+ *fn_addhist_ptr = (void(*)(const char*))dlsym(dlhandle, "add_history");
}
else
{
*/
static uschar *
-get_stdinput(char *(*fn_readline)(char *), char *(*fn_addhist)(char *))
+get_stdinput(char *(*fn_readline)(const char *), void(*fn_addhist)(const char *))
{
int i;
int size = 0;
int ptr = 0;
uschar *yield = NULL;
-if (fn_readline == NULL) printf("> ");
+if (fn_readline == NULL) { printf("> "); fflush(stdout); }
for (i = 0;; i++)
{
+/*************************************************
+* 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 varients */
+if (Ustrcmp(progname, US"-mailq") == 0)
+ {
+ fprintf(stderr,
+ "mailq - list the contents of the mail queue\n\n"
+ "For a list of options, see the Exim documentation.\n");
+ exit(EXIT_FAILURE);
+ }
+
+/* Generic usage - we output this whatever happens */
+fprintf(stderr,
+ "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");
+
+exit(EXIT_FAILURE);
+}
+
+
+
+/*************************************************
+* 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: none (macros is a global)
+Returns: true if trusted, false otherwise
+*/
+
+static BOOL
+macros_trusted(void)
+{
+#ifdef WHITELIST_D_MACROS
+macro_item *m;
+uschar *whitelisted, *end, *p, **whites, **w;
+int white_count, i, n;
+size_t len;
+BOOL prev_char_item, found;
+#endif
+
+if (macros == NULL)
+ 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_malloc(US WHITELIST_D_MACROS);
+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 macros should be very short. Accept the N*M complexity. */
+for (m = macros; m != NULL; m = m->next)
+ {
+ found = FALSE;
+ for (w = whites; *w; ++w)
+ if (Ustrcmp(*w, m->name) == 0)
+ {
+ found = TRUE;
+ break;
+ }
+ if (!found)
+ return FALSE;
+ if (m->replacement == NULL)
+ continue;
+ len = Ustrlen(m->replacement);
+ if (len == 0)
+ continue;
+ n = pcre_exec(regex_whitelisted_macro, NULL, CS m->replacement, len,
+ 0, PCRE_EOPT, NULL, 0);
+ if (n < 0)
+ {
+ if (n != PCRE_ERROR_NOMATCH)
+ debug_printf("macros_trusted checking %s returned %d\n", m->name, n);
+ return FALSE;
+ }
+ }
+DEBUG(D_any) debug_printf("macros_trusted overridden to true by whitelisting\n");
+return TRUE;
+#endif
+}
+
+
/*************************************************
* Entry point and high-level code *
*************************************************/
int filter_sfd = -1;
int filter_ufd = -1;
int group_count;
-int i;
+int i, rv;
int list_queue_option = 0;
int msg_action = 0;
int msg_action_arg = -1;
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 more = TRUE;
BOOL one_msg_action = 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 version_printed = FALSE;
uschar *alias_arg = NULL;
uschar *called_as = US"";
+uschar *cmdline_syslog_name = NULL;
uschar *start_queue_run_id = NULL;
uschar *stop_queue_run_id = NULL;
+uschar *expansion_test_message = NULL;
uschar *ftest_domain = NULL;
uschar *ftest_localpart = NULL;
uschar *ftest_prefix = NULL;
uschar *ftest_suffix = NULL;
+uschar *log_oneline = NULL;
+uschar *malware_test_file = NULL;
uschar *real_sender_address;
uschar *originator_home = US"/";
+size_t sz;
void *reset_point;
struct passwd *pw;
int passed_qr_pipe = -1;
gid_t group_list[NGROUPS_MAX];
+/* For the -bI: flag */
+enum commandline_info info_flag = CMDINFO_NONE;
+BOOL info_stdout = FALSE;
+
/* Possible options for -R and -S */
static uschar *rsopts[] = { US"f", US"ff", US"r", US"rf", US"rff" };
#ifdef EXIM_USERNAME
if (route_finduser(US EXIM_USERNAME, &pw, &exim_uid))
{
- exim_gid = pw->pw_gid;
+ if (exim_uid == 0)
+ {
+ fprintf(stderr, "exim: refusing to run with uid 0 for \"%s\"\n",
+ EXIM_USERNAME);
+ exit(EXIT_FAILURE);
+ }
+ /* If ref:name uses a number as the name, route_finduser() returns
+ TRUE with exim_uid set and pw coerced to NULL. */
+ if (pw)
+ exim_gid = pw->pw_gid;
+#ifndef EXIM_GROUPNAME
+ else
+ {
+ fprintf(stderr,
+ "exim: ref:name should specify a usercode, not a group.\n"
+ "exim: can't let you get away with it unless you also specify a group.\n");
+ exit(EXIT_FAILURE);
+ }
+#endif
}
else
{
}
#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))
{
}
#endif
-/* In the Cygwin environment, some initialization needs doing. It is fudged
-in by means of this macro. */
+/* 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
exit(EXIT_FAILURE);
}
+/* 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".
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. */
+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 (real_uid == root_uid)
{
- setgid(real_gid);
- setuid(real_uid);
+ rv = setgid(real_gid);
+ if (rv)
+ {
+ fprintf(stderr, "exim: setgid(%ld) failed: %s\n",
+ (long int)real_gid, strerror(errno));
+ exit(EXIT_FAILURE);
+ }
+ rv = setuid(real_uid);
+ if (rv)
+ {
+ fprintf(stderr, "exim: setuid(%ld) failed: %s\n",
+ (long int)real_uid, strerror(errno));
+ exit(EXIT_FAILURE);
+ }
}
/* If neither the original real uid nor the original euid was root, Exim is
unprivileged = (real_uid != root_uid && original_euid != root_uid);
-/* If the first argument is --help, pretend there are no arguments. This will
-cause a brief message to be given. */
-
-if (argc > 1 && Ustrcmp(argv[1], "--help") == 0) argc = 1;
-
/* 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. */
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 == '\0') { badarg = TRUE; break; }
+ else
+ {
+ BOOL ignore = FALSE;
+ switch (*argrest)
+ {
+ case 'c':
+ case 'm':
+ if (*(argrest + 1) == '\0')
+ ignore = TRUE;
+ break;
+ }
+ if (!ignore) { badarg = TRUE; break; }
+ }
+ break;
+
/* -Btype is a sendmail option for 7bit/8bit setting. Exim is 8-bit clean
so has no need of it. */
else if (*argrest != 0) { badarg = TRUE; break; }
}
- /* -be: Run in expansion test mode */
+ /* -be: Run in expansion test mode
+ -bem: Ditto, but read a message from a file first
+ */
else if (*argrest == 'e')
+ {
expansion_test = checking = TRUE;
+ if (argrest[1] == 'm')
+ {
+ if (++i >= argc) { badarg = TRUE; break; }
+ expansion_test_message = argv[i];
+ argrest++;
+ }
+ if (argrest[1] != 0) { badarg = TRUE; break; }
+ }
/* -bF: Run system filter test */
else if (Ustrcmp(argrest, "i") == 0) bi_option = TRUE;
+ /* -bI: provide information, of the type to follow after a colon.
+ This is an Exim flag. */
+
+ else if (argrest[0] == 'I' && Ustrlen(argrest) >= 2 && argrest[1] == ':')
+ {
+ uschar *p = &argrest[2];
+ info_flag = CMDINFO_HELP;
+ if (Ustrlen(p))
+ {
+ if (strcmpic(p, CUS"sieve") == 0)
+ {
+ info_flag = CMDINFO_SIEVE;
+ info_stdout = TRUE;
+ }
+ else if (strcmpic(p, CUS"dscp") == 0)
+ {
+ info_flag = CMDINFO_DSCP;
+ info_stdout = TRUE;
+ }
+ else if (strcmpic(p, CUS"help") == 0)
+ {
+ info_stdout = TRUE;
+ }
+ }
+ }
+
/* -bm: Accept and deliver message - the default option. Reinstate
receiving_message, which got turned off for all -b options. */
else if (Ustrcmp(argrest, "m") == 0) receiving_message = TRUE;
+ /* -bmalware: test the filename given for malware */
+
+ else if (Ustrcmp(argrest, "malware") == 0)
+ {
+ if (++i >= argc) { badarg = TRUE; break; }
+ malware_test_file = argv[i];
+ }
+
/* -bnq: For locally originating messages, do not qualify unqualified
addresses. In the envelope, this causes errors; in header lines they
just get left. */
else if (Ustrcmp(argrest, "P") == 0)
{
- list_options = TRUE;
- debug_selector |= D_v;
- debug_file = stderr;
+ /* -bP config: we need to setup here, because later,
+ * when list_options is checked, the config is read already */
+ 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;
+ }
}
/* -brt: Test retry configuration lookup */
show_whats_supported(stdout);
}
+ /* -bw: inetd wait mode, accept a listening socket as stdin */
+
+ else if (*argrest == 'w')
+ {
+ inetd_wait_mode = TRUE;
+ background_daemon = FALSE;
+ daemon_listen = TRUE;
+ if (*(++argrest) != '\0')
+ {
+ inetd_wait_timeout = readconf_readtime(argrest, 0, FALSE);
+ if (inetd_wait_timeout <= 0)
+ {
+ fprintf(stderr, "exim: bad time value %s: abandoned\n", argv[i]);
+ exit(EXIT_FAILURE);
+ }
+ }
+ }
+
else badarg = TRUE;
break;
}
}
#endif
+ if (real_uid != root_uid)
+ {
+ #ifdef TRUSTED_CONFIG_LIST
+
+ if (real_uid != exim_uid
+ #ifdef CONFIGURE_OWNER
+ && real_uid != config_uid
+ #endif
+ )
+ 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 */
+ {
+ trusted_config = FALSE;
+ fclose(trust_list);
+ }
+ else
+ {
+ /* Well, the trust list at least is up to scratch... */
+ void *reset_point = store_get(0);
+ uschar *trusted_configs[32];
+ int nr_configs = 0;
+ int i = 0;
+
+ 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 == 32)
+ break;
+ }
+ fclose(trust_list);
+
+ if (nr_configs)
+ {
+ int sep = 0;
+ const uschar *list = argrest;
+ uschar *filename;
+ while (trusted_config && (filename = string_nextinlist(&list,
+ &sep, big_buffer, big_buffer_size)) != NULL)
+ {
+ for (i=0; i < nr_configs; i++)
+ {
+ if (Ustrcmp(filename, trusted_configs[i]) == 0)
+ break;
+ }
+ if (i == nr_configs)
+ {
+ trusted_config = FALSE;
+ break;
+ }
+ }
+ store_reset(reset_point);
+ }
+ else
+ {
+ /* No valid prefixes found in trust_list file. */
+ trusted_config = FALSE;
+ }
+ }
+ }
+ else
+ {
+ /* Could not open trust_list file. */
+ trusted_config = FALSE;
+ }
+ }
+ #else
+ /* Not root; don't trust config */
+ trusted_config = FALSE;
+ #endif
+ }
config_main_filelist = argrest;
config_changed = TRUE;
argrest++;
}
if (*argrest != 0)
- decode_bits(&selector, NULL, D_memory, 0, argrest, debug_options,
- debug_options_count, US"debug");
+ decode_bits(&selector, 1, debug_notall, argrest,
+ debug_options, debug_options_count, US"debug", 0);
debug_selector = selector;
}
break;
case 'f':
{
- int start, end;
+ int dummy_start, dummy_end;
uschar *errmess;
if (*argrest == 0)
{
{ badarg = TRUE; break; }
}
if (*argrest == 0)
- {
sender_address = string_sprintf(""); /* Ensure writeable memory */
- }
else
{
uschar *temp = argrest + Ustrlen(argrest) - 1;
if (temp >= argrest && *temp == '.') f_end_dot = TRUE;
allow_domain_literals = TRUE;
strip_trailing_dot = TRUE;
- sender_address = parse_extract_address(argrest, &errmess, &start, &end,
- &sender_address_domain, TRUE);
+#ifdef SUPPORT_I18N
+ allow_utf8_domains = TRUE;
+#endif
+ sender_address = parse_extract_address(argrest, &errmess,
+ &dummy_start, &dummy_end, &sender_address_domain, TRUE);
+#ifdef SUPPORT_I18N
+ message_smtputf8 = string_is_utf8(sender_address);
+ allow_utf8_domains = FALSE;
+#endif
allow_domain_literals = FALSE;
strip_trailing_dot = FALSE;
if (sender_address == NULL)
}
break;
- /* This is some Sendmail thing which can be ignored */
+ /* -G: sendmail invocation to specify that it's a gateway submission and
+ sendmail may complain about problems instead of fixing them.
+ We make it equivalent to an ACL "control = suppress_local_fixups" and do
+ not at this time complain about problems. */
case 'G':
+ flag_G = TRUE;
break;
/* -h: Set the hop count for an incoming message. Exim does not currently
break;
+ /* -L: set the identifier used for syslog; equivalent to setting
+ syslog_processname in the config file, but needs to be an admin option. */
+
+ case 'L':
+ if (*argrest == '\0')
+ {
+ if(++i < argc) argrest = argv[i]; else
+ { badarg = TRUE; break; }
+ }
+ sz = Ustrlen(argrest);
+ if (sz > 32)
+ {
+ fprintf(stderr, "exim: the -L syslog name is too long: \"%s\"\n", argrest);
+ return EXIT_FAILURE;
+ }
+ if (sz < 1)
+ {
+ fprintf(stderr, "exim: the -L syslog name is too short\n");
+ return EXIT_FAILURE;
+ }
+ cmdline_syslog_name = argrest;
+ break;
+
case 'M':
receiving_message = FALSE;
if (Ustrcmp(argrest, "C") == 0)
{
+ union sockaddr_46 interface_sock;
+ EXIM_SOCKLEN_T size = sizeof(interface_sock);
+
if (argc != i + 6)
{
fprintf(stderr, "exim: too many or too few arguments after -MC\n");
return EXIT_FAILURE;
}
+ /* Set up $sending_ip_address and $sending_port */
+
+ if (getsockname(fileno(stdin), (struct sockaddr *)(&interface_sock),
+ &size) == 0)
+ sending_ip_address = host_ntoa(-1, &interface_sock, NULL,
+ &sending_port);
+ else
+ {
+ fprintf(stderr, "exim: getsockname() failed after -MC option: %s\n",
+ strerror(errno));
+ return EXIT_FAILURE;
+ }
+
if (running_in_test_harness) millisleep(500);
break;
}
break;
}
+ /* -MCD: set the smtp_use_dsn flag; this indicates that the host
+ that exim is connected to supports the esmtp extension DSN */
+ else if (Ustrcmp(argrest, "CD") == 0)
+ {
+ smtp_use_dsn = TRUE;
+ break;
+ }
+
/* -MCP: set the smtp_use_pipelining flag; this is useful only when
it preceded -MC (see above) */
-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
*/
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;
break;
- /* -n: This means "don't alias" in sendmail, apparently. Just ignore
- it. */
+ /* -n: This means "don't alias" in sendmail, apparently.
+ For normal invocations, it has no effect.
+ It may affect some other options. */
case 'n':
+ flag_n = TRUE;
break;
/* -O: Just ignore it. In sendmail, apparently -O option=value means set
else if (Ustrcmp(argrest, "Mi") == 0) interface_address = argv[++i];
+ /* -oMm: Message reference */
+
+ else if (Ustrcmp(argrest, "Mm") == 0)
+ {
+ if (!mac_ismsgid(argv[i+1]))
+ {
+ fprintf(stderr,"-oMm must be a valid message ID\n");
+ exit(EXIT_FAILURE);
+ }
+ if (!trusted_config)
+ {
+ fprintf(stderr,"-oMm must be called by a trusted user/config\n");
+ exit(EXIT_FAILURE);
+ }
+ message_reference = argv[++i];
+ }
+
/* -oMr: Received protocol */
else if (Ustrcmp(argrest, "Mr") == 0) received_protocol = argv[++i];
case 'q':
receiving_message = FALSE;
+ if (queue_interval >= 0)
+ {
+ fprintf(stderr, "exim: -q specified more than once\n");
+ exit(EXIT_FAILURE);
+ }
/* -qq...: Do queue runs in a 2-stage manner */
}
}
else deliver_selectstring = argrest;
- if (queue_interval < 0) queue_interval = 0;
break;
}
}
else deliver_selectstring_sender = argrest;
- if (queue_interval < 0) queue_interval = 0;
break;
/* -Tqt is an option that is exclusively for use by the testing suite.
/* -tls-on-connect: don't wait for STARTTLS (for old clients) */
#ifdef SUPPORT_TLS
- else if (Ustrcmp(argrest, "ls-on-connect") == 0) tls_on_connect = TRUE;
+ else if (Ustrcmp(argrest, "ls-on-connect") == 0) tls_in.on_connect = TRUE;
#endif
else badarg = TRUE;
if (*argrest != 0) badarg = TRUE;
break;
+ /* -X: in sendmail: takes one parameter, logfile, and sends debugging
+ logs to that file. We swallow the parameter and otherwise ignore it. */
+
+ case 'X':
+ if (*argrest == '\0')
+ if (++i >= argc)
+ {
+ fprintf(stderr, "exim: string expected after -X\n");
+ exit(EXIT_FAILURE);
+ }
+ break;
+
+ case 'z':
+ if (*argrest == '\0')
+ if (++i < argc) log_oneline = argv[i]; else
+ {
+ fprintf(stderr, "exim: file name expected after %s\n", argv[i-1]);
+ exit(EXIT_FAILURE);
+ }
+ break;
+
/* All other initial characters are errors */
default:
}
-/* Arguments have been processed. Check for incompatibilities. */
+/* If -R or -S have been specified without -q, assume a single queue run. */
+
+if ((deliver_selectstring != NULL || deliver_selectstring_sender != NULL) &&
+ queue_interval < 0) queue_interval = 0;
+
END_ARG:
+/* 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) &&
(daemon_listen || queue_interval >= 0 || bi_option ||
daemon_listen && queue_interval == 0
) ||
(
+ inetd_wait_mode && queue_interval >= 0
+ ) ||
+ (
list_options &&
(checking || smtp_input || extract_recipients ||
filter_test != FTEST_NONE || bi_option)
) ||
(
deliver_selectstring != NULL && queue_interval < 0
+ ) ||
+ (
+ msg_action == MSG_LOAD &&
+ (!expansion_test || expansion_test_message != NULL)
)
)
{
debug_printf("Exim version %s uid=%ld gid=%ld pid=%d D=%x\n",
version_string, (long int)real_uid, (long int)real_gid, (int)getpid(),
debug_selector);
- show_whats_supported(stderr);
+ if (!version_printed)
+ show_whats_supported(stderr);
}
}
save the group list here first. */
group_count = getgroups(NGROUPS_MAX, group_list);
+if (group_count < 0)
+ {
+ fprintf(stderr, "exim: getgroups() failed: %s\n", strerror(errno));
+ exit(EXIT_FAILURE);
+ }
/* There is a fundamental difference in some BSD systems in the matter of
groups. FreeBSD and BSDI are known to be different; NetBSD and OpenBSD are
/* 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 the exim user, or if this is a filter testing run, remove any
-setuid privilege the program has, and run as the underlying user.
+not root, or if this is a filter testing run, remove any setuid privilege the
+program has and run as the underlying user.
-If ALT_CONFIG_ROOT_ONLY is defined, the exim user is locked out of this, which
-severely restricts the use of -C for some purposes.
+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).
configuration file changes and macro definitions haven't happened. */
if (( /* EITHER */
- (config_changed || macros != NULL) && /* Config changed, and */
+ (!trusted_config || /* Config changed, or */
+ !macros_trusted()) && /* impermissible macros and */
real_uid != root_uid && /* Not root, and */
- #ifndef ALT_CONFIG_ROOT_ONLY /* (when not locked out) */
- real_uid != exim_uid && /* Not exim, and */
- #endif
!running_in_test_harness /* Not fudged */
) || /* OR */
expansion_test /* expansion testing */
and should be used for any logging information because attempts to write
to the log will usually fail. To arrange this, we unset really_exim. However,
if no stderr is available there is no point - we might as well have a go
- at the log (if it fails, syslog will be written). */
+ at the log (if it fails, syslog will be written).
- if (log_stderr != NULL) really_exim = FALSE;
+ Note that if the invoker is Exim, the logs remain available. Messing with
+ this causes unlogged successful deliveries. */
+
+ if ((log_stderr != NULL) && (real_uid != exim_uid))
+ really_exim = FALSE;
}
/* Privilege is to be retained for the moment. It may be dropped later,
}
}
+/* 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();
+
+#ifdef SUPPORT_I18N
+if (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. */
+configuration data for delivery can be read if needed.
+
+NOTE: immediatly after opening the configuration file we change the working
+directory to "/"! Later we change to $spool_directory. We do it there, because
+during readconf_main() some expansion takes place already. */
+
+/* Store the initial cwd before we change directories */
+if ((initial_cwd = getcwd(NULL, 0)) == NULL)
+ {
+ perror("exim: can't get the current working directory");
+ exit(EXIT_FAILURE);
+ }
readconf_main();
+/* 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)
+ admin_user = TRUE;
+else
+ {
+ int i, j;
+ for (i = 0; i < group_count; i++)
+ {
+ if (group_list[i] == exim_gid) admin_user = TRUE;
+ else if (admin_groups != NULL)
+ {
+ for (j = 1; j <= (int)(admin_groups[0]); j++)
+ if (admin_groups[j] == group_list[i])
+ { admin_user = TRUE; break; }
+ }
+ if (admin_user) break;
+ }
+ }
+
+/* Another group of privileged users are the trusted users. These are root,
+exim, and any caller matching trusted_users or trusted_groups. Trusted callers
+are permitted to specify sender_addresses with -f on the command line, and
+other message parameters as well. */
+
+if (real_uid == root_uid || real_uid == exim_uid)
+ trusted_caller = TRUE;
+else
+ {
+ int i, j;
+
+ if (trusted_users != NULL)
+ {
+ for (i = 1; i <= (int)(trusted_users[0]); i++)
+ if (trusted_users[i] == real_uid)
+ { trusted_caller = TRUE; break; }
+ }
+
+ if (!trusted_caller && trusted_groups != NULL)
+ {
+ for (i = 1; i <= (int)(trusted_groups[0]); i++)
+ {
+ if (trusted_groups[i] == real_gid)
+ trusted_caller = TRUE;
+ else for (j = 0; j < group_count; j++)
+ {
+ if (trusted_groups[i] == group_list[j])
+ { trusted_caller = TRUE; break; }
+ }
+ if (trusted_caller) break;
+ }
+ }
+ }
+
/* Handle the decoding of logging options. */
-decode_bits(&log_write_selector, &log_extra_selector, 0, 0, log_selector_string,
- log_options, log_options_count, US"log");
+decode_bits(log_selector, log_selector_size, log_notall,
+ log_selector_string, log_options, log_options_count, US"log", 0);
DEBUG(D_any)
{
+ int i;
debug_printf("configuration file is %s\n", config_main_filename);
- debug_printf("log selectors = %08x %08x\n", log_write_selector,
- log_extra_selector);
+ debug_printf("log selectors =");
+ for (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
}
}
+/* See if an admin user overrode our logging. */
+
+if (cmdline_syslog_name != NULL)
+ {
+ if (admin_user)
+ {
+ syslog_processname = cmdline_syslog_name;
+ log_file_path = string_copy(CUS"syslog");
+ }
+ else
+ {
+ /* not a panic, non-privileged users should not be able to spam paniclog */
+ fprintf(stderr,
+ "exim: you lack sufficient privilege to specify syslog process name\n");
+ return EXIT_FAILURE;
+ }
+ }
+
/* Paranoia check of maximum lengths of certain strings. There is a check
on the length of the log file path in log.c, which will come into effect
if there are any calls to write the log earlier than this. However, if we
log_write(0, LOG_MAIN|LOG_PANIC_DIE,
"syslog_processname is longer than 32 chars: aborting");
+if (log_oneline)
+ {
+ if (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
-TMPDIR macro, if this macro is defined. */
+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 TMPDIR
+#ifdef EXIM_TMPDIR
{
uschar **p;
- for (p = USS environ; *p != NULL; p++)
+ if (environ) for (p = USS environ; *p != NULL; p++)
{
if (Ustrncmp(*p, "TMPDIR=", 7) == 0 &&
- Ustrcmp(*p+7, TMPDIR) != 0)
+ Ustrcmp(*p+7, EXIM_TMPDIR) != 0)
{
- uschar *newp = malloc(Ustrlen(TMPDIR) + 8);
- sprintf(CS newp, "TMPDIR=%s", TMPDIR);
+ uschar *newp = 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", TMPDIR);
+ DEBUG(D_any) debug_printf("reset TMPDIR=%s in environment\n", EXIM_TMPDIR);
}
}
}
uschar **new;
uschar **newp;
int count = 0;
- while (*p++ != NULL) count++;
+ if (environ) while (*p++ != NULL) count++;
if (envtz == NULL) count++;
newp = new = malloc(sizeof(uschar *) * (count + 1));
- for (p = USS environ; *p != NULL; p++)
+ if (environ) for (p = USS environ; *p != NULL; p++)
{
if (Ustrncmp(*p, "TZ=", 3) == 0) continue;
*newp++ = *p;
}
/* 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, and, provided that
-ALT_CONFIG_ROOT_ONLY is not defined, was not the Exim user that is built into
-the binary.
+-D. This means that the caller of Exim was not root.
-If ALT_CONFIG_ROOT_ONLY is not defined, there is a problem if it turns out we
-were running as the exim user defined in the configuration file (different to
-the one in the binary). The sysadmin may expect this case to retain privilege
-because "the binary was called by the Exim user", but it hasn't, because of the
-order in which it handles this stuff. There are two possibilities:
+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
(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 the Exim user
- defined in the binary (when deliver_drop_ privilege is false).
-
-If ALT_CONFIG_ROOT_ONLY is defined, we don't know whether we were called by the
-built-in exim user or one defined in the configuration. In either event,
-re-enable log processing, assuming the sysadmin knows what they are doing. */
+ 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 && (config_changed || macros != NULL) &&
+if (removed_privilege && (!trusted_config || macros != NULL) &&
real_uid == exim_uid)
{
- #ifdef ALT_CONFIG_ROOT_ONLY
- really_exim = TRUE; /* let logging work normally */
- #else
-
if (deliver_drop_privilege)
really_exim = TRUE; /* let logging work normally */
else
log_write(0, LOG_MAIN|LOG_PANIC,
- "exim user (uid=%d) is defined only at runtime; privilege lost for %s",
- (int)exim_uid, config_changed? "-C" : "-D");
- #endif
+ "exim user lost privilege for using %s option",
+ trusted_config? "-D" : "-C");
}
/* Start up Perl interpreter if Perl support is configured and there is a
Don't attempt it if logging is disabled, or if listing variables or if
verifying/testing addresses or expansions. */
-if (((debug_selector & D_any) != 0 || (log_extra_selector & LX_arguments) != 0)
+if (((debug_selector & D_any) != 0 || LOGGING(arguments))
&& really_exim && !list_options && !checking)
{
int i;
uschar *p = big_buffer;
- Ustrcpy(p, "cwd=");
- (void)getcwd(CS p+4, big_buffer_size - 4);
+ Ustrcpy(p, "cwd= (failed)");
+
+ Ustrncpy(p + 4, initial_cwd, big_buffer_size-5);
+
while (*p) p++;
(void)string_format(p, big_buffer_size - (p - big_buffer), " %d args:", argc);
while (*p) p++;
for (i = 0; i < argc; i++)
{
int len = Ustrlen(argv[i]);
- uschar *printing;
+ const uschar *printing;
uschar *quote;
if (p + len + 8 >= big_buffer + big_buffer_size)
{
printing = string_printing(argv[i]);
if (printing[0] == 0) quote = US"\""; else
{
- uschar *pp = printing;
+ const uschar *pp = printing;
quote = US"";
while (*pp != 0) if (isspace(*pp++)) { quote = US"\""; break; }
}
while (*p) p++;
}
- if ((log_extra_selector & LX_arguments) != 0)
+ if (LOGGING(arguments))
log_write(0, LOG_MAIN, "%s", big_buffer);
else
debug_printf("%s\n", big_buffer);
if (Uchdir(spool_directory) != 0)
{
+ int dummy;
(void)directory_make(spool_directory, US"", SPOOL_DIRECTORY_MODE, FALSE);
- (void)Uchdir(spool_directory);
+ dummy = /* quieten compiler */ Uchdir(spool_directory);
}
/* Handle calls with the -bi option. This is a sendmail option to rebuild *the*
}
}
-/* If an action on specific messages is requested, or if a daemon or queue
-runner is being started, we need to know if Exim was called by an admin user.
-This is the case if the real user is root or exim, or if the real group is
-exim, or if one of the supplementary groups is exim or a group listed in
-admin_groups. We don't fail all message actions immediately if not admin_user,
-since some actions can be performed by non-admin users. Instead, set admin_user
-for later interrogation. */
-
-if (real_uid == root_uid || real_uid == exim_uid || real_gid == exim_gid)
- admin_user = TRUE;
-else
- {
- int i, j;
-
- for (i = 0; i < group_count; i++)
- {
- if (group_list[i] == exim_gid) admin_user = TRUE;
- else if (admin_groups != NULL)
- {
- for (j = 1; j <= (int)(admin_groups[0]); j++)
- if (admin_groups[j] == group_list[i])
- { admin_user = TRUE; break; }
- }
- if (admin_user) break;
- }
- }
-
-/* Another group of privileged users are the trusted users. These are root,
-exim, and any caller matching trusted_users or trusted_groups. Trusted callers
-are permitted to specify sender_addresses with -f on the command line, and
-other message parameters as well. */
-
-if (real_uid == root_uid || real_uid == exim_uid)
- trusted_caller = TRUE;
-else
- {
- int i, j;
-
- if (trusted_users != NULL)
- {
- for (i = 1; i <= (int)(trusted_users[0]); i++)
- if (trusted_users[i] == real_uid)
- { trusted_caller = TRUE; break; }
- }
-
- if (!trusted_caller && trusted_groups != NULL)
- {
- for (i = 1; i <= (int)(trusted_groups[0]); i++)
- {
- if (trusted_groups[i] == real_gid)
- trusted_caller = TRUE;
- else for (j = 0; j < group_count; j++)
- {
- if (trusted_groups[i] == group_list[j])
- { trusted_caller = TRUE; break; }
- }
- if (trusted_caller) break;
- }
- }
- }
+/* We moved the admin/trusted check to be immediately after reading the
+configuration file. We leave these prints here to ensure that syslog setup,
+logfile setup, and so on has already happened. */
if (trusted_caller) DEBUG(D_any) debug_printf("trusted user\n");
if (admin_user) DEBUG(D_any) debug_printf("admin user\n");
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. */
+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 (!admin_user)
{
BOOL debugset = (debug_selector & ~D_v) != 0;
- if (deliver_give_up || daemon_listen ||
+ if (deliver_give_up || daemon_listen || malware_test_file ||
(count_queue && queue_list_requires_admin) ||
(list_queue && queue_list_requires_admin) ||
(queue_interval >= 0 && prod_requires_admin) ||
interface_port = check_port(interface_address);
}
+/* If the caller is trusted, then they can use -G to suppress_local_fixups. */
+if (flag_G)
+ {
+ if (trusted_caller)
+ {
+ suppress_local_fixups = suppress_local_fixups_default = TRUE;
+ DEBUG(D_acl) debug_printf("suppress_local_fixups forced on by -G\n");
+ }
+ else
+ {
+ fprintf(stderr, "exim: permission denied (-G requires a trusted user)\n");
+ return EXIT_FAILURE;
+ }
+ }
+
/* If an SMTP message is being received check to see if the standard input is a
TCP/IP socket. If it is, we assume that Exim was called from inetd if the
caller is root or the Exim user, or if the port is a privileged one. Otherwise,
interface_address = host_ntoa(-1, &interface_sock, NULL,
&interface_port);
- if (host_is_tls_on_connect_port(interface_port)) tls_on_connect = TRUE;
+ if (host_is_tls_on_connect_port(interface_port)) tls_in.on_connect = TRUE;
if (real_uid == root_uid || real_uid == exim_uid || interface_port < 1024)
{
(is_inetd && smtp_load_reserve >= 0)
))
{
- load_average = os_getloadavg();
+ load_average = OS_GETLOADAVG();
}
#endif
/* When we are retaining a privileged uid, we still change to the exim gid. */
-else setgid(exim_gid);
+else
+ {
+ int rv;
+ rv = setgid(exim_gid);
+ /* Impact of failure is that some stuff might end up with an incorrect group.
+ We track this for failures from root, since any attempt to change privilege
+ by root should succeed and failures should be examined. For non-root,
+ there's no security risk. For me, it's { exim -bV } on a just-built binary,
+ no need to complain then. */
+ if (rv == -1)
+ {
+ if (!(unprivileged || removed_privilege))
+ {
+ fprintf(stderr,
+ "exim: changing group failed: %s\n", strerror(errno));
+ exit(EXIT_FAILURE);
+ }
+ else
+ DEBUG(D_any) debug_printf("changing group to %ld failed: %s\n",
+ (long int)exim_gid, strerror(errno));
+ }
+ }
+
+/* Handle a request to scan a file for malware */
+if (malware_test_file)
+ {
+#ifdef WITH_CONTENT_SCAN
+ int result;
+ set_process_info("scanning file for malware");
+ result = malware_in_file(malware_test_file);
+ if (result == 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 */
exit(yield);
}
-/* All the modes below here require the remaining configuration sections
-to be read, except that we can skip over the ACL setting when delivering
-specific messages, or doing a queue run. (For various testing cases we could
-skip too, but as they are rare, it doesn't really matter.) The argument is TRUE
-for skipping. */
+/* We used to set up here to skip reading the ACL section, on
+ (msg_action_arg > 0 || (queue_interval == 0 && !daemon_listen)
+Now, since the intro of the ${acl } expansion, ACL definitions may be
+needed in transports so we lost the optimisation. */
-readconf_rest(msg_action_arg > 0 || (queue_interval == 0 && !daemon_listen));
+readconf_rest();
/* The configuration data will have been read into POOL_PERM because we won't
ever want to reset back past it. Change the current pool to POOL_MAIN. In fact,
}
/* Handle a request to list one or more configuration options */
+/* If -n was set, we suppress some information */
if (list_options)
{
set_process_info("listing variables");
- if (recipients_arg >= argc) readconf_print(US"all", NULL);
+ if (recipients_arg >= argc) readconf_print(US"all", NULL, flag_n);
else for (i = recipients_arg; i < argc; i++)
{
if (i < argc - 1 &&
(Ustrcmp(argv[i], "router") == 0 ||
Ustrcmp(argv[i], "transport") == 0 ||
- Ustrcmp(argv[i], "authenticator") == 0))
+ Ustrcmp(argv[i], "authenticator") == 0 ||
+ Ustrcmp(argv[i], "macro") == 0 ||
+ Ustrcmp(argv[i], "environment") == 0))
{
- readconf_print(argv[i+1], argv[i]);
+ readconf_print(argv[i+1], argv[i], flag_n);
i++;
}
- else readconf_print(argv[i], NULL);
+ else readconf_print(argv[i], NULL, flag_n);
}
exim_exit(EXIT_SUCCESS);
}
+if (list_config)
+ {
+ set_process_info("listing config");
+ readconf_print(US"config", NULL, flag_n);
+ exim_exit(EXIT_SUCCESS);
+ }
+
+
+/* Initialise subsystems as required */
+#ifndef DISABLE_DKIM
+dkim_exim_init();
+#endif
+
/* 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
for incoming messages via the daemon. The daemon cannot be run in mua_wrapper
mode. */
-if (daemon_listen || queue_interval > 0)
+if (daemon_listen || inetd_wait_mode || queue_interval > 0)
{
if (mua_wrapper)
{
/* 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. */
+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)
{
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 != NULL)
+ {
+ int save_stdin = dup(0);
+ int fd = Uopen(expansion_test_message, O_RDONLY, 0);
+ if (fd < 0)
+ {
+ fprintf(stderr, "exim: failed to open %s: %s\n", expansion_test_message,
+ strerror(errno));
+ return EXIT_FAILURE;
+ }
+ (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 */
+ }
+
+ /* Allow $recipients for this testing */
+
+ enable_dollar_recipients = TRUE;
+
/* Expand command line items */
if (recipients_arg < argc)
else
{
- char *(*fn_readline)(char *) = NULL;
- char *(*fn_addhist)(char *) = NULL;
+ char *(*fn_readline)(const char *) = NULL;
+ void (*fn_addhist)(const char *) = NULL;
#ifdef USE_READLINE
void *dlhandle = set_readline(&fn_readline, &fn_addhist);
"**** 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)
- log_write_selector &= ~L_smtp_connection;
+ 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())
{
reset_point = store_get(0);
if (smtp_setup_msg() <= 0) break;
if (!receive_msg(FALSE)) break;
}
+ 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. In the former case, show the configuration file name. */
+verification test or info dump.
+In the former case, show the configuration file name. */
if (recipients_arg >= argc && !extract_recipients && !smtp_input)
{
printf("Configuration file is %s\n", config_main_filename);
return EXIT_SUCCESS;
}
- if (filter_test == FTEST_NONE)
+
+ if (info_flag != CMDINFO_NONE)
{
- fprintf(stderr,
-"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");
- return EXIT_FAILURE;
+ 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);
}
deliver_drop_privilege = TRUE;
queue_smtp = FALSE;
queue_smtp_domains = NULL;
+#ifdef SUPPORT_I18N
+ message_utf8_downconvert = -1; /* convert-if-needed */
+#endif
}
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, and if we are
-in a non-local SMTP state it means we have come via inetd and 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. */
+/* 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 (sender_local) set_process_info("accepting a local SMTP message from <%s>",
- sender_address);
+ if (!is_inetd) set_process_info("accepting a local %sSMTP message from <%s>",
+ smtp_batched_input? "batched " : "",
+ (sender_address!= NULL)? sender_address : originator_login);
}
else
{
sender_address);
}
-/* Initialize the local_queue-only flag (this will be ignored if mua_wrapper is
-set) */
+/* Initialize the session_local_queue-only flag (this will be ignored if
+mua_wrapper is set) */
queue_check_only();
-local_queue_only = queue_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
return EXIT_FAILURE;
}
-/* If this is smtp input of any kind, handle the start of the SMTP
-session. */
+/* 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)
- log_write_selector &= ~L_smtp_connection;
+ 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())
{
store_reset(reset_point);
message_id[0] = 0;
- /* In the SMTP case, we have to handle the initial SMTP input and build the
- recipients list, before calling receive_msg() to read the message proper.
- Whatever sender address is actually given in the SMTP transaction is
- actually 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.
-
- However, if this value is NULL, we are dealing with a trusted caller when
- -f was not used; in this case, the SMTP sender is allowed to stand.
-
- Also, if untrusted_set_sender is set, we permit sender addresses that match
- anything in its list.
-
- The variable raw_sender_address holds the sender address before rewriting. */
+ /* 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)
{
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;
+ enable_dollar_recipients = TRUE;
+ (void)acl_check(ACL_WHERE_NOTSMTP_START, NULL, acl_not_smtp_start,
+ &user_msg, &log_msg);
+ enable_dollar_recipients = FALSE;
+ }
+
+ /* Now get the data for the message */
+
more = receive_msg(extract_recipients);
if (message_id[0] == 0)
{
if (more) continue;
+ smtp_log_no_mail(); /* Log no mail if configured */
exim_exit(EXIT_FAILURE);
}
}
- else exim_exit((rc == 0)? EXIT_SUCCESS : EXIT_FAILURE);
+ else
+ {
+ smtp_log_no_mail(); /* Log no mail if configured */
+ exim_exit((rc == 0)? EXIT_SUCCESS : EXIT_FAILURE);
+ }
}
/* In the non-SMTP case, we have all the information from the command
if (recipients_max > 0 && ++rcount > recipients_max &&
!extract_recipients)
- {
if (error_handling == ERRORS_STDERR)
{
fprintf(stderr, "exim: too many recipients\n");
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 (string_is_utf8(recipient))
+ message_smtputf8 = TRUE;
+ else
+ allow_utf8_domains = b;
+ }
+#endif
if (domain == 0 && !allow_unqualified_recipient)
{
recipient = NULL;
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(
if (ftest_prefix != NULL) printf("Prefix = %s\n", ftest_prefix);
if (ftest_suffix != NULL) printf("Suffix = %s\n", ftest_suffix);
- (void)chdir("/"); /* Get away from wherever the user is running this from */
+ 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
}
/* 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, local_queue_only will be
- TRUE. If it is not, check on the number of messages received in this
- connection. If that's OK and queue_only_load is set, check that the load
- average is below it. If it is not, set local_queue_only TRUE. Note that 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. */
-
- if (!local_queue_only)
+ 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)
{
- if (smtp_accept_queue_per_connection > 0 &&
- receive_messagecount > smtp_accept_queue_per_connection)
- {
- local_queue_only = TRUE;
- queue_only_reason = 2;
- }
- else if (queue_only_load >= 0)
+ 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. */
+
+ local_queue_only = session_local_queue_only;
+ if (!local_queue_only && queue_only_load >= 0)
+ {
+ local_queue_only = (load_average = OS_GETLOADAVG()) > queue_only_load;
+ if (local_queue_only)
{
- local_queue_only = (load_average = os_getloadavg()) > queue_only_load;
- if (local_queue_only) queue_only_reason = 3;
+ queue_only_reason = 3;
+ if (queue_only_load_latch) session_local_queue_only = TRUE;
}
}