* Exim - an Internet mail transport agent *
*************************************************/
-/* Copyright (c) University of Cambridge 1995 - 2015 */
+/* Copyright (c) University of Cambridge 1995 - 2016 */
/* See the file NOTICE for conditions of use and distribution. */
#include "exim.h"
+#if defined(__GLIBC__) && !defined(__UCLIBC__)
+# include <gnu/libc-version.h>
+#endif
+
#ifdef USE_GNUTLS
# include <gnutls/gnutls.h>
# if GNUTLS_VERSION_NUMBER < 0x030103 && !defined(DISABLE_OCSP)
void
set_process_info(const char *format, ...)
{
-int len;
+int len = sprintf(CS process_info, "%5d ", (int)getpid());
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 ****");
os_restarting_signal(sig, usr1_handler);
-fd = Uopen(process_log_path, O_APPEND|O_WRONLY, LOG_MODE);
-if (fd < 0)
+if ((fd = Uopen(process_log_path, O_APPEND|O_WRONLY, LOG_MODE)) < 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
Returns: -1, 0, or +1
*/
-int
+static int
exim_tvcmp(struct timeval *t1, struct timeval *t2)
{
if (t1->tv_sec > t2->tv_sec) return +1;
/* Exim uses a time + a pid to generate a unique identifier in two places: its
message IDs, and in file names for maildir deliveries. Because some OS now
re-use pids within the same second, sub-second times are now being used.
-However, for absolute certaintly, we must ensure the clock has ticked before
+However, for absolute certainty, we must ensure the clock has ticked before
allowing the relevant process to complete. At the time of implementation of
this code (February 2003), the speed of processors is such that the clock will
invariably have ticked already by the time a process has done its job. This
#ifdef WITH_CONTENT_SCAN
fprintf(f, " Content_Scanning");
#endif
-#ifdef WITH_OLD_DEMIME
- fprintf(f, " Old_Demime");
-#endif
#ifndef DISABLE_DKIM
fprintf(f, " DKIM");
#endif
#ifndef DISABLE_DNSSEC
fprintf(f, " DNSSEC");
#endif
+#ifndef DISABLE_EVENT
+ fprintf(f, " Event");
+#endif
+#ifdef SUPPORT_I18N
+ fprintf(f, " I18N");
+#endif
#ifndef DISABLE_OCSP
fprintf(f, " OCSP");
#endif
#ifndef DISABLE_PRDR
fprintf(f, " PRDR");
#endif
-#ifndef DISABLE_SOCKS
+#ifdef SUPPORT_PROXY
+ fprintf(f, " PROXY");
+#endif
+#ifdef SUPPORT_SOCKS
fprintf(f, " SOCKS");
#endif
+#ifdef TCP_FASTOPEN
+ fprintf(f, " TCP_Fast_Open");
+#endif
+#ifdef EXPERIMENTAL_LMDB
+ fprintf(f, " Experimental_LMDB");
+#endif
+#ifdef EXPERIMENTAL_QUEUEFILE
+ fprintf(f, " Experimental_QUEUEFILE");
+#endif
#ifdef EXPERIMENTAL_SPF
fprintf(f, " Experimental_SPF");
#endif
#ifdef EXPERIMENTAL_DSN_INFO
fprintf(f, " Experimental_DSN_info");
#endif
-#ifdef EXPERIMENTAL_INTERNATIONAL
- fprintf(f, " Experimental_International");
-#endif
-#ifdef EXPERIMENTAL_PROXY
- fprintf(f, " Experimental_Proxy");
-#endif
-#ifdef EXPERIMENTAL_EVENT
- fprintf(f, " Experimental_Event");
-#endif
-#ifdef EXPERIMENTAL_REDIS
- fprintf(f, " Experimental_Redis");
-#endif
fprintf(f, "\n");
fprintf(f, "Lookups (built-in):");
#if defined(LOOKUP_LDAP) && LOOKUP_LDAP!=2
fprintf(f, " ldap ldapdn ldapm");
#endif
+#ifdef EXPERIMENTAL_LMDB
+ fprintf(f, " lmdb");
+#endif
#if defined(LOOKUP_MYSQL) && LOOKUP_MYSQL!=2
fprintf(f, " mysql");
#endif
#if defined(LOOKUP_PGSQL) && LOOKUP_PGSQL!=2
fprintf(f, " pgsql");
#endif
+#if defined(LOOKUP_REDIS) && LOOKUP_REDIS!=2
+ fprintf(f, " redis");
+#endif
#if defined(LOOKUP_SQLITE) && LOOKUP_SQLITE!=2
fprintf(f, " sqlite");
#endif
#ifdef TRANSPORT_PIPE
fprintf(f, " pipe");
#endif
+#ifdef EXPERIMENTAL_QUEUEFILE
+ fprintf(f, " queuefile");
+#endif
#ifdef TRANSPORT_SMTP
fprintf(f, " smtp");
#endif
fprintf(f, "%d\n", (unsigned int)fixed_never_users[i]);
}
+fprintf(f, "Configure owner: %d:%d\n", config_uid, config_gid);
+
fprintf(f, "Size of off_t: " SIZE_T_FMT "\n", sizeof(off_t));
/* Everything else is details which are only worth reporting when debugging.
fprintf(f, "Compiler: <unknown>\n");
#endif
+#if defined(__GLIBC__) && !defined(__UCLIBC__)
+ fprintf(f, "Library version: Glibc: Compile: %d.%d\n",
+ __GLIBC__, __GLIBC_MINOR__);
+ if (__GLIBC_PREREQ(2, 1))
+ fprintf(f, " Runtime: %s\n",
+ gnu_get_libc_version());
+#endif
+
#ifdef SUPPORT_TLS
tls_version_report(f);
#endif
-#ifdef EXPERIMENTAL_INTERNATIONAL
+#ifdef SUPPORT_I18N
utf8_version_report(f);
#endif
characters; unless it's an ancient version of PCRE in which case it
is not defined. */
#ifndef PCRE_PRERELEASE
-#define PCRE_PRERELEASE
+# define PCRE_PRERELEASE
#endif
#define QUOTE(X) #X
#define EXPAND_AND_QUOTE(X) QUOTE(X)
if (!needs_quote) return lpart;
size = ptr = 0;
-yield = string_cat(NULL, &size, &ptr, US"\"", 1);
+yield = string_catn(NULL, &size, &ptr, US"\"", 1);
for (;;)
{
uschar *nq = US Ustrpbrk(lpart, "\\\"");
if (nq == NULL)
{
- yield = string_cat(yield, &size, &ptr, lpart, Ustrlen(lpart));
+ yield = string_cat(yield, &size, &ptr, lpart);
break;
}
- yield = string_cat(yield, &size, &ptr, lpart, nq - lpart);
- yield = string_cat(yield, &size, &ptr, US"\\", 1);
- yield = string_cat(yield, &size, &ptr, nq, 1);
+ yield = string_catn(yield, &size, &ptr, lpart, nq - lpart);
+ yield = string_catn(yield, &size, &ptr, US"\\", 1);
+ yield = string_catn(yield, &size, &ptr, nq, 1);
lpart = nq + 1;
}
-yield = string_cat(yield, &size, &ptr, US"\"", 1);
+yield = string_catn(yield, &size, &ptr, US"\"", 1);
yield[ptr] = 0;
return yield;
}
while (p < ss && isspace(*p)) p++; /* leading space after cont */
}
- yield = string_cat(yield, &size, &ptr, p, ss - p);
+ yield = string_catn(yield, &size, &ptr, p, ss - p);
#ifdef USE_READLINE
if (fn_readline != NULL) free(readline_line);
#endif
+ /* yield can only be NULL if ss==p */
if (ss == p || yield[ptr-1] != '\\')
{
- yield[ptr] = 0;
+ if (yield) yield[ptr] = 0;
break;
}
yield[--ptr] = 0;
exim_usage(uschar *progname)
{
-/* Handle specific program invocation varients */
+/* Handle specific program invocation variants */
if (Ustrcmp(progname, US"-mailq") == 0)
{
fprintf(stderr,
/* Typically, Exim will drop privileges if macros are supplied. In some
cases, we want to not do so.
-Arguments: none (macros is a global)
+Arguments: opt_D_used - true if the commandline had a "-D" option
Returns: true if trusted, false otherwise
*/
static BOOL
-macros_trusted(void)
+macros_trusted(BOOL opt_D_used)
{
#ifdef WHITELIST_D_MACROS
macro_item *m;
BOOL prev_char_item, found;
#endif
-if (macros == NULL)
+if (!opt_D_used)
return TRUE;
#ifndef WHITELIST_D_MACROS
return FALSE;
}
whites[i] = NULL;
-/* The list of macros should be very short. Accept the N*M complexity. */
-for (m = macros; m != NULL; m = m->next)
+/* The list of commandline macros should be very short.
+Accept the N*M complexity. */
+for (m = macros; m; m = m->next) if (m->command_line)
{
found = FALSE;
for (w = whites; *w; ++w)
}
if (!found)
return FALSE;
- if (m->replacement == NULL)
+ if (!m->replacement)
continue;
- len = Ustrlen(m->replacement);
- if (len == 0)
+ if ((len = m->replen) == 0)
continue;
n = pcre_exec(regex_whitelisted_macro, NULL, CS m->replacement, len,
0, PCRE_EOPT, NULL, 0);
BOOL local_queue_only;
BOOL more = TRUE;
BOOL one_msg_action = FALSE;
+BOOL opt_D_used = FALSE;
BOOL queue_only_set = FALSE;
BOOL receiving_message = TRUE;
BOOL sender_ident_set = FALSE;
/* Ensure we have a buffer for constructing log entries. Use malloc directly,
because store_malloc writes a log entry on failure. */
-log_buffer = (uschar *)malloc(LOG_BUFFER_SIZE);
-if (log_buffer == NULL)
+if (!(log_buffer = US malloc(LOG_BUFFER_SIZE)))
{
fprintf(stderr, "exim: failed to get store for log buffer\n");
exit(EXIT_FAILURE);
for (i = 0; i < REGEX_VARS; i++) regex_vars[i] = NULL;
-
/* If the program is called as "mailq" treat it as equivalent to "exim -bp";
this seems to be a generally accepted convention, since one finds symbolic
links called "mailq" in standard OS configurations. */
break;
}
- /* An option consistion of -- terminates the options */
+ /* An option consisting of -- terminates the options */
if (Ustrcmp(arg, "--") == 0)
{
else if (*argrest == 'F')
{
- filter_test |= FTEST_SYSTEM;
+ filter_test |= checking = FTEST_SYSTEM;
if (*(++argrest) != 0) { badarg = TRUE; break; }
if (++i < argc) filter_test_sfile = argv[i]; else
{
{
if (*(++argrest) == 0)
{
- filter_test |= FTEST_USER;
+ filter_test |= checking = FTEST_USER;
if (++i < argc) filter_test_ufile = argv[i]; else
{
fprintf(stderr, "exim: file name expected after %s\n", argv[i-1]);
sender_host_address = argv[i];
host_checking = checking = log_testing_mode = TRUE;
host_checking_callout = argrest[1] == 'c';
+ message_logs = FALSE;
}
/* -bi: This option is used by sendmail to initialize *the* alias file,
else if (Ustrcmp(argrest, "malware") == 0)
{
if (++i >= argc) { badarg = TRUE; break; }
+ checking = TRUE;
malware_test_file = argv[i];
}
else if (Ustrcmp(argrest, "rt") == 0)
{
+ checking = TRUE;
test_retry_arg = i + 1;
goto END_ARG;
}
else if (Ustrcmp(argrest, "rw") == 0)
{
+ checking = TRUE;
test_rewrite_arg = i + 1;
goto END_ARG;
}
printf("%s\n", CS version_copyright);
version_printed = TRUE;
show_whats_supported(stdout);
+ log_testing_mode = TRUE;
}
/* -bw: inetd wait mode, accept a listening socket as stdin */
#ifdef ALT_CONFIG_PREFIX
int sep = 0;
int len = Ustrlen(ALT_CONFIG_PREFIX);
- uschar *list = argrest;
+ const uschar *list = argrest;
uschar *filename;
while((filename = string_nextinlist(&list, &sep, big_buffer,
big_buffer_size)) != NULL)
#else
{
int ptr = 0;
- macro_item *mlast = NULL;
macro_item *m;
uschar name[24];
uschar *s = argrest;
+ opt_D_used = TRUE;
while (isspace(*s)) s++;
if (*s < 'A' || *s > 'Z')
while (isspace(*s)) s++;
}
- for (m = macros; m != NULL; m = m->next)
- {
+ for (m = macros; m; m = m->next)
if (Ustrcmp(m->name, name) == 0)
{
fprintf(stderr, "exim: duplicated -D in command line\n");
exit(EXIT_FAILURE);
}
- mlast = m;
- }
- m = store_get(sizeof(macro_item) + Ustrlen(name));
- m->next = NULL;
- m->command_line = TRUE;
- if (mlast == NULL) macros = m; else mlast->next = m;
- Ustrcpy(m->name, name);
- m->replacement = string_copy(s);
+ m = macro_create(string_copy(name), string_copy(s), TRUE);
if (clmacro_count >= MAX_CLMACROS)
{
if (temp >= argrest && *temp == '.') f_end_dot = TRUE;
allow_domain_literals = TRUE;
strip_trailing_dot = TRUE;
-#ifdef EXPERIMENTAL_INTERNATIONAL
+#ifdef SUPPORT_I18N
allow_utf8_domains = TRUE;
#endif
sender_address = parse_extract_address(argrest, &errmess,
&dummy_start, &dummy_end, &sender_address_domain, TRUE);
-#ifdef EXPERIMENTAL_INTERNATIONAL
+#ifdef SUPPORT_I18N
message_smtputf8 = string_is_utf8(sender_address);
allow_utf8_domains = FALSE;
#endif
return EXIT_FAILURE;
}
- /* Set up $sending_ip_address and $sending_port */
+ /* Set up $sending_ip_address and $sending_port, unless proxied */
- 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 (!continue_proxy_cipher)
+ 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;
}
+ else if (*argrest == 'C' && argrest[1] && !argrest[2])
+ {
+ switch(argrest[1])
+ {
/* -MCA: set the smtp_authenticated flag; this is useful only when it
precedes -MC (see above). The flag indicates that the host to which
Exim is connected has accepted an AUTH sequence. */
- else if (Ustrcmp(argrest, "CA") == 0)
- {
- smtp_authenticated = TRUE;
- break;
- }
+ case 'A': smtp_authenticated = TRUE; break;
/* -MCD: set the smtp_use_dsn flag; this indicates that the host
that exim is connected to supports the esmtp extension DSN */
- else if (Ustrcmp(argrest, "CD") == 0)
- {
- smtp_use_dsn = TRUE;
- break;
- }
+
+ case 'D': smtp_peer_options |= OPTION_DSN; break;
+
+ /* -MCG: set the queue name, to a non-default value */
+
+ case 'G': if (++i < argc) queue_name = string_copy(argv[i]);
+ else badarg = TRUE;
+ break;
+
+ /* -MCK: the peer offered CHUNKING. Must precede -MC */
+
+ case 'K': smtp_peer_options |= OPTION_CHUNKING; break;
/* -MCP: set the smtp_use_pipelining flag; this is useful only when
it preceded -MC (see above) */
- else if (Ustrcmp(argrest, "CP") == 0)
- {
- smtp_use_pipelining = TRUE;
- break;
- }
+ case 'P': smtp_peer_options |= OPTION_PIPE; break;
/* -MCQ: pass on the pid of the queue-running process that started
this chain of deliveries and the fd of its synchronizing pipe; this
is useful only when it precedes -MC (see above) */
- else if (Ustrcmp(argrest, "CQ") == 0)
- {
- if(++i < argc) passed_qr_pid = (pid_t)(Uatol(argv[i]));
- else badarg = TRUE;
- if(++i < argc) passed_qr_pipe = (int)(Uatol(argv[i]));
- else badarg = TRUE;
- break;
- }
+ case 'Q': if (++i < argc) passed_qr_pid = (pid_t)(Uatol(argv[i]));
+ else badarg = TRUE;
+ if (++i < argc) passed_qr_pipe = (int)(Uatol(argv[i]));
+ else badarg = TRUE;
+ break;
/* -MCS: set the smtp_use_size flag; this is useful only when it
precedes -MC (see above) */
- else if (Ustrcmp(argrest, "CS") == 0)
- {
- smtp_use_size = TRUE;
- break;
- }
+ case 'S': smtp_peer_options |= OPTION_SIZE; break;
+
+#ifdef SUPPORT_TLS
+ /* -MCt: similar to -MCT below but the connection is still open
+ via a proxy proces which handles the TLS context and coding.
+ Require three arguments for the proxied local address and port,
+ and the TLS cipher. */
+
+ case 't': if (++i < argc) sending_ip_address = argv[i];
+ else badarg = TRUE;
+ if (++i < argc) sending_port = (int)(Uatol(argv[i]));
+ else badarg = TRUE;
+ if (++i < argc) continue_proxy_cipher = argv[i];
+ else badarg = TRUE;
+ /*FALLTHROUGH*/
/* -MCT: set the tls_offered flag; this is useful only when it
precedes -MC (see above). The flag indicates that the host to which
Exim is connected has offered TLS support. */
- #ifdef SUPPORT_TLS
- else if (Ustrcmp(argrest, "CT") == 0)
- {
- tls_offered = TRUE;
- break;
+ case 'T': smtp_peer_options |= OPTION_TLS; break;
+#endif
+
+ default: badarg = TRUE; break;
+ }
+ break;
}
- #endif
/* -M[x]: various operations on the following list of message ids:
-M deliver the messages, ignoring next retry times and thawing
/* -oMr: Received protocol */
- else if (Ustrcmp(argrest, "Mr") == 0) received_protocol = argv[++i];
+ else if (Ustrcmp(argrest, "Mr") == 0)
+
+ if (received_protocol)
+ {
+ fprintf(stderr, "received_protocol is set already\n");
+ exit(EXIT_FAILURE);
+ }
+ else received_protocol = argv[++i];
/* -oMs: Set sender host name */
if (*argrest != 0)
{
- uschar *hn = Ustrchr(argrest, ':');
+ uschar *hn;
+
+ if (received_protocol)
+ {
+ fprintf(stderr, "received_protocol is set already\n");
+ exit(EXIT_FAILURE);
+ }
+
+ hn = Ustrchr(argrest, ':');
if (hn == NULL)
{
received_protocol = argrest;
}
else
{
+ int old_pool = store_pool;
+ store_pool = POOL_PERM;
received_protocol = string_copyn(argrest, hn - argrest);
+ store_pool = old_pool;
sender_host_name = hn + 1;
}
}
if (*argrest == 'f')
{
queue_run_force = TRUE;
- if (*(++argrest) == 'f')
+ if (*++argrest == 'f')
{
deliver_force_thaw = TRUE;
argrest++;
argrest++;
}
- /* -q[f][f][l]: Run the queue, optionally forced, optionally local only,
- optionally starting from a given message id. */
+ /* -q[f][f][l][G<name>]... Work on the named queue */
+
+ if (*argrest == 'G')
+ {
+ int i;
+ for (argrest++, i = 0; argrest[i] && argrest[i] != '/'; ) i++;
+ queue_name = string_copyn(argrest, i);
+ argrest += i;
+ if (*argrest == '/') argrest++;
+ }
+
+ /* -q[f][f][l][G<name>]: Run the queue, optionally forced, optionally local
+ only, optionally named, optionally starting from a given message id. */
if (*argrest == 0 &&
(i + 1 >= argc || argv[i+1][0] == '-' || mac_ismsgid(argv[i+1])))
stop_queue_run_id = argv[++i];
}
- /* -q[f][f][l]<n>: Run the queue at regular intervals, optionally forced,
- optionally local only. */
+ /* -q[f][f][l][G<name>/]<n>: Run the queue at regular intervals, optionally
+ forced, optionally local only, optionally named. */
- else
+ else if ((queue_interval = readconf_readtime(*argrest ? argrest : argv[++i],
+ 0, FALSE)) <= 0)
{
- if (*argrest != 0)
- queue_interval = readconf_readtime(argrest, 0, FALSE);
- else
- queue_interval = readconf_readtime(argv[++i], 0, FALSE);
- if (queue_interval <= 0)
- {
- fprintf(stderr, "exim: bad time value %s: abandoned\n", argv[i]);
- exit(EXIT_FAILURE);
- }
+ fprintf(stderr, "exim: bad time value %s: abandoned\n", argv[i]);
+ exit(EXIT_FAILURE);
}
break;
if (*argrest != 0)
{
int i;
- for (i = 0; i < sizeof(rsopts)/sizeof(uschar *); i++)
- {
+ for (i = 0; i < nelem(rsopts); i++)
if (Ustrcmp(argrest, rsopts[i]) == 0)
{
if (i != 2) queue_run_force = TRUE;
if (i == 1 || i == 4) deliver_force_thaw = TRUE;
argrest += Ustrlen(rsopts[i]);
}
- }
}
/* -R: Set string to match in addresses for forced queue run to
pick out particular messages. */
- if (*argrest == 0)
+ if (*argrest)
+ deliver_selectstring = argrest;
+ else if (i+1 < argc)
+ deliver_selectstring = argv[++i];
+ else
{
- if (i+1 < argc) deliver_selectstring = argv[++i]; else
- {
- fprintf(stderr, "exim: string expected after -R\n");
- exit(EXIT_FAILURE);
- }
+ fprintf(stderr, "exim: string expected after -R\n");
+ exit(EXIT_FAILURE);
}
- else deliver_selectstring = argrest;
break;
in all cases provided there are no further characters in this
argument. */
- if (*argrest != 0)
+ if (*argrest)
{
int i;
- for (i = 0; i < sizeof(rsopts)/sizeof(uschar *); i++)
- {
+ for (i = 0; i < nelem(rsopts); i++)
if (Ustrcmp(argrest, rsopts[i]) == 0)
{
if (i != 2) queue_run_force = TRUE;
if (i == 1 || i == 4) deliver_force_thaw = TRUE;
argrest += Ustrlen(rsopts[i]);
}
- }
}
/* -S: Set string to match in addresses for forced queue run to
pick out particular messages. */
- if (*argrest == 0)
+ if (*argrest)
+ deliver_selectstring_sender = argrest;
+ else if (i+1 < argc)
+ deliver_selectstring_sender = argv[++i];
+ else
{
- if (i+1 < argc) deliver_selectstring_sender = argv[++i]; else
- {
- fprintf(stderr, "exim: string expected after -S\n");
- exit(EXIT_FAILURE);
- }
+ fprintf(stderr, "exim: string expected after -S\n");
+ exit(EXIT_FAILURE);
}
- else deliver_selectstring_sender = argrest;
break;
/* -Tqt is an option that is exclusively for use by the testing suite.
/* 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;
+if ( (deliver_selectstring || deliver_selectstring_sender)
+ && queue_interval < 0)
+ queue_interval = 0;
END_ARG:
) ||
(
msg_action_arg > 0 &&
- (daemon_listen || queue_interval >= 0 || list_options ||
+ (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) &&
+ (daemon_listen || queue_interval > 0) &&
(sender_address != NULL || list_options || list_queue || checking ||
bi_option)
) ||
if (( /* EITHER */
(!trusted_config || /* Config changed, or */
- !macros_trusted()) && /* impermissible macros and */
+ !macros_trusted(opt_D_used)) && /* impermissible macros and */
real_uid != root_uid && /* Not root, and */
!running_in_test_harness /* Not fudged */
) || /* OR */
This needs to happen before we read the main configuration. */
init_lookup_list();
-#ifdef EXPERIMENTAL_INTERNATIONAL
+#ifdef SUPPORT_I18N
if (running_in_test_harness) smtputf8_advertise_hosts = NULL;
#endif
/* Read the main runtime configuration data; this gives up if there
is a failure. It leaves the configuration file open so that the subsequent
-configuration data for delivery can be read if needed. */
+configuration data for delivery can be read if needed.
+
+NOTE: immediatly after opening the configuration file we change the working
+directory to "/"! Later we change to $spool_directory. We do it there, because
+during readconf_main() some expansion takes place already. */
+
+/* Store the initial cwd before we change directories */
+if ((initial_cwd = os_getcwd(NULL, 0)) == NULL)
+ {
+ perror("exim: can't get the current working directory");
+ exit(EXIT_FAILURE);
+ }
+
+/* checking:
+ -be[m] expansion test -
+ -b[fF] filter test new
+ -bh[c] host test -
+ -bmalware malware_test_file new
+ -brt retry test new
+ -brw rewrite test new
+ -bt address test -
+ -bv[s] address verify -
+ list_options:
+ -bP <option> (except -bP config, which sets list_config)
+
+If any of these options is set, we suppress warnings about configuration
+issues (currently about tls_advertise_hosts and keep_environment not being
+defined) */
+
+readconf_main(checking || list_options);
+
+if (builtin_macros_create_trigger) DEBUG(D_any)
+ debug_printf("Builtin macros created (expensive) due to config line '%.*s'\n",
+ Ustrlen(builtin_macros_create_trigger)-1, builtin_macros_create_trigger);
+
+/* Now in directory "/" */
+
+if (cleanup_environment() == FALSE)
+ log_write(0, LOG_PANIC_DIE, "Can't cleanup environment");
-readconf_main();
/* If an action on specific messages is requested, or if a daemon or queue
runner is being started, we need to know if Exim was called by an admin user.
else
{
int i, j;
- for (i = 0; i < group_count; i++)
- {
- if (group_list[i] == exim_gid) admin_user = TRUE;
- else if (admin_groups != NULL)
- {
- for (j = 1; j <= (int)(admin_groups[0]); j++)
+ for (i = 0; i < group_count && !admin_user; i++)
+ if (group_list[i] == exim_gid)
+ admin_user = TRUE;
+ else if (admin_groups)
+ for (j = 1; j <= (int)admin_groups[0] && !admin_user; j++)
if (admin_groups[j] == group_list[i])
- { admin_user = TRUE; break; }
- }
- if (admin_user) break;
- }
+ admin_user = TRUE;
}
/* Another group of privileged users are the trusted users. These are root,
{
int i, j;
- if (trusted_users != NULL)
- {
- for (i = 1; i <= (int)(trusted_users[0]); i++)
+ if (trusted_users)
+ for (i = 1; i <= (int)trusted_users[0] && !trusted_caller; i++)
if (trusted_users[i] == real_uid)
- { trusted_caller = TRUE; break; }
- }
+ trusted_caller = TRUE;
- if (!trusted_caller && trusted_groups != NULL)
- {
- for (i = 1; i <= (int)(trusted_groups[0]); i++)
- {
+ if (trusted_groups)
+ for (i = 1; i <= (int)trusted_groups[0] && !trusted_caller; i++)
if (trusted_groups[i] == real_gid)
trusted_caller = TRUE;
- else for (j = 0; j < group_count; j++)
- {
+ else for (j = 0; j < group_count && !trusted_caller; j++)
if (trusted_groups[i] == group_list[j])
- { trusted_caller = TRUE; break; }
- }
- if (trusted_caller) break;
- }
- }
+ trusted_caller = TRUE;
}
+/* At this point, we know if the user is privileged and some command-line
+options become possibly imperssible, depending upon the configuration file. */
+
+if (checking && commandline_checks_require_admin && !admin_user) {
+ fprintf(stderr, "exim: those command-line flags are set to require admin\n");
+ exit(EXIT_FAILURE);
+}
+
/* Handle the decoding of logging options. */
decode_bits(log_selector, log_selector_size, log_notall,
"syslog_processname is longer than 32 chars: aborting");
if (log_oneline)
- {
if (admin_user)
{
log_write(0, LOG_MAIN, "%s", log_oneline);
}
else
return EXIT_FAILURE;
- }
/* In some operating systems, the environment variable TMPDIR controls where
temporary files are created; Exim doesn't use these (apart from when delivering
to MBX mailboxes), but called libraries such as DBM libraries may require them.
If TMPDIR is found in the environment, reset it to the value defined in the
-TMPDIR macro, if this macro is defined. */
+EXIM_TMPDIR macro, if this macro is defined. For backward compatibility this
+macro may be called TMPDIR in old "Local/Makefile"s. It's converted to
+EXIM_TMPDIR by the build scripts.
+*/
-#ifdef TMPDIR
+#ifdef EXIM_TMPDIR
{
uschar **p;
- for (p = USS environ; *p != NULL; p++)
- {
- if (Ustrncmp(*p, "TMPDIR=", 7) == 0 &&
- Ustrcmp(*p+7, TMPDIR) != 0)
+ if (environ) for (p = USS environ; *p; p++)
+ if (Ustrncmp(*p, "TMPDIR=", 7) == 0 && Ustrcmp(*p+7, EXIM_TMPDIR) != 0)
{
- uschar *newp = malloc(Ustrlen(TMPDIR) + 8);
- sprintf(CS newp, "TMPDIR=%s", TMPDIR);
+ uschar * newp = store_malloc(Ustrlen(EXIM_TMPDIR) + 8);
+ sprintf(CS newp, "TMPDIR=%s", EXIM_TMPDIR);
*p = newp;
- DEBUG(D_any) debug_printf("reset TMPDIR=%s in environment\n", TMPDIR);
+ DEBUG(D_any) debug_printf("reset TMPDIR=%s in environment\n", EXIM_TMPDIR);
}
- }
}
#endif
this. We have to make a new environment if TZ is wrong, but don't bother if
timestamps_utc is set, because then all times are in UTC anyway. */
-if (timezone_string != NULL && strcmpic(timezone_string, US"UTC") == 0)
- {
+if (timezone_string && strcmpic(timezone_string, US"UTC") == 0)
timestamps_utc = TRUE;
- }
else
{
uschar *envtz = US getenv("TZ");
- if ((envtz == NULL && timezone_string != NULL) ||
- (envtz != NULL &&
- (timezone_string == NULL ||
- Ustrcmp(timezone_string, envtz) != 0)))
+ if (envtz
+ ? !timezone_string || Ustrcmp(timezone_string, envtz) != 0
+ : timezone_string != NULL
+ )
{
uschar **p = USS environ;
uschar **new;
uschar **newp;
int count = 0;
- while (*p++ != NULL) count++;
- if (envtz == NULL) count++;
- newp = new = malloc(sizeof(uschar *) * (count + 1));
- for (p = USS environ; *p != NULL; p++)
- {
- if (Ustrncmp(*p, "TZ=", 3) == 0) continue;
- *newp++ = *p;
- }
- if (timezone_string != NULL)
+ if (environ) while (*p++) count++;
+ if (!envtz) count++;
+ newp = new = store_malloc(sizeof(uschar *) * (count + 1));
+ if (environ) for (p = USS environ; *p; p++)
+ if (Ustrncmp(*p, "TZ=", 3) != 0) *newp++ = *p;
+ if (timezone_string)
{
- *newp = malloc(Ustrlen(timezone_string) + 4);
+ *newp = store_malloc(Ustrlen(timezone_string) + 4);
sprintf(CS *newp++, "TZ=%s", timezone_string);
}
*newp = NULL;
root for -C or -D, the caller must either be root or be invoking a
trusted configuration file (when deliver_drop_privilege is false). */
-if (removed_privilege && (!trusted_config || macros != NULL) &&
- real_uid == exim_uid)
- {
+if ( removed_privilege
+ && (!trusted_config || opt_D_used)
+ && real_uid == exim_uid)
if (deliver_drop_privilege)
really_exim = TRUE; /* let logging work normally */
else
log_write(0, LOG_MAIN|LOG_PANIC,
"exim user lost privilege for using %s option",
trusted_config? "-D" : "-C");
- }
/* Start up Perl interpreter if Perl support is configured and there is a
perl_startup option, and the configuration or the command line specifies
{
int i;
uschar *p = big_buffer;
- char * dummy;
Ustrcpy(p, "cwd= (failed)");
- dummy = /* quieten compiler */ getcwd(CS p+4, big_buffer_size - 4);
+
+ Ustrncpy(p + 4, initial_cwd, big_buffer_size-5);
+
while (*p) p++;
(void)string_format(p, big_buffer_size - (p - big_buffer), " %d args:", argc);
while (*p) p++;
quote = US"";
while (*pp != 0) if (isspace(*pp++)) { quote = US"\""; break; }
}
- sprintf(CS p, " %s%.*s%s", quote, (int)(big_buffer_size -
+ p += sprintf(CS p, " %s%.*s%s", quote, (int)(big_buffer_size -
(p - big_buffer) - 4), printing, quote);
- while (*p) p++;
}
if (LOGGING(arguments))
int dummy;
(void)directory_make(spool_directory, US"", SPOOL_DIRECTORY_MODE, FALSE);
dummy = /* quieten compiler */ Uchdir(spool_directory);
+ dummy = dummy; /* yet more compiler quietening, sigh */
}
/* Handle calls with the -bi option. This is a sendmail option to rebuild *the*
Note that authority for performing certain actions on messages is tested in the
queue_action() function. */
-if (!trusted_caller && !checking && filter_test == FTEST_NONE)
+if (!trusted_caller && !checking)
{
sender_host_name = sender_host_address = interface_address =
sender_ident = received_protocol = NULL;
(msg_action_arg < 0 || /* and */
msg_action != MSG_DELIVER) && /* not delivering and */
(!checking || !address_test_mode) /* not address checking */
- )
- ))
- {
+ ) ) )
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. */
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,
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 */
}
}
- yield = retry_find_config(s1, s2, basic_errno, more_errno);
- if (yield == NULL) printf("No retry information found\n"); else
+ if (!(yield = retry_find_config(s1, s2, basic_errno, more_errno)))
+ printf("No retry information found\n");
+ else
{
retry_rule *r;
more_errno = yield->more_errno;
printf("auth_failed ");
else printf("* ");
- for (r = yield->rules; r != NULL; r = r->next)
+ for (r = yield->rules; r; r = r->next)
{
printf("%c,%s", r->rule, readconf_printtime(r->timeout)); /* Do not */
printf(",%s", readconf_printtime(r->p1)); /* amalgamate */
(Ustrcmp(argv[i], "router") == 0 ||
Ustrcmp(argv[i], "transport") == 0 ||
Ustrcmp(argv[i], "authenticator") == 0 ||
- Ustrcmp(argv[i], "macro") == 0))
+ Ustrcmp(argv[i], "macro") == 0 ||
+ Ustrcmp(argv[i], "environment") == 0))
{
readconf_print(argv[i+1], argv[i], flag_n);
i++;
if (list_config)
{
set_process_info("listing config");
- readconf_print(US"config", NULL, FALSE);
+ readconf_print(US"config", NULL, flag_n);
exim_exit(EXIT_SUCCESS);
}
+/* Initialise subsystems as required */
+#ifndef DISABLE_DKIM
+dkim_exim_init();
+#endif
+deliver_init();
+
+
/* Handle a request to deliver one or more messages that are already on the
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.
(start_queue_run_id == NULL)? US"" : start_queue_run_id,
(stop_queue_run_id == NULL)? US"" : US" stopping at ",
(stop_queue_run_id == NULL)? US"" : stop_queue_run_id);
- set_process_info("running the queue (single queue run)");
+ if (*queue_name)
+ set_process_info("running the '%s' queue (single queue run)", queue_name);
+ else
+ set_process_info("running the queue (single queue run)");
queue_run(start_queue_run_id, stop_queue_run_id, FALSE);
exim_exit(EXIT_SUCCESS);
}
if (sender_address == NULL /* No sender_address set */
|| /* OR */
(sender_address[0] != 0 && /* Non-empty sender address, AND */
- !checking && /* Not running tests, AND */
- filter_test == FTEST_NONE)) /* Not testing a filter */
+ !checking)) /* Not running tests, including filter tests */
{
sender_address = originator_login;
sender_address_forced = FALSE;
if (expansion_test)
{
+ dns_init(FALSE, FALSE, FALSE);
if (msg_action_arg > 0 && msg_action == MSG_LOAD)
{
uschar spoolname[256]; /* Not big_buffer; used in spool_read_header() */
}
message_id = argv[msg_action_arg];
(void)string_format(spoolname, sizeof(spoolname), "%s-H", message_id);
- if (!spool_open_datafile(message_id))
+ if ((deliver_datafile = spool_open_datafile(message_id)) < 0)
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);
verify_get_ident(1413);
}
- /* In case the given address is a non-canonical IPv6 address, canonicize
+ /* In case the given address is a non-canonical IPv6 address, canonicalize
it. The code works for both IPv4 and IPv6, as it happens. */
size = host_aton(sender_host_address, x);
"**** This is not for real!\n\n",
sender_host_address);
+ memset(sender_host_cache, 0, sizeof(sender_host_cache));
if (verify_check_host(&hosts_connection_nolog) == OK)
BIT_CLEAR(log_selector, log_selector_size, Li_smtp_connection);
log_write(L_smtp_connection, LOG_MAIN, "%s", smtp_get_connection_info());
if (smtp_start_session())
{
- reset_point = store_get(0);
- for (;;)
+ for (reset_point = store_get(0); ; store_reset(reset_point))
{
- store_reset(reset_point);
if (smtp_setup_msg() <= 0) break;
if (!receive_msg(FALSE)) break;
+
+ return_path = sender_address = NULL;
+ dnslist_domain = dnslist_matched = NULL;
+#ifndef DISABLE_DKIM
+ dkim_cur_signer = NULL;
+#endif
+ acl_var_m = NULL;
+ deliver_localpart_orig = NULL;
+ deliver_domain_orig = NULL;
+ callout_address = sending_ip_address = NULL;
+ sender_rate = sender_rate_limit = sender_rate_period = NULL;
}
smtp_log_no_mail();
}
deliver_drop_privilege = TRUE;
queue_smtp = FALSE;
queue_smtp_domains = NULL;
-#ifdef EXPERIMENTAL_INTERNATIONAL
+#ifdef SUPPORT_I18N
message_utf8_downconvert = -1; /* convert-if-needed */
#endif
}
}
else
{
- if (received_protocol == NULL)
+ int old_pool = store_pool;
+ store_pool = POOL_PERM;
+ if (!received_protocol)
received_protocol = string_sprintf("local%s", called_as);
+ store_pool = old_pool;
set_process_info("accepting a local non-SMTP message from <%s>",
sender_address);
}
{
smtp_in = stdin;
smtp_out = stdout;
+ memset(sender_host_cache, 0, sizeof(sender_host_cache));
if (verify_check_host(&hosts_connection_nolog) == OK)
BIT_CLEAR(log_selector, log_selector_size, Li_smtp_connection);
log_write(L_smtp_connection, LOG_MAIN, "%s", smtp_get_connection_info());
else
{
thismessage_size_limit = expand_string_integer(message_size_limit, TRUE);
- if (expand_string_message != NULL)
- {
+ if (expand_string_message)
if (thismessage_size_limit == -1)
log_write(0, LOG_MAIN|LOG_PANIC_DIE, "failed to expand "
"message_size_limit: %s", expand_string_message);
else
log_write(0, LOG_MAIN|LOG_PANIC_DIE, "invalid value for "
"message_size_limit: %s", expand_string_message);
- }
}
/* Loop for several messages when reading SMTP input. If we fork any child
of Linux (where SIG_IGN does work) that are picky. If, having set SIG_IGN, a
process then calls waitpid(), a grumble is written to the system log, because
this is logically inconsistent. In other words, it doesn't like the paranoia.
-As a consequenc of this, the waitpid() below is now excluded if we are sure
+As a consequence of this, the waitpid() below is now excluded if we are sure
that SIG_IGN works. */
if (!synchronous_delivery)
while (more)
{
- store_reset(reset_point);
message_id[0] = 0;
/* Handle the SMTP case; call smtp_setup_mst() to deal with the initial SMTP
more = receive_msg(extract_recipients);
if (message_id[0] == 0)
{
- if (more) continue;
+ cancel_cutthrough_connection(TRUE, US"receive dropped");
+ if (more) goto moreloop;
smtp_log_no_mail(); /* Log no mail if configured */
exim_exit(EXIT_FAILURE);
}
}
else
{
+ cancel_cutthrough_connection(TRUE, US"message setup dropped");
smtp_log_no_mail(); /* Log no mail if configured */
exim_exit((rc == 0)? EXIT_SUCCESS : EXIT_FAILURE);
}
errors_sender_rc : EXIT_FAILURE;
}
-#ifdef EXPERIMENTAL_INTERNATIONAL
+#ifdef SUPPORT_I18N
{
BOOL b = allow_utf8_domains;
allow_utf8_domains = TRUE;
recipient =
parse_extract_address(s, &errmess, &start, &end, &domain, FALSE);
-#ifdef EXPERIMENTAL_INTERNATIONAL
+#ifdef SUPPORT_I18N
if (string_is_utf8(recipient))
message_smtputf8 = TRUE;
else
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)
+ if (acl_not_smtp_start)
{
uschar *user_msg, *log_msg;
enable_dollar_recipients = TRUE;
enable_dollar_recipients = FALSE;
}
+ /* Pause for a while waiting for input. If none received in that time,
+ close the logfile, if we had one open; then if we wait for a long-running
+ datasource (months, in one use-case) log rotation will not leave us holding
+ the file copy. */
+
+ if (!receive_timeout)
+ {
+ struct timeval t = { .tv_sec = 30*60, .tv_usec = 0 }; /* 30 minutes */
+ fd_set r;
+
+ FD_ZERO(&r); FD_SET(0, &r);
+ if (select(1, &r, NULL, NULL, &t) == 0) mainlog_close();
+ }
+
/* 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. */
not if queue_only is set (case 0). Case 1 doesn't happen here (too many
connections). */
- if (local_queue_only) switch(queue_only_reason)
+ if (local_queue_only)
{
- case 2:
- log_write(L_delay_delivery,
- LOG_MAIN, "no immediate delivery: more than %d messages "
- "received in one connection", smtp_accept_queue_per_connection);
- break;
-
- case 3:
- log_write(L_delay_delivery,
- LOG_MAIN, "no immediate delivery: load average %.2f",
- (double)load_average/1000.0);
- break;
+ cancel_cutthrough_connection(TRUE, US"no delivery; queueing");
+ switch(queue_only_reason)
+ {
+ case 2:
+ log_write(L_delay_delivery,
+ LOG_MAIN, "no immediate delivery: more than %d messages "
+ "received in one connection", smtp_accept_queue_per_connection);
+ break;
+
+ case 3:
+ log_write(L_delay_delivery,
+ LOG_MAIN, "no immediate delivery: load average %.2f",
+ (double)load_average/1000.0);
+ break;
+ }
}
+ else if (queue_only_policy || deliver_freeze)
+ cancel_cutthrough_connection(TRUE, US"no delivery; queueing");
+
/* 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
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)
+ else
{
pid_t pid;
search_tidyup();
if (geteuid() != root_uid && !deliver_drop_privilege && !unprivileged)
{
- (void)child_exec_exim(CEE_EXEC_EXIT, FALSE, NULL, FALSE, 2, US"-Mc",
- message_id);
+ delivery_re_exec(CEE_EXEC_EXIT);
/* Control does not return here. */
}
if (pid < 0)
{
+ cancel_cutthrough_connection(TRUE, US"delivery fork failed");
log_write(0, LOG_MAIN|LOG_PANIC, "failed to fork automatic delivery "
"process: %s", strerror(errno));
}
+ else
+ {
+ release_cutthrough_connection(US"msg passed for delivery");
- /* In the parent, wait if synchronous delivery is required. This will
- always be the case in MUA wrapper mode. */
+ /* In the parent, wait if synchronous delivery is required. This will
+ always be the case in MUA wrapper mode. */
- else if (synchronous_delivery)
- {
- int status;
- while (wait(&status) != pid);
- if ((status & 0x00ff) != 0)
- log_write(0, LOG_MAIN|LOG_PANIC,
- "process %d crashed with signal %d while delivering %s",
- (int)pid, status & 0x00ff, message_id);
- if (mua_wrapper && (status & 0xffff) != 0) exim_exit(EXIT_FAILURE);
+ if (synchronous_delivery)
+ {
+ int status;
+ while (wait(&status) != pid);
+ if ((status & 0x00ff) != 0)
+ log_write(0, LOG_MAIN|LOG_PANIC,
+ "process %d crashed with signal %d while delivering %s",
+ (int)pid, status & 0x00ff, message_id);
+ if (mua_wrapper && (status & 0xffff) != 0) exim_exit(EXIT_FAILURE);
+ }
}
}
#ifndef SIG_IGN_WORKS
while (waitpid(-1, NULL, WNOHANG) > 0);
#endif
+
+moreloop:
+ return_path = sender_address = NULL;
+ authenticated_sender = NULL;
+ deliver_localpart_orig = NULL;
+ deliver_domain_orig = NULL;
+ deliver_host = deliver_host_address = NULL;
+ dnslist_domain = dnslist_matched = NULL;
+#ifdef WITH_CONTENT_SCAN
+ malware_name = NULL;
+#endif
+ callout_address = NULL;
+ sending_ip_address = NULL;
+ acl_var_m = NULL;
+ { int i; for(i=0; i<REGEX_VARS; i++) regex_vars[i] = NULL; }
+
+ store_reset(reset_point);
}
exim_exit(EXIT_SUCCESS); /* Never returns */
return 0; /* To stop compiler warning */
}
+/*************************************************
+* read as much as requested *
+*************************************************/
+
+/* The syscall read(2) doesn't always returns as much as we want. For
+several reasons it might get less. (Not talking about signals, as syscalls
+are restartable). When reading from a network or pipe connection the sender
+might send in smaller chunks, with delays between these chunks. The read(2)
+may return such a chunk.
+
+The more the writer writes and the smaller the pipe between write and read is,
+the more we get the chance of reading leass than requested. (See bug 2130)
+
+This function read(2)s until we got all the data we *requested*.
+
+Note: This function may block. Use it only if you're sure about the
+amount of data you will get.
+
+Argument:
+ fd the file descriptor to read from
+ buffer pointer to a buffer of size len
+ len the requested(!) amount of bytes
+
+Returns: the amount of bytes read
+*/
+ssize_t
+readn(int fd, void *buffer, size_t len)
+{
+ void *next = buffer;
+ void *end = buffer + len;
+
+ while (next < end)
+ {
+ ssize_t got = read(fd, next, end - next);
+
+ /* I'm not sure if there are signals that can interrupt us,
+ for now I assume the worst */
+ if (got == -1 && errno == EINTR) continue;
+ if (got <= 0) return next - buffer;
+ next += got;
+ }
+
+ return len;
+}
+
+
/* End of exim.c */