X-Git-Url: https://git.exim.org/exim.git/blobdiff_plain/ed7f7860402395aedcb9e9c0cbade291c675a12f..93a680e4c9c899d86ff3fde0933fb5367b34af50:/src/src/exim.c diff --git a/src/src/exim.c b/src/src/exim.c index 7d1c3b6a6..ddb35982d 100644 --- a/src/src/exim.c +++ b/src/src/exim.c @@ -1,10 +1,8 @@ -/* $Cambridge: exim/src/src/exim.c,v 1.67 2010/06/06 00:27:52 pdp Exp $ */ - /************************************************* * Exim - an Internet mail transport agent * *************************************************/ -/* Copyright (c) University of Cambridge 1995 - 2009 */ +/* Copyright (c) University of Cambridge 1995 - 2016 */ /* See the file NOTICE for conditions of use and distribution. */ @@ -14,6 +12,15 @@ Also a few functions that don't naturally fit elsewhere. */ #include "exim.h" +#ifdef USE_GNUTLS +# include +# if GNUTLS_VERSION_NUMBER < 0x030103 && !defined(DISABLE_OCSP) +# define DISABLE_OCSP +# endif +#endif + +extern void init_lookup_list(void); + /************************************************* @@ -52,6 +59,16 @@ store_free(block); +/************************************************* +* Enums for cmdline interface * +*************************************************/ + +enum commandline_info { CMDINFO_NONE=0, + CMDINFO_HELP, CMDINFO_SIEVE, CMDINFO_DSCP }; + + + + /************************************************* * Compile regular expression and panic on fail * *************************************************/ @@ -71,7 +88,7 @@ Returns: pointer to the compiled pattern */ 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; @@ -83,7 +100,7 @@ if (use_malloc) 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) @@ -114,10 +131,11 @@ Returns: TRUE or FALSE */ 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; @@ -127,7 +145,7 @@ if (yield) 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--; @@ -138,6 +156,38 @@ return yield; +/************************************************* +* Set up processing details * +*************************************************/ + +/* Save a text string for dumping when SIGUSR1 is received. +Do checks for overruns. + +Arguments: format and arguments, as for printf() +Returns: nothing +*/ + +void +set_process_info(const char *format, ...) +{ +int len; +va_list ap; +sprintf(CS process_info, "%5d ", (int)getpid()); +len = Ustrlen(process_info); +va_start(ap, format); +if (!string_vformat(process_info + len, PROCESS_INFO_SIZE - len - 2, format, ap)) + Ustrcpy(process_info + len, "**** string overflowed buffer ****"); +len = Ustrlen(process_info); +process_info[len+0] = '\n'; +process_info[len+1] = '\0'; +process_info_len = len + 1; +DEBUG(D_process_info) debug_printf("set_process_info: %s", process_info); +va_end(ap); +} + + + + /************************************************* * Handler for SIGUSR1 * *************************************************/ @@ -147,6 +197,8 @@ what it is currently doing. It will only be used if the OS is capable of setting up a handler that causes automatic restarting of any system call that is in progress at the time. +This function takes care to be signal-safe. + Argument: the signal number (SIGUSR1) Returns: nothing */ @@ -154,10 +206,32 @@ 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); } @@ -201,6 +275,10 @@ 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 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 */ @@ -210,6 +288,9 @@ milliwait(struct itimerval *itval) { 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 */ @@ -332,10 +413,11 @@ if (exim_tvcmp(&now_tv, then_tv) <= 0) { 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); } } @@ -346,35 +428,6 @@ if (exim_tvcmp(&now_tv, then_tv) <= 0) -/************************************************* -* Set up processing details * -*************************************************/ - -/* Save a text string for dumping when SIGUSR1 is received. -Do checks for overruns. - -Arguments: format and arguments, as for printf() -Returns: nothing -*/ - -void -set_process_info(char *format, ...) -{ -int len; -va_list ap; -sprintf(CS process_info, "%5d ", (int)getpid()); -len = Ustrlen(process_info); -va_start(ap, format); -if (!string_vformat(process_info + len, PROCESS_INFO_SIZE - len, format, ap)) - Ustrcpy(process_info + len, "**** string overflowed buffer ****"); -DEBUG(D_process_info) debug_printf("set_process_info: %s\n", process_info); -va_end(ap); -} - - - - - /************************************************* * Call fopen() with umask 777 and adjust mode * *************************************************/ @@ -395,7 +448,7 @@ Returns: the fopened FILE or NULL */ 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); @@ -489,7 +542,7 @@ close_unwanted(void) 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)); @@ -568,17 +621,20 @@ if (euid == root_uid || euid != uid || egid != gid || igflag) 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(" ", strerror(save_errno)); else debug_printf(" "); debug_printf("\n"); } @@ -685,8 +741,8 @@ else * 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 @@ -695,6 +751,8 @@ 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) @@ -759,8 +817,26 @@ fprintf(f, "Support for:"); #ifndef DISABLE_DKIM fprintf(f, " DKIM"); #endif -#ifdef WITH_OLD_DEMIME - fprintf(f, " Old_Demime"); +#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"); @@ -771,58 +847,70 @@ fprintf(f, "Support for:"); #ifdef EXPERIMENTAL_BRIGHTMAIL fprintf(f, " Experimental_Brightmail"); #endif +#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"); @@ -837,12 +925,21 @@ fprintf(f, "Authenticators:"); #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:"); @@ -905,18 +1002,109 @@ if (fixed_never_users[0] > 0) 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: \n"); +#endif -/* This runtime check is to help diagnose library linkage mismatches which -result in segfaults and the like; as such, it's left until the end, -just in case. There will still be a "Configuration file is" line still to -come. */ #ifdef SUPPORT_TLS -tls_version_report(f); + 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; + } +} /************************************************* @@ -990,19 +1178,23 @@ Returns: the dlopen handle or NULL on failure */ 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 { @@ -1032,7 +1224,7 @@ Returns: pointer to dynamic memory, or NULL at end of file */ 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; @@ -1131,6 +1323,117 @@ 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 * *************************************************/ @@ -1160,7 +1463,7 @@ int arg_error_handling = error_handling; 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; @@ -1179,11 +1482,14 @@ 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 more = TRUE; BOOL one_msg_action = FALSE; @@ -1199,6 +1505,7 @@ 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; @@ -1206,9 +1513,11 @@ 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; @@ -1217,6 +1526,10 @@ pid_t passed_qr_pid = (pid_t)0; 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" }; @@ -1234,7 +1547,25 @@ This is a feature to make the lives of binary distributors easier. */ #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 { @@ -1262,6 +1593,10 @@ if (!route_finduser(US CONFIGURE_OWNERNAME, NULL, &config_uid)) } #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)) { @@ -1271,8 +1606,9 @@ 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 @@ -1305,6 +1641,10 @@ if (log_buffer == NULL) 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". @@ -1407,6 +1747,16 @@ 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. */ @@ -1480,8 +1830,20 @@ real_gid = getgid(); 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 @@ -1489,16 +1851,6 @@ running in an unprivileged state. */ unprivileged = (real_uid != root_uid && original_euid != root_uid); -/* If the first argument is --help, set usage_wanted and pretend there -are no arguments. This will cause a brief message to be given. We do -the message generation downstream so we can pick up how we were invoked */ - -if (argc > 1 && Ustrcmp(argv[1], "--help") == 0) - { - argc = 1; - usage_wanted = TRUE; - } - /* 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. */ @@ -1563,10 +1915,45 @@ for (i = 1; i < argc; i++) 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. */ @@ -1609,7 +1996,7 @@ for (i = 1; i < argc; i++) else if (*argrest == 'F') { - filter_test |= FTEST_SYSTEM; + filter_test |= checking = FTEST_SYSTEM; if (*(++argrest) != 0) { badarg = TRUE; break; } if (++i < argc) filter_test_sfile = argv[i]; else { @@ -1629,7 +2016,7 @@ for (i = 1; i < argc; i++) { if (*(++argrest) == 0) { - filter_test |= FTEST_USER; + filter_test |= checking = FTEST_USER; if (++i < argc) filter_test_ufile = argv[i]; else { fprintf(stderr, "exim: file name expected after %s\n", argv[i-1]); @@ -1668,6 +2055,32 @@ for (i = 1; i < argc; i++) 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. */ @@ -1678,6 +2091,7 @@ for (i = 1; i < argc; i++) else if (Ustrcmp(argrest, "malware") == 0) { if (++i >= argc) { badarg = TRUE; break; } + checking = TRUE; malware_test_file = argv[i]; } @@ -1740,15 +2154,26 @@ for (i = 1; i < argc; i++) 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 */ else if (Ustrcmp(argrest, "rt") == 0) { + checking = TRUE; test_retry_arg = i + 1; goto END_ARG; } @@ -1757,6 +2182,7 @@ for (i = 1; i < argc; i++) else if (Ustrcmp(argrest, "rw") == 0) { + checking = TRUE; test_rewrite_arg = i + 1; goto END_ARG; } @@ -1799,6 +2225,25 @@ for (i = 1; i < argc; i++) printf("%s\n", CS version_copyright); version_printed = TRUE; show_whats_supported(stdout); + log_testing_mode = TRUE; + } + + /* -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; @@ -1834,6 +2279,103 @@ for (i = 1; i < argc; i++) } } #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; @@ -1930,8 +2472,8 @@ for (i = 1; i < argc; i++) argrest++; } if (*argrest != 0) - decode_bits(&selector, NULL, D_memory, 0, argrest, debug_options, - debug_options_count, US"debug", 0); + decode_bits(&selector, 1, debug_notall, argrest, + debug_options, debug_options_count, US"debug", 0); debug_selector = selector; } break; @@ -2003,7 +2545,7 @@ for (i = 1; i < argc; i++) case 'f': { - int start, end; + int dummy_start, dummy_end; uschar *errmess; if (*argrest == 0) { @@ -2011,9 +2553,7 @@ for (i = 1; i < argc; i++) { badarg = TRUE; break; } } if (*argrest == 0) - { sender_address = string_sprintf(""); /* Ensure writeable memory */ - } else { uschar *temp = argrest + Ustrlen(argrest) - 1; @@ -2021,8 +2561,15 @@ for (i = 1; i < argc; i++) 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) @@ -2035,9 +2582,13 @@ for (i = 1; i < argc; i++) } 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 @@ -2062,6 +2613,29 @@ for (i = 1; i < argc; i++) 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; @@ -2139,6 +2713,14 @@ for (i = 1; i < argc; i++) 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) */ @@ -2324,10 +2906,12 @@ for (i = 1; i < argc; i++) 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 @@ -2470,6 +3054,23 @@ for (i = 1; i < argc; i++) 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]; @@ -2775,7 +3376,7 @@ for (i = 1; i < argc; i++) /* -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; @@ -2816,6 +3417,27 @@ for (i = 1; i < argc; i++) 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: @@ -2866,6 +3488,9 @@ if (( daemon_listen && queue_interval == 0 ) || ( + inetd_wait_mode && queue_interval >= 0 + ) || + ( list_options && (checking || smtp_input || extract_recipients || filter_test != FTEST_NONE || bi_option) @@ -2911,7 +3536,8 @@ if (debug_selector != 0) debug_printf("Exim version %s uid=%ld gid=%ld pid=%d D=%x\n", version_string, (long int)real_uid, (long int)real_gid, (int)getpid(), debug_selector); - show_whats_supported(stderr); + if (!version_printed) + show_whats_supported(stderr); } } @@ -2991,6 +3617,11 @@ till after reading the config, which might specify the exim gid. Therefore, save the group list here first. */ group_count = getgroups(NGROUPS_MAX, group_list); +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 @@ -3019,11 +3650,11 @@ if (setgroups(0, NULL) != 0) /* 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). @@ -3035,11 +3666,9 @@ 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 */ - (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 */ @@ -3055,9 +3684,13 @@ if (( /* EITHER */ and should be used for any logging information because attempts to write to the log will usually fail. To arrange this, we unset really_exim. However, if no stderr is available there is no point - we might as well have a go - 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, @@ -3092,22 +3725,133 @@ if ((filter_test & FTEST_USER) != 0) } } +/* Initialise lookup_list +If debugging, already called above via version reporting. +In either case, we initialise the list of available lookups while running +as root. All dynamically modules are loaded from a directory which is +hard-coded into the binary and is code which, if not a module, would be +part of Exim already. Ability to modify the content of the directory +is equivalent to the ability to modify a setuid binary! + +This needs to happen before we read the main configuration. */ +init_lookup_list(); + +#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 = os_getcwd(NULL, 0)) == NULL) + { + perror("exim: can't get the current working directory"); + exit(EXIT_FAILURE); + } + +/* 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