X-Git-Url: https://git.exim.org/exim.git/blobdiff_plain/79d4bc3d95d75446a2d149ca35525f078a978027..44bbabb570db6e700a31469a0faf2ac27bf3bfe0:/src/src/exim.c diff --git a/src/src/exim.c b/src/src/exim.c index 729114c1c..b4ea01dcd 100644 --- a/src/src/exim.c +++ b/src/src/exim.c @@ -1,5 +1,3 @@ -/* $Cambridge: exim/src/src/exim.c,v 1.71 2010/06/07 00:12:42 pdp Exp $ */ - /************************************************* * Exim - an Internet mail transport agent * *************************************************/ @@ -14,6 +12,8 @@ Also a few functions that don't naturally fit elsewhere. */ #include "exim.h" +extern void init_lookup_list(void); + /************************************************* @@ -138,6 +138,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 +179,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 +188,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); } @@ -346,35 +402,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 +422,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); @@ -568,17 +595,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"); } @@ -695,6 +725,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) @@ -776,53 +808,53 @@ fprintf(f, "Support for:"); #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"); @@ -837,6 +869,9 @@ fprintf(f, "Authenticators:"); #ifdef AUTH_DOVECOT fprintf(f, " dovecot"); #endif +#ifdef AUTH_GSASL + fprintf(f, " gsasl"); +#endif #ifdef AUTH_PLAINTEXT fprintf(f, " plaintext"); #endif @@ -907,13 +942,71 @@ if (fixed_never_users[0] > 0) fprintf(f, "Size of off_t: " SIZE_T_FMT "\n", sizeof(off_t)); -/* This runtime check is to help diagnose library linkage mismatches which -result in segfaults and the like; as such, it's left until the end, -just in case. There will still be a "Configuration file is" line still to -come. */ +/* Everything else is details which are only worth reporting when debugging. +Perhaps the tls_version_report should move into this too. */ +DEBUG(D_any) do { + + int i; + +/* clang defines __GNUC__ (at least, for me) so test for it first */ +#if defined(__clang__) + fprintf(f, "Compiler: CLang [%s]\n", __clang_version__); +#elif defined(__GNUC__) + fprintf(f, "Compiler: GCC [%s]\n", +# ifdef __VERSION__ + __VERSION__ +# else + "? unknown version ?" +# endif + ); +#else + fprintf(f, "Compiler: \n"); +#endif + #ifdef SUPPORT_TLS -tls_version_report(f); + tls_version_report(f); +#endif + + for (authi = auths_available; *authi->driver_name != '\0'; ++authi) { + if (authi->version_report) { + (*authi->version_report)(f); + } + } + + 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 +# define STRINGIFY(x) #x + STRINGIFY(PCRE_PRERELEASE) "", +# undef STRINGIFY +#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); } @@ -990,19 +1083,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 +1129,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 +1228,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 +1368,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; @@ -1240,7 +1448,19 @@ if (route_finduser(US EXIM_USERNAME, &pw, &exim_uid)) EXIM_USERNAME); exit(EXIT_FAILURE); } - exim_gid = pw->pw_gid; + /* If ref:name uses a number as the name, route_finduser() returns + TRUE with exim_uid set and pw coerced to NULL. */ + if (pw) + exim_gid = pw->pw_gid; +#ifndef EXIM_GROUPNAME + else + { + fprintf(stderr, + "exim: ref:name should specify a usercode, not a group.\n" + "exim: can't let you get away with it unless you also specify a group.\n"); + exit(EXIT_FAILURE); + } +#endif } else { @@ -1417,6 +1637,15 @@ 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 + + /* 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. */ @@ -1490,8 +1719,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 @@ -1851,13 +2092,17 @@ for (i = 1; i < argc; i++) #endif if (real_uid != root_uid) { - #ifdef TRUSTED_CONFIG_PREFIX_LIST + #ifdef TRUSTED_CONFIG_LIST - if (Ustrstr(argrest, "/../")) + if (real_uid != exim_uid + #ifdef CONFIGURE_OWNER + && real_uid != config_uid + #endif + ) trusted_config = FALSE; else { - FILE *trust_list = Ufopen(TRUSTED_CONFIG_PREFIX_LIST, "rb"); + FILE *trust_list = Ufopen(TRUSTED_CONFIG_LIST, "rb"); if (trust_list) { struct stat statbuf; @@ -1883,8 +2128,8 @@ for (i = 1; i < argc; i++) { /* Well, the trust list at least is up to scratch... */ void *reset_point = store_get(0); - uschar *trusted_prefixes[32]; - int nr_prefixes = 0; + uschar *trusted_configs[32]; + int nr_configs = 0; int i = 0; while (Ufgets(big_buffer, big_buffer_size, trust_list)) @@ -1897,13 +2142,13 @@ for (i = 1; i < argc; i++) nl = Ustrchr(start, '\n'); if (nl) *nl = 0; - trusted_prefixes[nr_prefixes++] = string_copy(start); - if (nr_prefixes == 32) + trusted_configs[nr_configs++] = string_copy(start); + if (nr_configs == 32) break; } fclose(trust_list); - if (nr_prefixes) + if (nr_configs) { int sep = 0; uschar *list = argrest; @@ -1911,14 +2156,12 @@ for (i = 1; i < argc; i++) while (trusted_config && (filename = string_nextinlist(&list, &sep, big_buffer, big_buffer_size)) != NULL) { - for (i=0; i < nr_prefixes; i++) + for (i=0; i < nr_configs; i++) { - int len = Ustrlen(trusted_prefixes[i]); - if (Ustrlen(filename) >= len && - Ustrncmp(filename, trusted_prefixes[i], len) == 0) + if (Ustrcmp(filename, trusted_configs[i]) == 0) break; } - if (i == nr_prefixes) + if (i == nr_configs) { trusted_config = FALSE; break; @@ -3021,7 +3264,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); } } @@ -3101,6 +3345,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 @@ -3145,7 +3394,8 @@ 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 */ - (!trusted_config || macros != NULL) && /* Config changed, and */ + (!trusted_config || /* Config changed, or */ + !macros_trusted()) && /* impermissible macros and */ real_uid != root_uid && /* Not root, and */ !running_in_test_harness /* Not fudged */ ) || /* OR */ @@ -3162,9 +3412,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, @@ -3362,7 +3616,7 @@ if (removed_privilege && (!trusted_config || macros != NULL) && else log_write(0, LOG_MAIN|LOG_PANIC, "exim user lost privilege for using %s option", - (int)exim_uid, trusted_config? "-D" : "-C"); + trusted_config? "-D" : "-C"); } /* Start up Perl interpreter if Perl support is configured and there is a @@ -3387,6 +3641,13 @@ if (opt_perl_at_start && opt_perl_startup != NULL) } #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 @@ -3702,7 +3963,28 @@ if (!unprivileged && /* originally had root AND */ /* 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) @@ -4334,8 +4616,8 @@ if (expansion_test) 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);