-/* $Cambridge: exim/src/src/exim.c,v 1.68 2010/06/06 02:08:50 pdp Exp $ */
+/* $Cambridge: exim/src/src/exim.c,v 1.71 2010/06/07 00:12:42 pdp Exp $ */
/*************************************************
* Exim - an Internet mail transport agent *
#include "exim.h"
+extern void init_lookup_list(void);
+
/*************************************************
#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
+#if defined(LOOKUP_DBM) && LOOKUP_DBM!=2
fprintf(f, " dbm 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_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");
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));
/* This runtime check is to help diagnose library linkage mismatches which
result in segfaults and the like; as such, it's left until the end,
#ifdef SUPPORT_TLS
tls_version_report(f);
#endif
+
+/* 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;
+
+#ifdef AUTH_CYRUS_SASL
+ auth_cyrus_sasl_version_report(f);
+#endif
+
+ fprintf(f, "Library version: PCRE: Compile: %d.%d%s\n"
+ " Runtime: %s\n",
+ PCRE_MAJOR, PCRE_MINOR,
+ /* PRE_PRERELEASE is either defined and empty or a string.
+ * unless its an ancient version of PCRE in which case it
+ * is not defined */
+#ifdef PCRE_PRERELEASE
+ PCRE_PRERELEASE "",
+#else
+ "",
+#endif
+ pcre_version());
+
+ 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);
}
+/*************************************************
+* 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_printf("macros_trusted overriden 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;
#ifdef EXIM_USERNAME
if (route_finduser(US EXIM_USERNAME, &pw, &exim_uid))
{
+ if (exim_uid == 0)
+ {
+ fprintf(stderr, "exim: refusing to run with uid 0 for \"%s\"\n",
+ EXIM_USERNAME);
+ exit(EXIT_FAILURE);
+ }
exim_gid = pw->pw_gid;
}
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))
{
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
+
+
/* 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
else if (Ustrcmp(argrest, "version") == 0)
{
switchchar = 'b';
- argrest = "V";
+ argrest = US"V";
}
}
}
}
#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;
+ 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;
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);
}
}
/* 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,
}
/* 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
}
#endif /* EXIM_PERL */
+/* Initialise lookup_list
+If debugging, already called above via version reporting.
+This does mean that debugging causes the list to be initialised while root.
+This *should* be harmless -- all modules are loaded from a fixed dir and
+it's code that would, if not a module, be part of Exim already. */
+init_lookup_list();
+
/* 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
/* 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);
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);
}