-/* $Cambridge: exim/src/src/exim.c,v 1.5 2004/10/19 11:04:26 ph10 Exp $ */
+/* $Cambridge: exim/src/src/exim.c,v 1.62 2009/06/10 07:34:04 tom Exp $ */
/*************************************************
* Exim - an Internet mail transport agent *
*************************************************/
-/* Copyright (c) University of Cambridge 1995 - 2004 */
+/* Copyright (c) University of Cambridge 1995 - 2007 */
/* See the file NOTICE for conditions of use and distribution. */
/* This function is called by millisleep() and exim_wait_tick() to wait for a
period of time that may include a fraction of a second. The coding is somewhat
-tedious. We do not expect setitimer() ever to fail, but if it does, the process
-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.
+tedious. We do not expect setitimer() ever to fail, but if it does, the process
+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. :-)
Argument: an itimerval structure containing the interval
(void)sigaddset(&sigmask, SIGALRM); /* Add SIGALRM */
(void)sigprocmask(SIG_BLOCK, &sigmask, &old_sigmask); /* Block SIGALRM */
if (setitimer(ITIMER_REAL, itval, NULL) < 0) /* Start timer */
- log_write(0, LOG_MAIN|LOG_PANIC_DIE,
- "setitimer() failed: %s", strerror(errno));
+ log_write(0, LOG_MAIN|LOG_PANIC_DIE,
+ "setitimer() failed: %s", strerror(errno));
(void)sigfillset(&sigmask); /* All signals */
(void)sigdelset(&sigmask, SIGALRM); /* Remove SIGALRM */
(void)sigsuspend(&sigmask); /* Until SIGALRM */
+/*************************************************
+* 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 *
*************************************************/
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);
}
#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();
*************************************************/
/* 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
static int
check_port(uschar *address)
{
-int port = host_extract_port(address);
-if (!string_is_ip_address(address, NULL))
+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);
flags flag bits for verify_address()
exit_value to be set for failures
-Returns: nothint
+Returns: nothing
*/
static void
else
{
int rc = verify_address(deliver_make_addr(address,TRUE), stdout, flags, -1,
- -1, NULL, NULL, NULL);
+ -1, -1, NULL, NULL, NULL);
if (rc == FAIL) *exit_value = 2;
else if (rc == DEFER && *exit_value == 0) *exit_value = 1;
}
for one). The top bit in the table means "put in 2nd selector". This does not
yet apply to debug options, so the "=" facility sets only the first selector.
+The "all" selector, which must be equal to 0xffffffff, is recognized specially.
+It sets all the bits in both selectors. However, there is a facility for then
+unsetting certain bits, because we want to turn off "memory" in the debug case.
+
A bad value for a debug setting is treated as an unknown option - error message
to stderr and die. For log settings, which come from the configuration file,
we write to the log on the way out...
Arguments:
selector1 address of the first bit string
selector2 address of the second bit string, or NULL
+ notall1 bits to exclude from "all" for selector1
+ notall2 bits to exclude from "all" for selector2
string the configured string
options the table of option names
count size of table
*/
static void
-decode_bits(unsigned int *selector1, unsigned int *selector2, uschar *string,
- bit_table *options, int count, uschar *which)
+decode_bits(unsigned int *selector1, unsigned int *selector2, int notall1,
+ int notall2, uschar *string, bit_table *options, int count, uschar *which)
{
uschar *errmsg;
if (string == NULL) return;
unsigned int bit = middle->bit;
unsigned int *selector;
- /* The value with all bits set means "set all bits in both selectors"
+ /* The value with all bits set means "force all bits in both selectors"
in the case where two are being handled. However, the top bit in the
- second selector is never set. */
+ second selector is never set. When setting, some bits can be excluded.
+ */
if (bit == 0xffffffff)
{
- *selector1 = adding? bit : 0;
- if (selector2 != NULL) *selector2 = adding? 0x7fffffff : 0;
+ if (adding)
+ {
+ *selector1 = 0xffffffff ^ notall1;
+ if (selector2 != NULL) *selector2 = 0x7fffffff ^ notall2;
+ }
+ else
+ {
+ *selector1 = 0;
+ if (selector2 != NULL) *selector2 = 0;
+ }
}
/* Otherwise, the 0x80000000 bit means "this value, without the top
#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
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
+#ifdef EXPERIMENTAL_SPF
+ fprintf(f, " Experimental_SPF");
+#endif
+#ifdef EXPERIMENTAL_SRS
+ fprintf(f, " Experimental_SRS");
+#endif
+#ifdef EXPERIMENTAL_BRIGHTMAIL
+ fprintf(f, " Experimental_Brightmail");
+#endif
+#ifdef EXPERIMENTAL_DCC
+ fprintf(f, " Experimental_DCC");
+#endif
fprintf(f, "\n");
fprintf(f, "Lookups:");
#ifdef LOOKUP_PGSQL
fprintf(f, " pgsql");
#endif
+#ifdef LOOKUP_SQLITE
+ fprintf(f, " sqlite");
+#endif
#ifdef LOOKUP_TESTDB
fprintf(f, " testdb");
#endif
#ifdef AUTH_CYRUS_SASL
fprintf(f, " cyrus_sasl");
#endif
+#ifdef AUTH_DOVECOT
+ fprintf(f, " dovecot");
+#endif
#ifdef AUTH_PLAINTEXT
fprintf(f, " plaintext");
#endif
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: %d\n", sizeof(off_t));
}
int ptr = 0;
uschar *yield = NULL;
-if (fn_readline == NULL) printf("> ");
+if (fn_readline == NULL) { printf("> "); fflush(stdout); }
for (i = 0;; i++)
{
+/*************************************************
+* Output usage information for the program *
+*************************************************/
+
+/* This function is called when there are no recipients
+ or a specific --help argument was added.
+
+Arguments:
+ progname information on what name we were called by
+
+Returns: DOES NOT RETURN
+*/
+
+static void
+exim_usage(uschar *progname)
+{
+
+/* Handle specific program invocation varients */
+if (Ustrcmp(progname, US"-mailq") == 0)
+ {
+ fprintf(stderr,
+ "mailq - list the contents of the mail queue\n\n"
+ "For a list of options, see the Exim documentation.\n");
+ exit(EXIT_FAILURE);
+ }
+
+/* Generic usage - we output this whatever happens */
+fprintf(stderr,
+ "Exim is a Mail Transfer Agent. It is normally called by Mail User Agents,\n"
+ "not directly from a shell command line. Options and/or arguments control\n"
+ "what it does when called. For a list of options, see the Exim documentation.\n");
+
+exit(EXIT_FAILURE);
+}
+
+
+
/*************************************************
* Entry point and high-level code *
*************************************************/
int arg_receive_timeout = -1;
int arg_smtp_receive_timeout = -1;
int arg_error_handling = error_handling;
-int filter_fd = -1;
+int filter_sfd = -1;
+int filter_ufd = -1;
int group_count;
int i;
int list_queue_option = 0;
BOOL one_msg_action = FALSE;
BOOL queue_only_set = FALSE;
BOOL receiving_message = TRUE;
+BOOL sender_ident_set = FALSE;
+BOOL session_local_queue_only;
BOOL unprivileged;
BOOL removed_privilege = FALSE;
+BOOL usage_wanted = FALSE;
BOOL verify_address_mode = FALSE;
BOOL verify_as_sender = FALSE;
BOOL version_printed = FALSE;
uschar *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 *real_sender_address;
uschar *originator_home = US"/";
-BOOL ftest_system = FALSE;
void *reset_point;
struct passwd *pw;
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
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. */
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 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;
+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
else if (*argrest != 0) { badarg = TRUE; break; }
}
- /* -be: Run in expansion test mode */
+ /* -be: Run in expansion test mode
+ -bem: Ditto, but read a message from a file first
+ */
else if (*argrest == 'e')
+ {
expansion_test = checking = TRUE;
+ if (argrest[1] == 'm')
+ {
+ if (++i >= argc) { badarg = TRUE; break; }
+ expansion_test_message = argv[i];
+ argrest++;
+ }
+ if (argrest[1] != 0) { badarg = TRUE; break; }
+ }
+
+ /* -bF: Run system filter test */
+
+ else if (*argrest == 'F')
+ {
+ filter_test |= FTEST_SYSTEM;
+ if (*(++argrest) != 0) { badarg = TRUE; break; }
+ if (++i < argc) filter_test_sfile = argv[i]; else
+ {
+ fprintf(stderr, "exim: file name expected after %s\n", argv[i-1]);
+ exit(EXIT_FAILURE);
+ }
+ }
- /* -bf: Run in mail filter testing mode
- -bF: Ditto, but for system filters
+ /* -bf: Run user filter test
-bfd: Set domain for filter testing
-bfl: Set local part for filter testing
-bfp: Set prefix for filter testing
-bfs: Set suffix for filter testing
*/
- else if (*argrest == 'f' || *argrest == 'F')
+ else if (*argrest == 'f')
{
- ftest_system = *argrest++ == 'F';
- if (*argrest == 0)
+ if (*(++argrest) == 0)
{
- if(++i < argc) filter_test = argv[i]; else
+ filter_test |= FTEST_USER;
+ if (++i < argc) filter_test_ufile = argv[i]; else
{
fprintf(stderr, "exim: file name expected after %s\n", argv[i-1]);
exit(EXIT_FAILURE);
break;
/* -d: Set debug level (see also -v below) or set the drop_cr option.
- The latter is now a no-opt, retained for compatibility only. */
+ The latter is now a no-op, retained for compatibility only. If -dd is used,
+ debugging subprocesses of the daemon is disabled. */
case 'd':
if (Ustrcmp(argrest, "ropcr") == 0)
unsigned int selector = D_default;
debug_selector = 0;
debug_file = NULL;
+ if (*argrest == 'd')
+ {
+ debug_daemon = TRUE;
+ argrest++;
+ }
if (*argrest != 0)
- decode_bits(&selector, NULL, argrest, debug_options,
+ decode_bits(&selector, NULL, D_memory, 0, argrest, debug_options,
debug_options_count, US"debug");
debug_selector = selector;
}
{ badarg = TRUE; break; }
}
originator_name = argrest;
+ sender_name_forced = TRUE;
break;
if (Ustrcmp(argrest, "C") == 0)
{
+ union sockaddr_46 interface_sock;
+ EXIM_SOCKLEN_T size = sizeof(interface_sock);
+
if (argc != i + 6)
{
fprintf(stderr, "exim: too many or too few arguments after -MC\n");
return EXIT_FAILURE;
}
+ /* Set up $sending_ip_address and $sending_port */
+
+ if (getsockname(fileno(stdin), (struct sockaddr *)(&interface_sock),
+ &size) == 0)
+ sending_ip_address = host_ntoa(-1, &interface_sock, NULL,
+ &sending_port);
+ else
+ {
+ fprintf(stderr, "exim: getsockname() failed after -MC option: %s\n",
+ strerror(errno));
+ return EXIT_FAILURE;
+ }
+
if (running_in_test_harness) millisleep(500);
break;
}
-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
*/
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;
/* -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 */
case 'q':
receiving_message = FALSE;
+ if (queue_interval >= 0)
+ {
+ fprintf(stderr, "exim: -q specified more than once\n");
+ exit(EXIT_FAILURE);
+ }
/* -qq...: Do queue runs in a 2-stage manner */
}
}
else deliver_selectstring = argrest;
- if (queue_interval < 0) queue_interval = 0;
break;
}
}
else deliver_selectstring_sender = argrest;
- if (queue_interval < 0) queue_interval = 0;
break;
/* -Tqt is an option that is exclusively for use by the testing suite.
}
-/* 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 ||
test_retry_arg >= 0 || test_rewrite_arg >= 0 ||
- filter_test != NULL || (msg_action_arg > 0 && !one_msg_action))
+ filter_test != FTEST_NONE || (msg_action_arg > 0 && !one_msg_action))
) ||
(
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
(
list_options &&
(checking || smtp_input || extract_recipients ||
- filter_test != NULL || bi_option)
+ filter_test != FTEST_NONE || bi_option)
) ||
(
verify_address_mode &&
(address_test_mode || smtp_input || extract_recipients ||
- filter_test != NULL || bi_option)
+ filter_test != FTEST_NONE || bi_option)
) ||
(
address_test_mode && (smtp_input || extract_recipients ||
- filter_test != NULL || bi_option)
+ filter_test != FTEST_NONE || bi_option)
) ||
(
- smtp_input && (sender_address != NULL || filter_test != NULL ||
+ smtp_input && (sender_address != NULL || filter_test != FTEST_NONE ||
extract_recipients)
) ||
(
deliver_selectstring != NULL && queue_interval < 0
+ ) ||
+ (
+ msg_action == MSG_LOAD &&
+ (!expansion_test || expansion_test_message != NULL)
)
)
{
strerror(errno));
rlp.rlim_cur = rlp.rlim_max = 0;
}
-
- /* I originally chose 1000 as a nice big number that was unlikely to
+
+ /* I originally chose 1000 as a nice big number that was unlikely to
be exceeded. It turns out that some older OS have a fixed upper limit of
256. */
-
+
if (rlp.rlim_cur < 1000)
{
rlp.rlim_cur = rlp.rlim_max = 1000;
if (setrlimit(RLIMIT_NOFILE, &rlp) < 0)
- {
+ {
rlp.rlim_cur = rlp.rlim_max = 256;
if (setrlimit(RLIMIT_NOFILE, &rlp) < 0)
log_write(0, LOG_MAIN|LOG_PANIC, "setrlimit(RLIMIT_NOFILE) failed: %s",
strerror(errno));
- }
+ }
}
#endif
) || /* OR */
expansion_test /* expansion testing */
|| /* OR */
- filter_test != NULL) /* Filter testing */
+ filter_test != FTEST_NONE) /* Filter testing */
{
setgroups(group_count, group_list);
exim_setugid(real_uid, real_gid, FALSE,
else exim_setugid(geteuid(), getegid(), FALSE, US"forcing real = effective");
-/* If testing a filter, open the file now, before wasting time doing other
+/* If testing a filter, open the file(s) now, before wasting time doing other
setups and reading the message. */
-if (filter_test != NULL)
+if ((filter_test & FTEST_SYSTEM) != 0)
{
- filter_fd = Uopen(filter_test, O_RDONLY,0);
- if (filter_fd < 0)
+ filter_sfd = Uopen(filter_test_sfile, O_RDONLY, 0);
+ if (filter_sfd < 0)
{
- fprintf(stderr, "exim: failed to open %s: %s\n", filter_test,
+ fprintf(stderr, "exim: failed to open %s: %s\n", filter_test_sfile,
+ strerror(errno));
+ return EXIT_FAILURE;
+ }
+ }
+
+if ((filter_test & FTEST_USER) != 0)
+ {
+ filter_ufd = Uopen(filter_test_ufile, O_RDONLY, 0);
+ if (filter_ufd < 0)
+ {
+ fprintf(stderr, "exim: failed to open %s: %s\n", filter_test_ufile,
strerror(errno));
return EXIT_FAILURE;
}
/* Handle the decoding of logging options. */
-decode_bits(&log_write_selector, &log_extra_selector, log_selector_string,
+decode_bits(&log_write_selector, &log_extra_selector, 0, 0, log_selector_string,
log_options, log_options_count, US"log");
DEBUG(D_any)
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;
(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);
}
if (bi_option)
{
- fclose(config_file);
+ (void)fclose(config_file);
if (bi_command != NULL)
{
int i = 0;
else
{
int i, j;
-
for (i = 0; i < group_count; i++)
{
if (group_list[i] == exim_gid) admin_user = TRUE;
}
/* If the caller is not trusted, certain arguments are ignored when running for
-real, but are permitted when checking things (-be, -bv, -bt, -bh, -bf). Note
-that authority for performing certain actions on messages is tested in the
+real, but are permitted when checking things (-be, -bv, -bt, -bh, -bf, -bF).
+Note that authority for performing certain actions on messages is tested in the
queue_action() function. */
-if (!trusted_caller && !checking && filter_test == NULL)
+if (!trusted_caller && !checking && filter_test == FTEST_NONE)
{
sender_host_name = sender_host_address = interface_address =
sender_ident = received_protocol = NULL;
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;
(is_inetd && smtp_load_reserve >= 0)
))
{
- load_average = os_getloadavg();
+ load_average = OS_GETLOADAVG();
}
#endif
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 */
)
))
{
- 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. */
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");
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)
/* 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)
{
if (originator_name == NULL)
{
if (sender_address == NULL ||
- (!trusted_caller && filter_test == NULL))
+ (!trusted_caller && filter_test == FTEST_NONE))
{
uschar *name = US pw->pw_gecos;
uschar *amp = Ustrchr(name, '&');
/* 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)
{
/* 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();
}
message via SMTP (inetd invocation or otherwise). */
if ((sender_address == NULL && !smtp_input) ||
- (!trusted_caller && filter_test == NULL))
+ (!trusted_caller && filter_test == FTEST_NONE))
{
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.
|| /* OR */
(sender_address[0] != 0 && /* Non-empty sender address, AND */
!checking && /* Not running tests, AND */
- filter_test == NULL)) /* Not testing a filter */
+ filter_test == FTEST_NONE)) /* Not testing a filter */
{
sender_address = originator_login;
sender_address_forced = FALSE;
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);
}
}
#endif
}
+ /* The data file will be open after -Mset */
+
+ if (deliver_datafile >= 0)
+ {
+ (void)close(deliver_datafile);
+ deliver_datafile = -1;
+ }
+
exim_exit(EXIT_SUCCESS);
}
}
/* 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)
{
- sender_ident = NULL;
- if (running_in_test_harness && sender_host_port != 0 &&
- interface_address != NULL && interface_port != 0)
- verify_get_ident(1413);
+ int x[4];
+ int size;
+
+ 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, ':');
+
+ /* Now set up for testing */
host_build_sender_fullhost();
smtp_input = TRUE;
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: <reason>") and writing another one is
+ unnecessary clutter. */
+
if (smtp_start_session())
{
reset_point = store_get(0);
if (smtp_setup_msg() <= 0) break;
if (!receive_msg(FALSE)) break;
}
+ smtp_log_no_mail();
}
exim_exit(EXIT_SUCCESS);
}
printf("Configuration file is %s\n", config_main_filename);
return EXIT_SUCCESS;
}
- if (filter_test == NULL)
- {
- 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;
- }
+
+ if (filter_test == FTEST_NONE)
+ exim_usage(called_as);
}
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();
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
{
sender_address);
}
-/* Initialize the local_queue-only flag (this will be ignored if mua_wrapper is
-set) */
+/* Initialize the session_local_queue-only flag (this will be ignored if
+mua_wrapper is set) */
queue_check_only();
-local_queue_only = queue_only;
+session_local_queue_only = queue_only;
/* For non-SMTP and for batched SMTP input, check that there is enough space on
the spool if so configured. On failure, we must not attempt to send an error
return EXIT_FAILURE;
}
-/* If this is smtp input of any kind, handle the start of the SMTP
-session. */
+/* If this is smtp input of any kind, real or batched, handle the start of the
+SMTP session.
+
+NOTE: We do *not* call smtp_log_no_mail() if smtp_start_session() fails,
+because a log line has already been written for all its failure exists
+(usually "connection refused: <reason>") and writing another one is
+unnecessary clutter. */
if (smtp_input)
{
}
}
-/* 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 "
store_reset(reset_point);
message_id[0] = 0;
- /* In the SMTP case, we have to handle the initial SMTP input and build the
- recipients list, before calling receive_msg() to read the message proper.
- Whatever sender address is actually given in the SMTP transaction is
- actually ignored for local senders - we use the actual sender, which is
- normally either the underlying user running this process or a -f argument
- provided by a trusted caller. It is saved in real_sender_address.
-
- However, if this value is NULL, we are dealing with a trusted caller when
- -f was not used; in this case, the SMTP sender is allowed to stand.
-
- Also, if untrusted_set_sender is set, we permit sender addresses that match
- anything in its list.
-
- The variable raw_sender_address holds the sender address before rewriting. */
+ /* Handle the SMTP case; call smtp_setup_mst() to deal with the initial SMTP
+ input and build the recipients list, before calling receive_msg() to read the
+ message proper. Whatever sender address is given in the SMTP transaction is
+ often ignored for local senders - we use the actual sender, which is normally
+ either the underlying user running this process or a -f argument provided by
+ a trusted caller. It is saved in real_sender_address. The test for whether to
+ accept the SMTP sender is encapsulated in receive_check_set_sender(). */
if (smtp_input)
{
sender_address = raw_sender = real_sender_address;
sender_address_unrewritten = NULL;
}
+
+ /* For batched SMTP, we have to run the acl_not_smtp_start ACL, since it
+ isn't really SMTP, so no other ACL will run until the acl_not_smtp one at
+ the very end. The result of the ACL is ignored (as for other non-SMTP
+ messages). It is run for its potential side effects. */
+
+ if (smtp_batched_input && acl_not_smtp_start != NULL)
+ {
+ uschar *user_msg, *log_msg;
+ enable_dollar_recipients = TRUE;
+ (void)acl_check(ACL_WHERE_NOTSMTP_START, NULL, acl_not_smtp_start,
+ &user_msg, &log_msg);
+ enable_dollar_recipients = FALSE;
+ }
+
+ /* Now get the data for the message */
+
more = receive_msg(extract_recipients);
if (message_id[0] == 0)
{
if (more) continue;
+ smtp_log_no_mail(); /* Log no mail if configured */
exim_exit(EXIT_FAILURE);
}
}
- else exim_exit((rc == 0)? EXIT_SUCCESS : EXIT_FAILURE);
+ else
+ {
+ smtp_log_no_mail(); /* Log no mail if configured */
+ exim_exit((rc == 0)? EXIT_SUCCESS : EXIT_FAILURE);
+ }
}
/* In the non-SMTP case, we have all the information from the command
int rcount = 0;
int count = argc - recipients_arg;
uschar **list = argv + recipients_arg;
-
+
/* These options cannot be changed dynamically for non-SMTP messages */
-
+
active_local_sender_retain = local_sender_retain;
- active_local_from_check = local_from_check;
+ active_local_from_check = local_from_check;
/* Save before any rewriting */
}
}
- /* Read the data for the message. If filter_test is true, this will
- just read the headers for the message, and not write anything onto
- the spool. */
+ /* 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. */
message_ended = END_NOTENDED;
more = receive_msg(extract_recipients);
unless specified. The the return path is set to to the sender unless it has
already been set from a return-path header in the message. */
- if (filter_test != NULL)
+ if (filter_test != FTEST_NONE)
{
deliver_domain = (ftest_domain != NULL)?
ftest_domain : qualify_domain_recipient;
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 */
- exim_exit(filter_runtest(filter_fd, ftest_system, more)?
- EXIT_SUCCESS : EXIT_FAILURE);
+ (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;
}
}
/* Else do the delivery unless the ACL or local_scan() called for queue only
or froze the message. Always deliver in a separate process. A fork failure is
not a disaster, as the delivery will eventually happen on a subsequent queue
- run. */
+ run. The search cache must be tidied before the fork, as the parent will
+ do it before exiting. The child will trigger a lookup failure and
+ thereby defer the delivery if it tries to use (for example) a cached ldap
+ connection that the parent has called unbind on. */
else if (!queue_only_policy && !deliver_freeze)
{
pid_t pid;
+ search_tidyup();
+
if ((pid = fork()) == 0)
{
int rc;