X-Git-Url: https://git.exim.org/exim.git/blobdiff_plain/a5a28604d8bf14b6521ed3ab028872b9cafd1b24..e2f5dc151e2e79058e93924e6d35510557f0535d:/src/src/exim.c diff --git a/src/src/exim.c b/src/src/exim.c index c38a58c54..0d8f24492 100644 --- a/src/src/exim.c +++ b/src/src/exim.c @@ -1,10 +1,10 @@ -/* $Cambridge: exim/src/src/exim.c,v 1.13 2005/01/11 15:51:02 ph10 Exp $ */ +/* $Cambridge: exim/src/src/exim.c,v 1.71 2010/06/07 00:12:42 pdp Exp $ */ /************************************************* * Exim - an Internet mail transport agent * *************************************************/ -/* Copyright (c) University of Cambridge 1995 - 2005 */ +/* Copyright (c) University of Cambridge 1995 - 2009 */ /* See the file NOTICE for conditions of use and distribution. */ @@ -375,6 +375,38 @@ va_end(ap); +/************************************************* +* Call fopen() with umask 777 and adjust mode * +*************************************************/ + +/* Exim runs with umask(0) so that files created with open() have the mode that +is specified in the open() call. However, there are some files, typically in +the spool directory, that are created with fopen(). They end up world-writeable +if no precautions are taken. Although the spool directory is not accessible to +the world, this is an untidiness. So this is a wrapper function for fopen() +that sorts out the mode of the created file. + +Arguments: + filename the file name + options the fopen() options + mode the required mode + +Returns: the fopened FILE or NULL +*/ + +FILE * +modefopen(uschar *filename, char *options, mode_t mode) +{ +mode_t saved_umask = umask(0777); +FILE *f = Ufopen(filename, options); +(void)umask(saved_umask); +if (f != NULL) (void)fchmod(fileno(f), mode); +return f; +} + + + + /************************************************* * Ensure stdin, stdout, and stderr exist * *************************************************/ @@ -406,10 +438,10 @@ for (i = 0; i <= 2; i++) if (devnull < 0) devnull = open("/dev/null", O_RDWR); if (devnull < 0) log_write(0, LOG_MAIN|LOG_PANIC_DIE, "%s", string_open_failed(errno, "/dev/null")); - if (devnull != i) dup2(devnull, i); + if (devnull != i) (void)dup2(devnull, i); } } -if (devnull > 2) close(devnull); +if (devnull > 2) (void)close(devnull); } @@ -459,19 +491,19 @@ if (smtp_input) #ifdef SUPPORT_TLS tls_close(FALSE); /* Shut down the TLS library */ #endif - close(fileno(smtp_in)); - close(fileno(smtp_out)); + (void)close(fileno(smtp_in)); + (void)close(fileno(smtp_out)); smtp_in = NULL; } else { - close(0); /* stdin */ - if ((debug_selector & D_resolver) == 0) close(1); /* stdout */ - if (debug_selector == 0) /* stderr */ + (void)close(0); /* stdin */ + if ((debug_selector & D_resolver) == 0) (void)close(1); /* stdout */ + if (debug_selector == 0) /* stderr */ { if (!synchronous_delivery) { - close(2); + (void)close(2); log_stderr = NULL; } (void)setsid(); @@ -586,7 +618,8 @@ exit(rc); *************************************************/ /* Called to extract the port from the values given to -oMa and -oMi. -It also checks the syntax of the address. +It also checks the syntax of the address, and terminates it before the +port data when a port is extracted. Argument: address the address, with possible port on the end @@ -598,8 +631,8 @@ Returns: the port, or zero if there isn't one static int check_port(uschar *address) { -int port = host_extract_port(address); -if (string_is_ip_address(address, NULL) == 0) +int port = host_address_extract_port(address); +if (string_is_ip_address(address, NULL) == 0) { fprintf(stderr, "exim abandoned: \"%s\" is not an IP address\n", address); exit(EXIT_FAILURE); @@ -648,146 +681,12 @@ else -/************************************************* -* 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 =, 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. - -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 - 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, 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 "set all bits in both selectors" - in the case where two are being handled. However, the top bit in the - second selector is never set. */ - - if (bit == 0xffffffff) - { - *selector1 = adding? bit : 0; - if (selector2 != NULL) *selector2 = adding? 0x7fffffff : 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 @@ -817,18 +716,27 @@ fprintf(f, "Using tdb\n"); #endif fprintf(f, "Support for:"); +#ifdef SUPPORT_CRYPTEQ + fprintf(f, " crypteq"); +#endif #if HAVE_ICONV fprintf(f, " iconv()"); #endif #if HAVE_IPV6 fprintf(f, " IPv6"); #endif +#ifdef HAVE_SETCLASSRESOURCES + fprintf(f, " use_setclassresources"); +#endif #ifdef SUPPORT_PAM fprintf(f, " PAM"); #endif #ifdef EXIM_PERL fprintf(f, " Perl"); #endif +#ifdef EXPAND_DLFUNC + fprintf(f, " Expand_dlfunc"); +#endif #ifdef USE_TCP_WRAPPERS fprintf(f, " TCPwrappers"); #endif @@ -839,9 +747,18 @@ fprintf(f, "Support for:"); fprintf(f, " OpenSSL"); #endif #endif +#ifdef SUPPORT_TRANSLATE_IP_ADDRESS + fprintf(f, " translate_ip_address"); +#endif +#ifdef SUPPORT_MOVE_FROZEN_MESSAGES + fprintf(f, " move_frozen_messages"); +#endif #ifdef WITH_CONTENT_SCAN fprintf(f, " Content_Scanning"); #endif +#ifndef DISABLE_DKIM + fprintf(f, " DKIM"); +#endif #ifdef WITH_OLD_DEMIME fprintf(f, " Old_Demime"); #endif @@ -854,6 +771,9 @@ fprintf(f, "Support for:"); #ifdef EXPERIMENTAL_BRIGHTMAIL fprintf(f, " Experimental_Brightmail"); #endif +#ifdef EXPERIMENTAL_DCC + fprintf(f, " Experimental_DCC"); +#endif fprintf(f, "\n"); fprintf(f, "Lookups:"); @@ -896,6 +816,9 @@ fprintf(f, "Lookups:"); #ifdef LOOKUP_PGSQL fprintf(f, " pgsql"); #endif +#ifdef LOOKUP_SQLITE + fprintf(f, " sqlite"); +#endif #ifdef LOOKUP_TESTDB fprintf(f, " testdb"); #endif @@ -911,6 +834,9 @@ fprintf(f, "Authenticators:"); #ifdef AUTH_CYRUS_SASL fprintf(f, " cyrus_sasl"); #endif +#ifdef AUTH_DOVECOT + fprintf(f, " dovecot"); +#endif #ifdef AUTH_PLAINTEXT fprintf(f, " plaintext"); #endif @@ -978,6 +904,16 @@ if (fixed_never_users[0] > 0) fprintf(f, "%d:", (unsigned int)fixed_never_users[i]); fprintf(f, "%d\n", (unsigned int)fixed_never_users[i]); } + +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. */ +#ifdef SUPPORT_TLS +tls_version_report(f); +#endif } @@ -1103,7 +1039,7 @@ 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++) { @@ -1158,6 +1094,43 @@ return yield; +/************************************************* +* 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); +} + + + /************************************************* * Entry point and high-level code * *************************************************/ @@ -1216,8 +1189,11 @@ 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; @@ -1225,10 +1201,12 @@ uschar *alias_arg = NULL; uschar *called_as = US""; 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 *malware_test_file = NULL; uschar *real_sender_address; uschar *originator_home = US"/"; void *reset_point; @@ -1256,6 +1234,12 @@ This is a feature to make the lives of binary distributors easier. */ #ifdef EXIM_USERNAME if (route_finduser(US EXIM_USERNAME, &pw, &exim_uid)) { + if (exim_uid == 0) + { + fprintf(stderr, "exim: refusing to run with uid 0 for \"%s\"\n", + EXIM_USERNAME); + exit(EXIT_FAILURE); + } exim_gid = pw->pw_gid; } else @@ -1401,10 +1385,17 @@ message_id_external[0] = 'E'; message_id = message_id_external + 1; message_id[0] = 0; -/* Set the umask to zero so that any files that Exim creates are created -with the modes that it specifies. */ +/* Set the umask to zero so that any files Exim creates using open() are +created with the modes that it specifies. NOTE: Files created with fopen() have +a problem, which was not recognized till rather late (February 2006). With this +umask, such files will be world writeable. (They are all content scanning files +in the spool directory, which isn't world-accessible, so this is not a +disaster, but it's untidy.) I don't want to change this overall setting, +however, because it will interact badly with the open() calls. Instead, there's +now a function called modefopen() that fiddles with the umask while calling +fopen(). */ -umask(0); +(void)umask(0); /* Precompile the regular expression for matching a message id. Keep this in step with the code that generates ids in the accept.c module. We need to do @@ -1414,6 +1405,14 @@ using mac_ismsgid, which uses this. */ regex_ismsgid = regex_must_compile(US"^(?:[^\\W_]{6}-){2}[^\\W_]{2}$", FALSE, TRUE); +/* Precompile the regular expression that is used for matching an SMTP error +code, possibly extended, at the start of an error message. Note that the +terminating whitespace character is included. */ + +regex_smtp_code = + regex_must_compile(US"^\\d\\d\\d\\s(?:\\d\\.\\d\\d?\\d?\\.\\d\\d?\\d?\\s)?", + FALSE, TRUE); + /* 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. */ @@ -1496,11 +1495,6 @@ running in an unprivileged state. */ 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. */ @@ -1565,6 +1559,21 @@ 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) @@ -1591,10 +1600,21 @@ for (i = 1; i < argc; i++) 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 */ @@ -1664,6 +1684,14 @@ for (i = 1; i < argc; i++) 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. */ @@ -1820,6 +1848,7 @@ for (i = 1; i < argc; i++) config_main_filelist = argrest; config_changed = TRUE; + trusted_config = FALSE; } break; @@ -1890,7 +1919,7 @@ for (i = 1; i < argc; i++) break; /* -d: Set debug level (see also -v below) or set the drop_cr option. - The latter is now a no-op, retained for compatibility only. If -dd is used, + The latter is now a no-op, retained for compatibility only. If -dd is used, debugging subprocesses of the daemon is disabled. */ case 'd': @@ -1913,8 +1942,8 @@ for (i = 1; i < argc; i++) argrest++; } if (*argrest != 0) - decode_bits(&selector, NULL, argrest, debug_options, - debug_options_count, US"debug"); + decode_bits(&selector, NULL, D_memory, 0, argrest, debug_options, + debug_options_count, US"debug", 0); debug_selector = selector; } break; @@ -1965,6 +1994,7 @@ for (i = 1; i < argc; i++) { badarg = TRUE; break; } } originator_name = argrest; + sender_name_forced = TRUE; break; @@ -2062,6 +2092,9 @@ for (i = 1; i < argc; i++) 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"); @@ -2091,6 +2124,19 @@ for (i = 1; i < argc; i++) 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; } @@ -2162,7 +2208,9 @@ for (i = 1; i < argc; i++) -Mmad mark all recipients delivered -Mmd mark recipients(s) delivered -Mes edit sender + -Mset load a message for use with -be -Mvb show body + -Mvc show copy (of whole message, in RFC 2822 format) -Mvh show header -Mvl show log */ @@ -2199,12 +2247,22 @@ for (i = 1; i < argc; i++) one_msg_action = TRUE; } else if (Ustrcmp(argrest, "rm") == 0) msg_action = MSG_REMOVE; + else if (Ustrcmp(argrest, "set") == 0) + { + msg_action = MSG_LOAD; + one_msg_action = TRUE; + } else if (Ustrcmp(argrest, "t") == 0) msg_action = MSG_THAW; else if (Ustrcmp(argrest, "vb") == 0) { msg_action = MSG_SHOW_BODY; one_msg_action = TRUE; } + else if (Ustrcmp(argrest, "vc") == 0) + { + msg_action = MSG_SHOW_COPY; + one_msg_action = TRUE; + } else if (Ustrcmp(argrest, "vh") == 0) { msg_action = MSG_SHOW_HEADER; @@ -2434,7 +2492,11 @@ for (i = 1; i < argc; i++) /* -oMt: Set sender ident */ - else if (Ustrcmp(argrest, "Mt") == 0) sender_ident = argv[++i]; + else if (Ustrcmp(argrest, "Mt") == 0) + { + sender_ident_set = TRUE; + sender_ident = argv[++i]; + } /* Else a bad argument */ @@ -2534,6 +2596,11 @@ for (i = 1; i < argc; 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 */ @@ -2642,7 +2709,6 @@ for (i = 1; i < argc; i++) } } else deliver_selectstring = argrest; - if (queue_interval < 0) queue_interval = 0; break; @@ -2690,7 +2756,6 @@ for (i = 1; i < argc; i++) } } 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. @@ -2781,9 +2846,17 @@ for (i = 1; i < argc; i++) } -/* 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 || @@ -2792,13 +2865,14 @@ if (( ) || ( msg_action_arg > 0 && - (daemon_listen || queue_interval >= 0 || list_options || checking || - bi_option || test_retry_arg >= 0 || test_rewrite_arg >= 0) + (daemon_listen || queue_interval >= 0 || list_options || + (checking && msg_action != MSG_LOAD) || + bi_option || test_retry_arg >= 0 || test_rewrite_arg >= 0) ) || ( (daemon_listen || queue_interval >= 0) && (sender_address != NULL || list_options || list_queue || checking || - bi_option) + bi_option) ) || ( daemon_listen && queue_interval == 0 @@ -2823,6 +2897,10 @@ if (( ) || ( deliver_selectstring != NULL && queue_interval < 0 + ) || + ( + msg_action == MSG_LOAD && + (!expansion_test || expansion_test_message != NULL) ) ) { @@ -2969,7 +3047,7 @@ 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 || macros != NULL) && /* Config changed, and */ real_uid != root_uid && /* Not root, and */ #ifndef ALT_CONFIG_ROOT_ONLY /* (when not locked out) */ real_uid != exim_uid && /* Not exim, and */ @@ -3034,8 +3112,8 @@ readconf_main(); /* Handle the decoding of logging options. */ -decode_bits(&log_write_selector, &log_extra_selector, log_selector_string, - log_options, log_options_count, US"log"); +decode_bits(&log_write_selector, &log_extra_selector, 0, 0, + log_selector_string, log_options, log_options_count, US"log", 0); DEBUG(D_any) { @@ -3188,7 +3266,7 @@ 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. */ -if (removed_privilege && (config_changed || macros != NULL) && +if (removed_privilege && (!trusted_config || macros != NULL) && real_uid == exim_uid) { #ifdef ALT_CONFIG_ROOT_ONLY @@ -3200,7 +3278,7 @@ if (removed_privilege && (config_changed || macros != NULL) && 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"); + (int)exim_uid, trusted_config? "-D" : "-C"); #endif } @@ -3231,8 +3309,8 @@ a debugging feature for finding out what arguments certain MUAs actually use. Don't attempt it if logging is disabled, or if listing variables or if verifying/testing addresses or expansions. */ -if ((log_extra_selector & LX_arguments) != 0 && really_exim - && !list_options && !checking) +if (((debug_selector & D_any) != 0 || (log_extra_selector & LX_arguments) != 0) + && really_exim && !list_options && !checking) { int i; uschar *p = big_buffer; @@ -3264,18 +3342,23 @@ if ((log_extra_selector & LX_arguments) != 0 && really_exim (p - big_buffer) - 4), printing, quote); while (*p) p++; } - log_write(0, LOG_MAIN, "%s", big_buffer); + + if ((log_extra_selector & LX_arguments) != 0) + log_write(0, LOG_MAIN, "%s", big_buffer); + else + debug_printf("%s\n", big_buffer); } /* Set the working directory to be the top-level spool directory. We don't rely on this in the code, which always uses fully qualified names, but it's useful for core dumps etc. Don't complain if it fails - the spool directory might not be generally accessible and calls with the -C option (and others) have lost -privilege by now. */ +privilege by now. Before the chdir, we try to ensure that the directory exists. +*/ if (Uchdir(spool_directory) != 0) { - (void)directory_make(spool_directory, US"", SPOOL_DIRECTORY_MODE, TRUE); + (void)directory_make(spool_directory, US"", SPOOL_DIRECTORY_MODE, FALSE); (void)Uchdir(spool_directory); } @@ -3287,7 +3370,7 @@ script. */ if (bi_option) { - fclose(config_file); + (void)fclose(config_file); if (bi_command != NULL) { int i = 0; @@ -3326,7 +3409,6 @@ if (real_uid == root_uid || real_uid == exim_uid || real_gid == exim_gid) else { int i, j; - for (i = 0; i < group_count; i++) { if (group_list[i] == exim_gid) admin_user = TRUE; @@ -3382,12 +3464,13 @@ configuration, but the queue run restriction can be relaxed. Only an admin user may request that a message be returned to its sender forthwith. Only an admin user may specify a debug level greater than D_v (because it might show passwords, etc. in lookup queries). Only an admin user may request a queue -count. */ +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) || @@ -3447,7 +3530,7 @@ barf. */ if (smtp_input) { union sockaddr_46 inetd_sock; - SOCKLEN_T size = sizeof(inetd_sock); + EXIM_SOCKLEN_T size = sizeof(inetd_sock); if (getpeername(0, (struct sockaddr *)(&inetd_sock), &size) == 0) { int family = ((struct sockaddr *)(&inetd_sock))->sa_family; @@ -3490,7 +3573,7 @@ if (receiving_message && (is_inetd && smtp_load_reserve >= 0) )) { - load_average = os_getloadavg(); + load_average = OS_GETLOADAVG(); } #endif @@ -3513,7 +3596,9 @@ root privilege above as a result of -C, -D, -be, -bf or -bF, remove it now except when starting the daemon or doing some kind of delivery or address testing (-bt). These are the only cases when root need to be retained. We run as exim for -bv and -bh. However, if deliver_drop_privilege is set, root is -retained only for starting the daemon. */ +retained only for starting the daemon. We always do the initgroups() in this +situation (controlled by the TRUE below), in order to be as close as possible +to the state Exim usually runs in. */ if (!unprivileged && /* originally had root AND */ !removed_privilege && /* still got root AND */ @@ -3529,13 +3614,40 @@ if (!unprivileged && /* originally had root AND */ ) )) { - exim_setugid(exim_uid, exim_gid, FALSE, US"privilege not needed"); + exim_setugid(exim_uid, exim_gid, TRUE, US"privilege not needed"); } /* When we are retaining a privileged uid, we still change to the exim gid. */ else setgid(exim_gid); +/* 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 */ if (list_queue) @@ -3554,12 +3666,12 @@ if (count_queue) exit(EXIT_SUCCESS); } -/* Handle actions on specific messages, except for the force delivery action, -which is done below. Some actions take a whole list of message ids, which -are known to continue up to the end of the arguments. Others take a single -message id and then operate on the recipients list. */ +/* Handle actions on specific messages, except for the force delivery and +message load actions, which are done below. Some actions take a whole list of +message ids, which are known to continue up to the end of the arguments. Others +take a single message id and then operate on the recipients list. */ -if (msg_action_arg > 0 && msg_action != MSG_DELIVER) +if (msg_action_arg > 0 && msg_action != MSG_DELIVER && msg_action != MSG_LOAD) { int yield = EXIT_SUCCESS; set_process_info("acting on specified messages"); @@ -3641,11 +3753,13 @@ if (test_retry_arg >= 0) return EXIT_FAILURE; } - /* For the rcpt_4xx errors, a value of 255 means "any", and a code > 100 as - an error is for matching codes to the decade. Turn them into a real error - code, off the decade. */ + /* For the {MAIL,RCPT,DATA}_4xx errors, a value of 255 means "any", and a + code > 100 as an error is for matching codes to the decade. Turn them into + a real error code, off the decade. */ - if (basic_errno == ERRNO_RCPT4XX) + if (basic_errno == ERRNO_MAIL4XX || + basic_errno == ERRNO_RCPT4XX || + basic_errno == ERRNO_DATA4XX) { int code = (more_errno >> 8) & 255; if (code == 255) @@ -3725,7 +3839,8 @@ if (list_options) 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)) { readconf_print(argv[i+1], argv[i]); i++; @@ -3737,16 +3852,19 @@ if (list_options) /* Handle a request to deliver one or more messages that are already on the -queue. Values of msg_action other than MSG_DELIVER are dealt with above. This -is typically used for a small number when prodding by hand (when the option -forced_delivery will be set) or when re-execing to regain root privilege. -Each message delivery must happen in a separate process, so we fork a process -for each one, and run them sequentially so that debugging output doesn't get -intertwined, and to avoid spawning too many processes if a long list is given. -However, don't fork for the last one; this saves a process in the common case -when Exim is called to deliver just one message. */ - -if (msg_action_arg > 0) +queue. Values of msg_action other than MSG_DELIVER and MSG_LOAD are dealt with +above. MSG_LOAD is handled with -be (which is the only time it applies) below. + +Delivery of specific messages is typically used for a small number when +prodding by hand (when the option forced_delivery will be set) or when +re-execing to regain root privilege. Each message delivery must happen in a +separate process, so we fork a process for each one, and run them sequentially +so that debugging output doesn't get intertwined, and to avoid spawning too +many processes if a long list is given. However, don't fork for the last one; +this saves a process in the common case when Exim is called to deliver just one +message. */ + +if (msg_action_arg > 0 && msg_action != MSG_LOAD) { if (prod_requires_admin && !admin_user) { @@ -3879,7 +3997,7 @@ for (i = 0;;) /* If we cannot get a user login, log the incident and give up, unless the configuration specifies something to use. When running in the test harness, -any setting of unknown_login overrides the actual login name. */ +any setting of unknown_login overrides the actual name. */ if (originator_login == NULL || running_in_test_harness) { @@ -3913,12 +4031,17 @@ DEBUG(D_receive) debug_printf("originator: uid=%d gid=%d login=%s name=%s\n", /* Run in daemon and/or queue-running mode. The function daemon_go() never returns. We leave this till here so that the originator_ fields are available -for incoming messages via the daemon. */ +for incoming messages via the daemon. The daemon cannot be run in mua_wrapper +mode. */ if (daemon_listen || queue_interval > 0) { - if (mua_wrapper) log_write(0, LOG_MAIN|LOG_PANIC_DIE, "Daemon cannot be " - "run when mua_wrapper is set"); + if (mua_wrapper) + { + fprintf(stderr, "Daemon cannot be run when mua_wrapper is set\n"); + log_write(0, LOG_MAIN|LOG_PANIC_DIE, "Daemon cannot be run when " + "mua_wrapper is set"); + } daemon_go(); } @@ -3955,12 +4078,14 @@ if ((sender_address == NULL && !smtp_input) || sender_local = TRUE; /* A trusted caller can supply authenticated_sender and authenticated_id - via -oMas and -oMai and if so, they will already be set. */ + via -oMas and -oMai and if so, they will already be set. Otherwise, force + defaults except when host checking. */ - if (authenticated_sender == NULL) + if (authenticated_sender == NULL && !host_checking) authenticated_sender = string_sprintf("%s@%s", originator_login, qualify_domain_sender); - if (authenticated_id == NULL) authenticated_id = originator_login; + if (authenticated_id == NULL && !host_checking) + authenticated_id = originator_login; } /* Trusted callers are always permitted to specify the sender address. @@ -4059,18 +4184,65 @@ if (verify_address_mode || address_test_mode) exim_exit(exit_value); } -/* Handle expansion checking */ +/* Handle expansion checking. Either expand items on the command line, or read +from stdin if there aren't any. If -Mset was specified, load the message so +that its variables can be used, but restrict this facility to admin users. +Otherwise, if -bem was used, read a message from stdin. */ if (expansion_test) { + if (msg_action_arg > 0 && msg_action == MSG_LOAD) + { + uschar spoolname[256]; /* Not big_buffer; used in spool_read_header() */ + if (!admin_user) + { + fprintf(stderr, "exim: permission denied\n"); + exit(EXIT_FAILURE); + } + message_id = argv[msg_action_arg]; + (void)string_format(spoolname, sizeof(spoolname), "%s-H", message_id); + if (!spool_open_datafile(message_id)) + printf ("Failed to load message datafile %s\n", message_id); + if (spool_read_header(spoolname, TRUE, FALSE) != spool_read_OK) + printf ("Failed to load message %s\n", message_id); + } + + /* Read a test message from a file. We fudge it up to be on stdin, saving + stdin itself for later reading of expansion strings. */ + + else if (expansion_test_message != 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) { while (recipients_arg < argc) { uschar *s = argv[recipients_arg++]; uschar *ss = expand_string(s); - if (ss == NULL) - printf ("Failed: %s\n", expand_string_message); + if (ss == NULL) printf ("Failed: %s\n", expand_string_message); else printf("%s\n", CS ss); } } @@ -4102,6 +4274,14 @@ if (expansion_test) #endif } + /* The data file will be open after -Mset */ + + if (deliver_datafile >= 0) + { + (void)close(deliver_datafile); + deliver_datafile = -1; + } + exim_exit(EXIT_SUCCESS); } @@ -4125,24 +4305,28 @@ if (raw_active_hostname != NULL) } /* Handle host checking: this facility mocks up an incoming SMTP call from a -given IP address so that the blocking and relay configuration can be tested. An -RFC 1413 call is made only if we are running in the test harness and an -incoming interface and both ports are specified, because there is no TCP/IP -call to find the ident for. */ +given IP address so that the blocking and relay configuration can be tested. +Unless a sender_ident was set by -oMt, we discard it (the default is the +caller's login name). An RFC 1413 call is made only if we are running in the +test harness and an incoming interface and both ports are specified, because +there is no TCP/IP call to find the ident for. */ if (host_checking) { - int x[4]; + int x[4]; int size; - - sender_ident = NULL; - if (running_in_test_harness && sender_host_port != 0 && - interface_address != NULL && interface_port != 0) - verify_get_ident(1413); - + + if (!sender_ident_set) + { + sender_ident = NULL; + if (running_in_test_harness && sender_host_port != 0 && + interface_address != NULL && interface_port != 0) + verify_get_ident(1413); + } + /* In case the given address is a non-canonical IPv6 address, canonicize it. The code works for both IPv4 and IPv6, as it happens. */ - + size = host_aton(sender_host_address, x); sender_host_address = store_get(48); /* large enough for full IPv6 */ (void)host_nmtoa(size, x, -1, sender_host_address, ':'); @@ -4166,6 +4350,11 @@ if (host_checking) log_write_selector &= ~L_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: ") and writing another one is + unnecessary clutter. */ + if (smtp_start_session()) { reset_point = store_get(0); @@ -4175,6 +4364,7 @@ if (host_checking) if (smtp_setup_msg() <= 0) break; if (!receive_msg(FALSE)) break; } + smtp_log_no_mail(); } exim_exit(EXIT_SUCCESS); } @@ -4191,14 +4381,9 @@ 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) - { - 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; - } + exim_usage(called_as); } @@ -4244,7 +4429,7 @@ sender_ident. */ else if (is_inetd) { - fclose(stderr); + (void)fclose(stderr); exim_nullstd(); /* Re-open to /dev/null */ verify_get_ident(IDENT_PORT); host_build_sender_fullhost(); @@ -4274,18 +4459,19 @@ else if (!is_inetd) sender_host_unknown = TRUE; if exim is started from inetd. In this case fd 0 will be set to the socket, but fd 1 will not be set. This also happens for passed SMTP channels. */ -if (fstat(1, &statbuf) < 0) dup2(0, 1); +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 { @@ -4295,11 +4481,11 @@ 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 @@ -4312,8 +4498,13 @@ if ((!smtp_input || smtp_batched_input) && !receive_check_fs(0)) 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: ") and writing another one is +unnecessary clutter. */ if (smtp_input) { @@ -4329,12 +4520,12 @@ if (smtp_input) } } -/* Otherwise, set up the input size limit here */ +/* Otherwise, set up the input size limit here. */ else { - thismessage_size_limit = expand_string_integer(message_size_limit); - if (thismessage_size_limit < 0) + thismessage_size_limit = expand_string_integer(message_size_limit, TRUE); + if (expand_string_message != NULL) { if (thismessage_size_limit == -1) log_write(0, LOG_MAIN|LOG_PANIC_DIE, "failed to expand " @@ -4401,20 +4592,13 @@ while (more) 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) { @@ -4427,14 +4611,36 @@ while (more) 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 @@ -4545,6 +4751,19 @@ while (more) } } + /* Run the acl_not_smtp_start ACL if required. The result of the ACL is + ignored; rejecting here would just add complication, and it can just as + well be done later. Allow $recipients to be visible in the ACL. */ + + if (acl_not_smtp_start != 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; + } + /* Read the data for the message. If filter_test is not FTEST_NONE, this will just read the headers for the message, and not write anything onto the spool. */ @@ -4600,52 +4819,61 @@ while (more) if (ftest_prefix != NULL) printf("Prefix = %s\n", ftest_prefix); if (ftest_suffix != NULL) printf("Suffix = %s\n", ftest_suffix); - chdir("/"); /* Get away from wherever the user is running this from */ - - /* Now we run either a system filter test, or a user filter test, or both. - In the latter case, headers added by the system filter will persist and be - available to the user filter. We need to copy the filter variables + (void)chdir("/"); /* Get away from wherever the user is running this from */ + + /* Now we run either a system filter test, or a user filter test, or both. + In the latter case, headers added by the system filter will persist and be + available to the user filter. We need to copy the filter variables explicitly. */ - + if ((filter_test & FTEST_SYSTEM) != 0) { if (!filter_runtest(filter_sfd, filter_test_sfile, TRUE, more)) exim_exit(EXIT_FAILURE); - } - + } + memcpy(filter_sn, filter_n, sizeof(filter_sn)); - + if ((filter_test & FTEST_USER) != 0) { if (!filter_runtest(filter_ufd, filter_test_ufile, FALSE, more)) exim_exit(EXIT_FAILURE); - } - + } + exim_exit(EXIT_SUCCESS); } /* Else act on the result of message reception. We should not get here unless - message_id[0] is non-zero. If queue_only is set, 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; } }