summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorHeiko Schlittermann (HS12-RIPE) <hs@schlittermann.de>2021-05-01 12:04:41 +0200
committerHeiko Schlittermann (HS12-RIPE) <hs@schlittermann.de>2021-05-01 12:06:40 +0200
commitb2070b15251231e4841499e8c599676308ce394a (patch)
treebe26cbb4acb9d9f25086c0951b2833d6e164cd79
parent54a3c57cd134d235e1daec3b6e2dcf3dc1b778ff (diff)
parent5220dc30120bd79319d465bd7a6e4b21a0881f9a (diff)
downloadexim4-exim-4.92.3+fixes.tar.gz
Merge branch 'exim-4.92.3+fixes+qualys' into exim-4.92.3+fixesexim-4.92.3+fixes
Backported from 4.94.2 - minimal set of Qualys patches where appliciable - cherry-picks for other patches Testsuite (using GNUTLS) has the same failures as 4.92.3+fixes
-rw-r--r--doc/doc-txt/ChangeLog11
-rw-r--r--src/src/acl.c3
-rw-r--r--src/src/daemon.c180
-rw-r--r--src/src/dbfn.c110
-rw-r--r--src/src/deliver.c4
-rw-r--r--src/src/dmarc.c179
-rw-r--r--src/src/exim.c37
-rw-r--r--src/src/functions.h3
-rw-r--r--src/src/globals.c1
-rw-r--r--src/src/globals.h1
-rw-r--r--src/src/log.c215
-rw-r--r--src/src/macros.h1
-rw-r--r--src/src/parse.c9
-rw-r--r--src/src/pdkim/pdkim.c29
-rw-r--r--src/src/queue.c14
-rw-r--r--src/src/rda.c4
-rw-r--r--src/src/receive.c10
-rw-r--r--src/src/smtp_in.c85
-rw-r--r--src/src/smtp_out.c6
-rw-r--r--src/src/spool_in.c48
-rw-r--r--src/src/spool_out.c21
-rw-r--r--src/src/store.c29
-rw-r--r--src/src/string.c23
-rw-r--r--src/src/tls-openssl.c4
-rw-r--r--src/src/tls.c3
-rw-r--r--src/src/transports/smtp.c4
-rw-r--r--test/log/09001
-rw-r--r--test/scripts/0000-Basic/090036
-rw-r--r--test/stderr/02753
-rw-r--r--test/stderr/02783
-rw-r--r--test/stderr/03863
-rw-r--r--test/stderr/03883
-rw-r--r--test/stderr/03976
-rw-r--r--test/stderr/04023
-rw-r--r--test/stderr/04033
-rw-r--r--test/stderr/04043
-rw-r--r--test/stderr/04083
-rw-r--r--test/stderr/04873
-rw-r--r--test/stdout/090046
39 files changed, 799 insertions, 351 deletions
diff --git a/doc/doc-txt/ChangeLog b/doc/doc-txt/ChangeLog
index 5741fb212..5e7c0b12d 100644
--- a/doc/doc-txt/ChangeLog
+++ b/doc/doc-txt/ChangeLog
@@ -11,9 +11,20 @@ JH/42 Bug 2545: Fix CHUNKING for all RCPT commands rejected. Previously we
carried on and emitted a BDAT command, even when PIPELINING was not
active.
+QS/01 Creation of (database) files in $spool_dir: only uid=0 or the uid of
+ the Exim runtime user are allowed to create files.
Exim version 4.92.2
-------------------
+QS/02 PID file creation/deletion: only possible if uid=0 or uid is the Exim
+ runtime user.
+
+QS/01 Creation of (database) files in $spool_dir: only uid=0 or the euid of
+ the Exim runtime user are allowed to create files.
+
+QS/02 PID file creation/deletion: only possible if uid=0 or uid is the Exim
+ runtime user.
+
HS/01 Handle trailing backslash gracefully. (CVE-2019-15846)
diff --git a/src/src/acl.c b/src/src/acl.c
index f3b860e4a..49f6fe79c 100644
--- a/src/src/acl.c
+++ b/src/src/acl.c
@@ -4464,7 +4464,8 @@ switch (where)
/* Drop cutthrough conns, and drop heldopen verify conns if
the previous was not DATA */
{
- uschar prev = smtp_connection_had[smtp_ch_index-2];
+ uschar prev =
+ smtp_connection_had[SMTP_HBUFF_PREV(SMTP_HBUFF_PREV(smtp_ch_index))];
BOOL dropverify = !(prev == SCH_DATA || prev == SCH_BDAT);
cancel_cutthrough_connection(dropverify, US"quit or conndrop");
diff --git a/src/src/daemon.c b/src/src/daemon.c
index 01da39368..7c15d148c 100644
--- a/src/src/daemon.c
+++ b/src/src/daemon.c
@@ -888,6 +888,162 @@ while ((pid = waitpid(-1, &status, WNOHANG)) > 0)
}
+static void
+set_pid_file_path(void)
+{
+if (override_pid_file_path)
+ pid_file_path = override_pid_file_path;
+
+if (!*pid_file_path)
+ pid_file_path = string_sprintf("%s/exim-daemon.pid", spool_directory);
+
+if (pid_file_path[0] != '/')
+ log_write(0, LOG_PANIC_DIE, "pid file path %s must be absolute\n", pid_file_path);
+}
+
+
+enum pid_op { PID_WRITE, PID_CHECK, PID_DELETE };
+
+/* Do various pid file operations as safe as possible. Ideally we'd just
+drop the privileges for creation of the pid file and not care at all about removal of
+the file. FIXME.
+Returns: true on success, false + errno==EACCES otherwise
+*/
+static BOOL
+operate_on_pid_file(const enum pid_op operation, const pid_t pid)
+{
+char pid_line[sizeof(int) * 3 + 2];
+const int pid_len = snprintf(pid_line, sizeof(pid_line), "%d\n", (int)pid);
+BOOL lines_match = FALSE;
+
+char * path = NULL;
+char * base = NULL;
+char * dir = NULL;
+
+const int dir_flags = O_RDONLY | O_NONBLOCK;
+const int base_flags = O_NOFOLLOW | O_NONBLOCK;
+const mode_t base_mode = 0644;
+struct stat sb;
+
+int cwd_fd = -1;
+int dir_fd = -1;
+int base_fd = -1;
+
+BOOL success = FALSE;
+errno = EACCES;
+
+set_pid_file_path();
+if (!f.running_in_test_harness && real_uid != root_uid && real_uid != exim_uid) goto cleanup;
+if (pid_len < 2 || pid_len >= (int)sizeof(pid_line)) goto cleanup;
+
+path = CS string_copy(pid_file_path);
+if ((base = Ustrrchr(path, '/')) == NULL) /* should not happen, but who knows */
+ log_write(0, LOG_MAIN|LOG_PANIC_DIE, "pid file path \"%s\" does not contain a '/'", pid_file_path);
+
+dir = (base != path) ? path : "/";
+*base++ = '\0';
+
+if (!dir || !*dir || *dir != '/') goto cleanup;
+if (!base || !*base || strchr(base, '/') != NULL) goto cleanup;
+
+cwd_fd = open(".", dir_flags);
+if (cwd_fd < 0 || fstat(cwd_fd, &sb) != 0 || !S_ISDIR(sb.st_mode)) goto cleanup;
+dir_fd = open(dir, dir_flags);
+if (dir_fd < 0 || fstat(dir_fd, &sb) != 0 || !S_ISDIR(sb.st_mode)) goto cleanup;
+
+/* emulate openat */
+if (fchdir(dir_fd) != 0) goto cleanup;
+base_fd = open(base, O_RDONLY | base_flags);
+if (fchdir(cwd_fd) != 0)
+ log_write(0, LOG_MAIN|LOG_PANIC_DIE, "can't return to previous working dir: %s", strerror(errno));
+
+if (base_fd >= 0)
+ {
+ char line[sizeof(pid_line)];
+ ssize_t len = -1;
+
+ if (fstat(base_fd, &sb) != 0 || !S_ISREG(sb.st_mode)) goto cleanup;
+ if ((sb.st_mode & 07777) != base_mode || sb.st_nlink != 1) goto cleanup;
+ if (sb.st_size < 2 || sb.st_size >= (off_t)sizeof(line)) goto cleanup;
+
+ len = read(base_fd, line, sizeof(line));
+ if (len != (ssize_t)sb.st_size) goto cleanup;
+ line[len] = '\0';
+
+ if (strspn(line, "0123456789") != (size_t)len-1) goto cleanup;
+ if (line[len-1] != '\n') goto cleanup;
+ lines_match = (len == pid_len && strcmp(line, pid_line) == 0);
+ }
+
+if (operation == PID_WRITE)
+ {
+ if (!lines_match)
+ {
+ if (base_fd >= 0)
+ {
+ int error = -1;
+ /* emulate unlinkat */
+ if (fchdir(dir_fd) != 0) goto cleanup;
+ error = unlink(base);
+ if (fchdir(cwd_fd) != 0)
+ log_write(0, LOG_MAIN|LOG_PANIC_DIE, "can't return to previous working dir: %s", strerror(errno));
+ if (error) goto cleanup;
+ (void)close(base_fd);
+ base_fd = -1;
+ }
+ /* emulate openat */
+ if (fchdir(dir_fd) != 0) goto cleanup;
+ base_fd = open(base, O_WRONLY | O_CREAT | O_EXCL | base_flags, base_mode);
+ if (fchdir(cwd_fd) != 0)
+ log_write(0, LOG_MAIN|LOG_PANIC_DIE, "can't return to previous working dir: %s", strerror(errno));
+ if (base_fd < 0) goto cleanup;
+ if (fchmod(base_fd, base_mode) != 0) goto cleanup;
+ if (write(base_fd, pid_line, pid_len) != pid_len) goto cleanup;
+ DEBUG(D_any) debug_printf("pid written to %s\n", pid_file_path);
+ }
+ }
+else
+ {
+ if (!lines_match) goto cleanup;
+ if (operation == PID_DELETE)
+ {
+ int error = -1;
+ /* emulate unlinkat */
+ if (fchdir(dir_fd) != 0) goto cleanup;
+ error = unlink(base);
+ if (fchdir(cwd_fd) != 0)
+ log_write(0, LOG_MAIN|LOG_PANIC_DIE, "can't return to previous working dir: %s", strerror(errno));
+ if (error) goto cleanup;
+ }
+ }
+
+success = TRUE;
+errno = 0;
+
+cleanup:
+if (cwd_fd >= 0) (void)close(cwd_fd);
+if (dir_fd >= 0) (void)close(dir_fd);
+if (base_fd >= 0) (void)close(base_fd);
+return success;
+}
+
+
+/* Remove the daemon's pidfile. Note: runs with root privilege,
+as a direct child of the daemon. Does not return. */
+
+void
+delete_pid_file(void)
+{
+const BOOL success = operate_on_pid_file(PID_DELETE, getppid());
+
+DEBUG(D_any)
+ debug_printf("delete pid file %s %s: %s\n", pid_file_path,
+ success ? "success" : "failure", strerror(errno));
+
+exim_exit(EXIT_SUCCESS, US"");
+}
+
+
/*************************************************
* Exim Daemon Mainline *
@@ -1540,28 +1696,14 @@ The variable daemon_write_pid is used to control this. */
if (f.running_in_test_harness || write_pid)
{
- FILE *f;
-
- if (override_pid_file_path)
- pid_file_path = override_pid_file_path;
-
- if (pid_file_path[0] == 0)
- pid_file_path = string_sprintf("%s/exim-daemon.pid", spool_directory);
-
- if ((f = modefopen(pid_file_path, "wb", 0644)))
- {
- (void)fprintf(f, "%d\n", (int)getpid());
- (void)fclose(f);
- DEBUG(D_any) debug_printf("pid written to %s\n", pid_file_path);
- }
- else
- DEBUG(D_any)
- debug_printf("%s\n", string_open_failed(errno, "pid file %s",
- pid_file_path));
+ const enum pid_op operation = (f.running_in_test_harness
+ || real_uid == root_uid
+ || (real_uid == exim_uid && !override_pid_file_path)) ? PID_WRITE : PID_CHECK;
+ if (!operate_on_pid_file(operation, getpid()))
+ DEBUG(D_any) debug_printf("%s pid file %s: %s\n", (operation == PID_WRITE) ? "write" : "check", pid_file_path, strerror(errno));
}
/* Set up the handler for SIGHUP, which causes a restart of the daemon. */
-
sighup_seen = FALSE;
signal(SIGHUP, sighup_handler);
diff --git a/src/src/dbfn.c b/src/src/dbfn.c
index 336cfe73e..902756508 100644
--- a/src/src/dbfn.c
+++ b/src/src/dbfn.c
@@ -59,6 +59,66 @@ log_write(0, LOG_MAIN, "Berkeley DB error: %s", msg);
+static enum {
+ PRIV_DROPPING, PRIV_DROPPED,
+ PRIV_RESTORING, PRIV_RESTORED
+} priv_state = PRIV_RESTORED;
+
+static uid_t priv_euid;
+static gid_t priv_egid;
+static gid_t priv_groups[EXIM_GROUPLIST_SIZE + 1];
+static int priv_ngroups;
+
+/* Inspired by OpenSSH's temporarily_use_uid(). Thanks! */
+
+static void
+priv_drop_temp(const uid_t temp_uid, const gid_t temp_gid)
+{
+if (priv_state != PRIV_RESTORED) _exit(EXIT_FAILURE);
+priv_state = PRIV_DROPPING;
+
+priv_euid = geteuid();
+if (priv_euid == root_uid)
+ {
+ priv_egid = getegid();
+ priv_ngroups = getgroups(nelem(priv_groups), priv_groups);
+ if (priv_ngroups < 0) _exit(EXIT_FAILURE);
+
+ if (priv_ngroups > 0 && setgroups(1, &temp_gid) != 0) _exit(EXIT_FAILURE);
+ if (setegid(temp_gid) != 0) _exit(EXIT_FAILURE);
+ if (seteuid(temp_uid) != 0) _exit(EXIT_FAILURE);
+
+ if (geteuid() != temp_uid) _exit(EXIT_FAILURE);
+ if (getegid() != temp_gid) _exit(EXIT_FAILURE);
+ }
+
+priv_state = PRIV_DROPPED;
+}
+
+/* Inspired by OpenSSH's restore_uid(). Thanks! */
+
+static void
+priv_restore(void)
+{
+if (priv_state != PRIV_DROPPED) _exit(EXIT_FAILURE);
+priv_state = PRIV_RESTORING;
+
+if (priv_euid == root_uid)
+ {
+ if (seteuid(priv_euid) != 0) _exit(EXIT_FAILURE);
+ if (setegid(priv_egid) != 0) _exit(EXIT_FAILURE);
+ if (priv_ngroups > 0 && setgroups(priv_ngroups, priv_groups) != 0) _exit(EXIT_FAILURE);
+
+ if (geteuid() != priv_euid) _exit(EXIT_FAILURE);
+ if (getegid() != priv_egid) _exit(EXIT_FAILURE);
+ }
+
+priv_state = PRIV_RESTORED;
+}
+
+
+
+
/*************************************************
* Open and lock a database file *
*************************************************/
@@ -89,7 +149,6 @@ dbfn_open(uschar *name, int flags, open_db *dbblock, BOOL lof)
{
int rc, save_errno;
BOOL read_only = flags == O_RDONLY;
-BOOL created = FALSE;
flock_t lock_data;
uschar dirname[256], filename[256];
@@ -111,12 +170,13 @@ exists, there is no error. */
snprintf(CS dirname, sizeof(dirname), "%s/db", spool_directory);
snprintf(CS filename, sizeof(filename), "%s/%s.lockfile", dirname, name);
+priv_drop_temp(exim_uid, exim_gid);
if ((dbblock->lockfd = Uopen(filename, O_RDWR, EXIMDB_LOCKFILE_MODE)) < 0)
{
- created = TRUE;
(void)directory_make(spool_directory, US"db", EXIMDB_DIRECTORY_MODE, TRUE);
dbblock->lockfd = Uopen(filename, O_RDWR|O_CREAT, EXIMDB_LOCKFILE_MODE);
}
+priv_restore();
if (dbblock->lockfd < 0)
{
@@ -165,57 +225,17 @@ it easy to pin this down, there are now debug statements on either side of the
open call. */
snprintf(CS filename, sizeof(filename), "%s/%s", dirname, name);
-EXIM_DBOPEN(filename, dirname, flags, EXIMDB_MODE, &(dbblock->dbptr));
+priv_drop_temp(exim_uid, exim_gid);
+EXIM_DBOPEN(filename, dirname, flags, EXIMDB_MODE, &(dbblock->dbptr));
if (!dbblock->dbptr && errno == ENOENT && flags == O_RDWR)
{
DEBUG(D_hints_lookup)
debug_printf_indent("%s appears not to exist: trying to create\n", filename);
- created = TRUE;
EXIM_DBOPEN(filename, dirname, flags|O_CREAT, EXIMDB_MODE, &(dbblock->dbptr));
}
-
save_errno = errno;
-
-/* If we are running as root and this is the first access to the database, its
-files will be owned by root. We want them to be owned by exim. We detect this
-situation by noting above when we had to create the lock file or the database
-itself. Because the different dbm libraries use different extensions for their
-files, I don't know of any easier way of arranging this than scanning the
-directory for files with the appropriate base name. At least this deals with
-the lock file at the same time. Also, the directory will typically have only
-half a dozen files, so the scan will be quick.
-
-This code is placed here, before the test for successful opening, because there
-was a case when a file was created, but the DBM library still returned NULL
-because of some problem. It also sorts out the lock file if that was created
-but creation of the database file failed. */
-
-if (created && geteuid() == root_uid)
- {
- DIR *dd;
- struct dirent *ent;
- uschar *lastname = Ustrrchr(filename, '/') + 1;
- int namelen = Ustrlen(name);
-
- *lastname = 0;
- dd = opendir(CS filename);
-
- while ((ent = readdir(dd)))
- if (Ustrncmp(ent->d_name, name, namelen) == 0)
- {
- struct stat statbuf;
- Ustrcpy(lastname, ent->d_name);
- if (Ustat(filename, &statbuf) >= 0 && statbuf.st_uid != exim_uid)
- {
- DEBUG(D_hints_lookup) debug_printf_indent("ensuring %s is owned by exim\n", filename);
- if (Uchown(filename, exim_uid, exim_gid))
- DEBUG(D_hints_lookup) debug_printf_indent("failed setting %s to owned by exim\n", filename);
- }
- }
-
- closedir(dd);
- }
+priv_restore();
/* If the open has failed, return NULL, leaving errno set. If lof is TRUE,
log the event - also for debugging - but debug only if the file just doesn't
diff --git a/src/src/deliver.c b/src/src/deliver.c
index d4ed8af08..279672ce0 100644
--- a/src/src/deliver.c
+++ b/src/src/deliver.c
@@ -331,6 +331,10 @@ open_msglog_file(uschar *filename, int mode, uschar **error)
{
int fd, i;
+if (Ustrstr(filename, US"/../"))
+ log_write(0, LOG_MAIN|LOG_PANIC_DIE,
+ "Attempt to open msglog file path with upward-traversal: '%s'", filename);
+
for (i = 2; i > 0; i--)
{
fd = Uopen(filename,
diff --git a/src/src/dmarc.c b/src/src/dmarc.c
index f29f7eba6..c5e01c7ee 100644
--- a/src/src/dmarc.c
+++ b/src/src/dmarc.c
@@ -204,6 +204,97 @@ if ( dmarc_policy == DMARC_POLICY_REJECT && action == DMARC_RESULT_REJECT
}
}
+
+static int
+dmarc_write_history_file()
+{
+int tmp_ans;
+u_char **rua; /* aggregate report addressees */
+uschar *history_buffer = NULL;
+
+if (!dmarc_history_file)
+ {
+ DEBUG(D_receive) debug_printf("DMARC history file not set\n");
+ return DMARC_HIST_DISABLED;
+ }
+
+/* Generate the contents of the history file */
+history_buffer = string_sprintf(
+ "job %s\nreporter %s\nreceived %ld\nipaddr %s\nfrom %s\nmfrom %s\n",
+ message_id, primary_hostname, time(NULL), sender_host_address,
+ header_from_sender, expand_string(US"$sender_address_domain"));
+
+if (spf_response)
+ history_buffer = string_sprintf("%sspf %d\n", history_buffer, dmarc_spf_ares_result);
+ /* history_buffer = string_sprintf("%sspf -1\n", history_buffer); */
+
+history_buffer = string_sprintf(
+ "%s%spdomain %s\npolicy %d\n",
+ history_buffer, dkim_history_buffer, dmarc_used_domain, dmarc_policy);
+
+if ((rua = opendmarc_policy_fetch_rua(dmarc_pctx, NULL, 0, 1)))
+ for (tmp_ans = 0; rua[tmp_ans]; tmp_ans++)
+ history_buffer = string_sprintf("%srua %s\n", history_buffer, rua[tmp_ans]);
+else
+ history_buffer = string_sprintf("%srua -\n", history_buffer);
+
+opendmarc_policy_fetch_pct(dmarc_pctx, &tmp_ans);
+history_buffer = string_sprintf("%spct %d\n", history_buffer, tmp_ans);
+
+opendmarc_policy_fetch_adkim(dmarc_pctx, &tmp_ans);
+history_buffer = string_sprintf("%sadkim %d\n", history_buffer, tmp_ans);
+
+opendmarc_policy_fetch_aspf(dmarc_pctx, &tmp_ans);
+history_buffer = string_sprintf("%saspf %d\n", history_buffer, tmp_ans);
+
+opendmarc_policy_fetch_p(dmarc_pctx, &tmp_ans);
+history_buffer = string_sprintf("%sp %d\n", history_buffer, tmp_ans);
+
+opendmarc_policy_fetch_sp(dmarc_pctx, &tmp_ans);
+history_buffer = string_sprintf("%ssp %d\n", history_buffer, tmp_ans);
+
+history_buffer = string_sprintf(
+ "%salign_dkim %d\nalign_spf %d\naction %d\n",
+ history_buffer, da, sa, action);
+
+/* Write the contents to the history file */
+DEBUG(D_receive)
+ debug_printf("DMARC logging history data for opendmarc reporting%s\n",
+ (host_checking || f.running_in_test_harness) ? " (not really)" : "");
+if (host_checking || f.running_in_test_harness)
+ {
+ DEBUG(D_receive)
+ debug_printf("DMARC history data for debugging:\n%s", history_buffer);
+ }
+else
+ {
+ ssize_t written_len;
+ const int history_file_fd = log_open_as_exim(dmarc_history_file);
+
+ if (history_file_fd < 0)
+ {
+ log_write(0, LOG_MAIN|LOG_PANIC, "failure to create DMARC history file: %s",
+ dmarc_history_file);
+ return DMARC_HIST_FILE_ERR;
+ }
+
+ written_len = write_to_fd_buf(history_file_fd,
+ history_buffer,
+ Ustrlen(history_buffer));
+
+ (void)close(history_file_fd);
+
+ if (written_len <= 0)
+ {
+ log_write(0, LOG_MAIN|LOG_PANIC, "failure to write to DMARC history file: %s",
+ dmarc_history_file);
+ return DMARC_HIST_WRITE_ERR;
+ }
+ }
+return DMARC_HIST_OK;
+}
+
+
/* dmarc_process adds the envelope sender address to the existing
context (if any), retrieves the result, sets up expansion
strings and evaluates the condition outcome. */
@@ -486,94 +577,6 @@ if (!f.dmarc_disable_verify)
return OK;
}
-static int
-dmarc_write_history_file()
-{
-int history_file_fd;
-ssize_t written_len;
-int tmp_ans;
-u_char **rua; /* aggregate report addressees */
-uschar *history_buffer = NULL;
-
-if (!dmarc_history_file)
- {
- DEBUG(D_receive) debug_printf("DMARC history file not set\n");
- return DMARC_HIST_DISABLED;
- }
-history_file_fd = log_create(dmarc_history_file);
-
-if (history_file_fd < 0)
- {
- log_write(0, LOG_MAIN|LOG_PANIC, "failure to create DMARC history file: %s",
- dmarc_history_file);
- return DMARC_HIST_FILE_ERR;
- }
-
-/* Generate the contents of the history file */
-history_buffer = string_sprintf(
- "job %s\nreporter %s\nreceived %ld\nipaddr %s\nfrom %s\nmfrom %s\n",
- message_id, primary_hostname, time(NULL), sender_host_address,
- header_from_sender, expand_string(US"$sender_address_domain"));
-
-if (spf_response)
- history_buffer = string_sprintf("%sspf %d\n", history_buffer, dmarc_spf_ares_result);
- /* history_buffer = string_sprintf("%sspf -1\n", history_buffer); */
-
-history_buffer = string_sprintf(
- "%s%spdomain %s\npolicy %d\n",
- history_buffer, dkim_history_buffer, dmarc_used_domain, dmarc_policy);
-
-if ((rua = opendmarc_policy_fetch_rua(dmarc_pctx, NULL, 0, 1)))
- for (tmp_ans = 0; rua[tmp_ans]; tmp_ans++)
- history_buffer = string_sprintf("%srua %s\n", history_buffer, rua[tmp_ans]);
-else
- history_buffer = string_sprintf("%srua -\n", history_buffer);
-
-opendmarc_policy_fetch_pct(dmarc_pctx, &tmp_ans);
-history_buffer = string_sprintf("%spct %d\n", history_buffer, tmp_ans);
-
-opendmarc_policy_fetch_adkim(dmarc_pctx, &tmp_ans);
-history_buffer = string_sprintf("%sadkim %d\n", history_buffer, tmp_ans);
-
-opendmarc_policy_fetch_aspf(dmarc_pctx, &tmp_ans);
-history_buffer = string_sprintf("%saspf %d\n", history_buffer, tmp_ans);
-
-opendmarc_policy_fetch_p(dmarc_pctx, &tmp_ans);
-history_buffer = string_sprintf("%sp %d\n", history_buffer, tmp_ans);
-
-opendmarc_policy_fetch_sp(dmarc_pctx, &tmp_ans);
-history_buffer = string_sprintf("%ssp %d\n", history_buffer, tmp_ans);
-
-history_buffer = string_sprintf(
- "%salign_dkim %d\nalign_spf %d\naction %d\n",
- history_buffer, da, sa, action);
-
-/* Write the contents to the history file */
-DEBUG(D_receive)
- debug_printf("DMARC logging history data for opendmarc reporting%s\n",
- (host_checking || f.running_in_test_harness) ? " (not really)" : "");
-if (host_checking || f.running_in_test_harness)
- {
- DEBUG(D_receive)
- debug_printf("DMARC history data for debugging:\n%s", history_buffer);
- }
-else
- {
- written_len = write_to_fd_buf(history_file_fd,
- history_buffer,
- Ustrlen(history_buffer));
- if (written_len == 0)
- {
- log_write(0, LOG_MAIN|LOG_PANIC, "failure to write to DMARC history file: %s",
- dmarc_history_file);
- return DMARC_HIST_WRITE_ERR;
- }
- (void)close(history_file_fd);
- }
-return DMARC_HIST_OK;
-}
-
-
uschar *
dmarc_exim_expand_query(int what)
{
diff --git a/src/src/exim.c b/src/src/exim.c
index 83b5ef51f..4389a7154 100644
--- a/src/src/exim.c
+++ b/src/src/exim.c
@@ -227,18 +227,8 @@ int fd;
os_restarting_signal(sig, usr1_handler);
-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
- root, do the creation in an exim:exim subprocess. */
-
- int euid = geteuid();
- if (euid == exim_uid)
- fd = Uopen(process_log_path, O_CREAT|O_APPEND|O_WRONLY, LOG_MODE);
- else if (euid == root_uid)
- fd = log_create_as_exim(process_log_path);
- }
+if (!process_log_path) return;
+fd = log_open_as_exim(process_log_path);
/* If we are neither exim nor root, or if we failed to create the log file,
give up. There is not much useful we can do with errors, since we don't want
@@ -3054,8 +3044,16 @@ for (i = 1; i < argc; i++)
/* -oP <name>: set pid file path for daemon */
- else if (Ustrcmp(argrest, "P") == 0)
- override_pid_file_path = argv[++i];
+ else if (*argrest == 'P')
+ {
+ if (!f.running_in_test_harness && real_uid != root_uid && real_uid != exim_uid)
+ exim_fail("exim: only uid=%d or uid=%d can use -oP and -oPX "
+ "(uid=%d euid=%d | %d)\n",
+ root_uid, exim_uid, getuid(), geteuid(), real_uid);
+ if (Ustrcmp(argrest, "P") == 0) override_pid_file_path = argv[++i];
+ else if (Ustrcmp(argrest, "PX") == 0) delete_pid_file();
+ else badarg = TRUE;
+ }
/* -or <n>: set timeout for non-SMTP acceptance
-os <n>: set timeout for SMTP acceptance */
@@ -3664,6 +3662,9 @@ during readconf_main() some expansion takes place already. */
/* Store the initial cwd before we change directories. Can be NULL if the
dir has already been unlinked. */
initial_cwd = os_getcwd(NULL, 0);
+if (initial_cwd && strlen(CCS initial_cwd) >= BIG_BUFFER_SIZE) {
+ exim_fail("exim: initial cwd is far too long\n");
+}
/* checking:
-be[m] expansion test -
@@ -3950,11 +3951,9 @@ if ( (debug_selector & D_any || LOGGING(arguments))
p += 13;
else
{
- Ustrncpy(p + 4, initial_cwd, big_buffer_size-5);
- p += 4 + Ustrlen(initial_cwd);
- /* in case p is near the end and we don't provide enough space for
- * string_format to be willing to write. */
- *p = '\0';
+ p += 4;
+ snprintf(CS p, big_buffer_size - (p - big_buffer), "%s", CCS initial_cwd);
+ p += strlen(CCS p);
}
(void)string_format(p, big_buffer_size - (p - big_buffer), " %d args:", argc);
diff --git a/src/src/functions.h b/src/src/functions.h
index cab7a7363..366cb2f26 100644
--- a/src/src/functions.h
+++ b/src/src/functions.h
@@ -281,8 +281,7 @@ extern int ip_streamsocket(const uschar *, uschar **, int);
extern int ipv6_nmtoa(int *, uschar *);
extern uschar *local_part_quote(uschar *);
-extern int log_create(uschar *);
-extern int log_create_as_exim(uschar *);
+extern int log_open_as_exim(uschar *);
extern void log_close_all(void);
extern macro_item * macro_create(const uschar *, const uschar *, BOOL);
diff --git a/src/src/globals.c b/src/src/globals.c
index b3362a34c..894b8487b 100644
--- a/src/src/globals.c
+++ b/src/src/globals.c
@@ -247,6 +247,7 @@ struct global_flags f =
.authentication_local = FALSE,
.background_daemon = TRUE,
+ .bdat_readers_wanted = FALSE,
.chunking_offered = FALSE,
.config_changed = FALSE,
diff --git a/src/src/globals.h b/src/src/globals.h
index f71f104e2..58f7ae55f 100644
--- a/src/src/globals.h
+++ b/src/src/globals.h
@@ -173,6 +173,7 @@ extern struct global_flags {
BOOL authentication_local :1; /* TRUE if non-smtp (implicit authentication) */
BOOL background_daemon :1; /* Set FALSE to keep in foreground */
+ BOOL bdat_readers_wanted :1; /* BDAT-handling to be pushed on readfunc stack */
BOOL chunking_offered :1;
BOOL config_changed :1; /* True if -C used */
diff --git a/src/src/log.c b/src/src/log.c
index d08200044..15c88c13e 100644
--- a/src/src/log.c
+++ b/src/src/log.c
@@ -264,14 +264,19 @@ overwrite it temporarily if it is necessary to create the directory.
Returns: a file descriptor, or < 0 on failure (errno set)
*/
-int
-log_create(uschar *name)
+static int
+log_open_already_exim(uschar * const name)
{
-int fd = Uopen(name,
-#ifdef O_CLOEXEC
- O_CLOEXEC |
-#endif
- O_CREAT|O_APPEND|O_WRONLY, LOG_MODE);
+int fd = -1;
+const int flags = O_WRONLY | O_APPEND | O_CREAT | O_NONBLOCK;
+
+if (geteuid() != exim_uid)
+ {
+ errno = EACCES;
+ return -1;
+ }
+
+fd = Uopen(name, flags, LOG_MODE);
/* If creation failed, attempt to build a log directory in case that is the
problem. */
@@ -285,11 +290,7 @@ if (fd < 0 && errno == ENOENT)
DEBUG(D_any) debug_printf("%s log directory %s\n",
created ? "created" : "failed to create", name);
*lastslash = '/';
- if (created) fd = Uopen(name,
-#ifdef O_CLOEXEC
- O_CLOEXEC |
-#endif
- O_CREAT|O_APPEND|O_WRONLY, LOG_MODE);
+ if (created) fd = Uopen(name, flags, LOG_MODE);
}
return fd;
@@ -297,6 +298,81 @@ return fd;
+/* Inspired by OpenSSH's mm_send_fd(). Thanks! */
+
+static int
+log_send_fd(const int sock, const int fd)
+{
+struct msghdr msg;
+union {
+ struct cmsghdr hdr;
+ char buf[CMSG_SPACE(sizeof(int))];
+} cmsgbuf;
+struct cmsghdr *cmsg;
+struct iovec vec;
+char ch = 'A';
+ssize_t n;
+
+memset(&msg, 0, sizeof(msg));
+memset(&cmsgbuf, 0, sizeof(cmsgbuf));
+msg.msg_control = &cmsgbuf.buf;
+msg.msg_controllen = sizeof(cmsgbuf.buf);
+
+cmsg = CMSG_FIRSTHDR(&msg);
+cmsg->cmsg_len = CMSG_LEN(sizeof(int));
+cmsg->cmsg_level = SOL_SOCKET;
+cmsg->cmsg_type = SCM_RIGHTS;
+*(int *)CMSG_DATA(cmsg) = fd;
+
+vec.iov_base = &ch;
+vec.iov_len = 1;
+msg.msg_iov = &vec;
+msg.msg_iovlen = 1;
+
+while ((n = sendmsg(sock, &msg, 0)) == -1 && errno == EINTR);
+if (n != 1) return -1;
+return 0;
+}
+
+/* Inspired by OpenSSH's mm_receive_fd(). Thanks! */
+
+static int
+log_recv_fd(const int sock)
+{
+struct msghdr msg;
+union {
+ struct cmsghdr hdr;
+ char buf[CMSG_SPACE(sizeof(int))];
+} cmsgbuf;
+struct cmsghdr *cmsg;
+struct iovec vec;
+ssize_t n;
+char ch = '\0';
+int fd = -1;
+
+memset(&msg, 0, sizeof(msg));
+vec.iov_base = &ch;
+vec.iov_len = 1;
+msg.msg_iov = &vec;
+msg.msg_iovlen = 1;
+
+memset(&cmsgbuf, 0, sizeof(cmsgbuf));
+msg.msg_control = &cmsgbuf.buf;
+msg.msg_controllen = sizeof(cmsgbuf.buf);
+
+while ((n = recvmsg(sock, &msg, 0)) == -1 && errno == EINTR);
+if (n != 1 || ch != 'A') return -1;
+
+cmsg = CMSG_FIRSTHDR(&msg);
+if (cmsg == NULL) return -1;
+if (cmsg->cmsg_type != SCM_RIGHTS) return -1;
+fd = *(const int *)CMSG_DATA(cmsg);
+if (fd < 0) return -1;
+return fd;
+}
+
+
+
/*************************************************
* Create a log file as the exim user *
*************************************************/
@@ -312,41 +388,60 @@ Returns: a file descriptor, or < 0 on failure (errno set)
*/
int
-log_create_as_exim(uschar *name)
+log_open_as_exim(uschar * const name)
{
-pid_t pid = fork();
-int status = 1;
int fd = -1;
+const uid_t euid = geteuid();
-/* In the subprocess, change uid/gid and do the creation. Return 0 from the
-subprocess on success. If we don't check for setuid failures, then the file
-can be created as root, so vulnerabilities which cause setuid to fail mean
-that the Exim user can use symlinks to cause a file to be opened/created as
-root. We always open for append, so can't nuke existing content but it would
-still be Rather Bad. */
-
-if (pid == 0)
+if (euid == exim_uid)
{
- if (setgid(exim_gid) < 0)
- die(US"exim: setgid for log-file creation failed, aborting",
- US"Unexpected log failure, please try later");
- if (setuid(exim_uid) < 0)
- die(US"exim: setuid for log-file creation failed, aborting",
- US"Unexpected log failure, please try later");
- _exit((log_create(name) < 0)? 1 : 0);
+ fd = log_open_already_exim(name);
}
+else if (euid == root_uid)
+ {
+ int sock[2];
+ if (socketpair(AF_UNIX, SOCK_STREAM, 0, sock) == 0)
+ {
+ const pid_t pid = fork();
+ if (pid == 0)
+ {
+ (void)close(sock[0]);
+ if (setgroups(1, &exim_gid) != 0) _exit(EXIT_FAILURE);
+ if (setgid(exim_gid) != 0) _exit(EXIT_FAILURE);
+ if (setuid(exim_uid) != 0) _exit(EXIT_FAILURE);
+
+ if (getuid() != exim_uid || geteuid() != exim_uid) _exit(EXIT_FAILURE);
+ if (getgid() != exim_gid || getegid() != exim_gid) _exit(EXIT_FAILURE);
+
+ fd = log_open_already_exim(name);
+ if (fd < 0) _exit(EXIT_FAILURE);
+ if (log_send_fd(sock[1], fd) != 0) _exit(EXIT_FAILURE);
+ (void)close(sock[1]);
+ _exit(EXIT_SUCCESS);
+ }
-/* If we created a subprocess, wait for it. If it succeeded, try the open. */
-
-while (pid > 0 && waitpid(pid, &status, 0) != pid);
-if (status == 0) fd = Uopen(name,
-#ifdef O_CLOEXEC
- O_CLOEXEC |
-#endif
- O_APPEND|O_WRONLY, LOG_MODE);
+ (void)close(sock[1]);
+ if (pid > 0)
+ {
+ fd = log_recv_fd(sock[0]);
+ while (waitpid(pid, NULL, 0) == -1 && errno == EINTR);
+ }
+ (void)close(sock[0]);
+ }
+ }
-/* If we failed to create a subprocess, we are in a bad way. We return
-with fd still < 0, and errno set, letting the caller handle the error. */
+if (fd >= 0)
+ {
+ int flags;
+ flags = fcntl(fd, F_GETFD);
+ if (flags != -1) (void)fcntl(fd, F_SETFD, flags | FD_CLOEXEC);
+ flags = fcntl(fd, F_GETFL);
+ if (flags != -1) (void)fcntl(fd, F_SETFL, flags & ~O_NONBLOCK);
+ }
+else
+ {
+ errno = EACCES;
+ }
return fd;
}
@@ -459,52 +554,17 @@ if (!ok)
die(US"exim: log file path too long: aborting",
US"Logging failure; please try later");
-/* We now have the file name. Try to open an existing file. After a successful
-open, arrange for automatic closure on exec(), and then return. */
+/* We now have the file name. After a successful open, return. */
-*fd = Uopen(buffer,
-#ifdef O_CLOEXEC
- O_CLOEXEC |
-#endif
- O_APPEND|O_WRONLY, LOG_MODE);
+*fd = log_open_as_exim(buffer);
if (*fd >= 0)
{
-#ifndef O_CLOEXEC
- (void)fcntl(*fd, F_SETFD, fcntl(*fd, F_GETFD) | FD_CLOEXEC);
-#endif
return;
}
-/* Open was not successful: try creating the file. If this is a root process,
-we must do the creating in a subprocess set to exim:exim in order to ensure
-that the file is created with the right ownership. Otherwise, there can be a
-race if another Exim process is trying to write to the log at the same time.
-The use of SIGUSR1 by the exiwhat utility can provoke a lot of simultaneous
-writing. */
-
euid = geteuid();
-/* If we are already running as the Exim user (even if that user is root),
-we can go ahead and create in the current process. */
-
-if (euid == exim_uid) *fd = log_create(buffer);
-
-/* Otherwise, if we are root, do the creation in an exim:exim subprocess. If we
-are neither exim nor root, creation is not attempted. */
-
-else if (euid == root_uid) *fd = log_create_as_exim(buffer);
-
-/* If we now have an open file, set the close-on-exec flag and return. */
-
-if (*fd >= 0)
- {
-#ifndef O_CLOEXEC
- (void)fcntl(*fd, F_SETFD, fcntl(*fd, F_GETFD) | FD_CLOEXEC);
-#endif
- return;
- }
-
/* Creation failed. There are some circumstances in which we get here when
the effective uid is not root or exim, which is the problem. (For example, a
non-setuid binary with log_arguments set, called in certain ways.) Rather than
@@ -894,6 +954,7 @@ if (!(flags & (LOG_MAIN|LOG_PANIC|LOG_REJECT)))
if (f.disable_logging)
{
DEBUG(D_any) debug_printf("log writing disabled\n");
+ if ((flags & LOG_PANIC_DIE) == LOG_PANIC_DIE) exim_exit(EXIT_FAILURE, NULL);
return;
}
diff --git a/src/src/macros.h b/src/src/macros.h
index 0f93543ce..b3896b736 100644
--- a/src/src/macros.h
+++ b/src/src/macros.h
@@ -154,6 +154,7 @@ enough to hold all the headers from a normal kind of message. */
/* The size of the circular buffer that remembers recent SMTP commands */
#define SMTP_HBUFF_SIZE 20
+#define SMTP_HBUFF_PREV(n) ((n) ? (n)-1 : SMTP_HBUFF_SIZE-1)
/* The initial size of a big buffer for use in various places. It gets put
into big_buffer_size and in some circumstances increased. It should be at least
diff --git a/src/src/parse.c b/src/src/parse.c
index 4b0efa0e1..e1e2e7358 100644
--- a/src/src/parse.c
+++ b/src/src/parse.c
@@ -1149,9 +1149,12 @@ while (s < end)
{
if (ss >= end) ss--;
*t++ = '(';
- Ustrncpy(t, s, ss-s);
- t += ss-s;
- s = ss;
+ if (ss > s)
+ {
+ Ustrncpy(t, s, ss-s);
+ t += ss-s;
+ s = ss;
+ }
}
}
diff --git a/src/src/pdkim/pdkim.c b/src/src/pdkim/pdkim.c
index 594af03c5..512a3e352 100644
--- a/src/src/pdkim/pdkim.c
+++ b/src/src/pdkim/pdkim.c
@@ -107,7 +107,7 @@ pdkim_combined_canon_entry pdkim_combined_canons[] = {
};
-static blob lineending = {.data = US"\r\n", .len = 2};
+static const blob lineending = {.data = US"\r\n", .len = 2};
/* -------------------------------------------------------------------------- */
uschar *
@@ -719,9 +719,11 @@ return NULL;
If we have to relax the data for this sig, return our copy of it. */
static blob *
-pdkim_update_ctx_bodyhash(pdkim_bodyhash * b, blob * orig_data, blob * relaxed_data)
+pdkim_update_ctx_bodyhash(pdkim_bodyhash * b, const blob * orig_data, blob * relaxed_data)
{
-blob * canon_data = orig_data;
+const blob * canon_data = orig_data;
+size_t left;
+
/* Defaults to simple canon (no further treatment necessary) */
if (b->canon_method == PDKIM_CANON_RELAXED)
@@ -767,16 +769,17 @@ if (b->canon_method == PDKIM_CANON_RELAXED)
}
/* Make sure we don't exceed the to-be-signed body length */
+left = canon_data->len;
if ( b->bodylength >= 0
- && b->signed_body_bytes + (unsigned long)canon_data->len > b->bodylength
+ && left > (unsigned long)b->bodylength - b->signed_body_bytes
)
- canon_data->len = b->bodylength - b->signed_body_bytes;
+ left = (unsigned long)b->bodylength - b->signed_body_bytes;
-if (canon_data->len > 0)
+if (left > 0)
{
- exim_sha_update(&b->body_hash_ctx, CUS canon_data->data, canon_data->len);
- b->signed_body_bytes += canon_data->len;
- DEBUG(D_acl) pdkim_quoteprint(canon_data->data, canon_data->len);
+ exim_sha_update(&b->body_hash_ctx, CUS canon_data->data, left);
+ b->signed_body_bytes += left;
+ DEBUG(D_acl) pdkim_quoteprint(canon_data->data, left);
}
return relaxed_data;
@@ -825,7 +828,7 @@ for (sig = ctx->sig; sig; sig = sig->next)
/* VERIFICATION --------------------------------------------------------- */
/* Be careful that the header sig included a bodyash */
- if ( sig->bodyhash.data
+ if (sig->bodyhash.data && sig->bodyhash.len == b->bh.len
&& memcmp(b->bh.data, sig->bodyhash.data, b->bh.len) == 0)
{
DEBUG(D_acl) debug_printf("PDKIM [%s] Body hash compared OK\n", sig->domain);
@@ -1010,7 +1013,7 @@ else
last_sig->next = sig;
}
- if (--dkim_collect_input == 0)
+ if (dkim_collect_input && --dkim_collect_input == 0)
{
ctx->headers = pdkim_prepend_stringlist(ctx->headers, ctx->cur_header->s);
ctx->cur_header->s[ctx->cur_header->ptr = 0] = '\0';
@@ -1524,7 +1527,7 @@ for (sig = ctx->sig; sig; sig = sig->next)
do this hash incrementally.
We don't need the hash we're calculating here for the GnuTLS and OpenSSL
cases of RSA signing, since those library routines can do hash-and-sign.
-
+
Some time in the future we could easily avoid doing the hash here for those
cases (which will be common for a long while. We could also change from
the current copy-all-the-headers-into-one-block, then call the hash-and-sign
@@ -1779,7 +1782,7 @@ for (sig = ctx->sig; sig; sig = sig->next)
);
goto NEXT_VERIFY;
}
-
+
/* Make sure sig uses supported DKIM version (only v1) */
if (sig->version != 1)
{
diff --git a/src/src/queue.c b/src/src/queue.c
index 92109ef92..41af5b85e 100644
--- a/src/src/queue.c
+++ b/src/src/queue.c
@@ -416,12 +416,18 @@ if (!recurse)
p += sprintf(CS p, " -q%s", extras);
if (deliver_selectstring)
- p += sprintf(CS p, " -R%s %s", f.deliver_selectstring_regex? "r" : "",
- deliver_selectstring);
+ {
+ snprintf(CS p, big_buffer_size - (p - big_buffer), " -R%s %s",
+ f.deliver_selectstring_regex? "r" : "", deliver_selectstring);
+ p += strlen(CCS p);
+ }
if (deliver_selectstring_sender)
- p += sprintf(CS p, " -S%s %s", f.deliver_selectstring_sender_regex? "r" : "",
- deliver_selectstring_sender);
+ {
+ snprintf(CS p, big_buffer_size - (p - big_buffer), " -S%s %s",
+ f.deliver_selectstring_sender_regex? "r" : "", deliver_selectstring_sender);
+ p += strlen(CCS p);
+ }
log_detail = string_copy(big_buffer);
if (*queue_name)
diff --git a/src/src/rda.c b/src/src/rda.c
index 13f570928..c27e073a3 100644
--- a/src/src/rda.c
+++ b/src/src/rda.c
@@ -623,9 +623,13 @@ search_tidyup();
if ((pid = fork()) == 0)
{
header_line *waslast = header_last; /* Save last header */
+ int fd_flags = -1;
fd = pfd[pipe_write];
(void)close(pfd[pipe_read]);
+
+ if ((fd_flags = fcntl(fd, F_GETFD)) == -1) goto bad;
+ if (fcntl(fd, F_SETFD, fd_flags | FD_CLOEXEC) == -1) goto bad;
exim_setugid(ugid->uid, ugid->gid, FALSE, rname);
/* Addresses can get rewritten in filters; if we are not root or the exim
diff --git a/src/src/receive.c b/src/src/receive.c
index a0467e8c8..227ace084 100644
--- a/src/src/receive.c
+++ b/src/src/receive.c
@@ -488,6 +488,12 @@ if (recipients_count >= recipients_list_max)
{
recipient_item *oldlist = recipients_list;
int oldmax = recipients_list_max;
+
+ const int safe_recipients_limit = INT_MAX / 2 / sizeof(recipient_item);
+ if (recipients_list_max < 0 || recipients_list_max >= safe_recipients_limit)
+ {
+ log_write(0, LOG_MAIN|LOG_PANIC_DIE, "Too many recipients: %d", recipients_list_max);
+ }
recipients_list_max = recipients_list_max ? 2*recipients_list_max : 50;
recipients_list = store_get(recipients_list_max * sizeof(recipient_item));
if (oldlist != NULL)
@@ -4070,7 +4076,7 @@ if (message_logs && !blackholed_by)
{
int fd;
uschar * m_name = spool_fname(US"msglog", message_subdir, message_id, US"");
-
+
if ( (fd = Uopen(m_name, O_WRONLY|O_APPEND|O_CREAT, SPOOL_MODE)) < 0
&& errno == ENOENT
)
@@ -4229,7 +4235,7 @@ if(!smtp_reply)
if (f.deliver_freeze) log_write(0, LOG_MAIN, "frozen by %s", frozen_by);
if (f.queue_only_policy) log_write(L_delay_delivery, LOG_MAIN,
"no immediate delivery: queued%s%s by %s",
- *queue_name ? " in " : "", *queue_name ? CS queue_name : "",
+ *queue_name ? " in " : "", *queue_name ? CS queue_name : "",
queued_by);
}
f.receive_call_bombout = FALSE;
diff --git a/src/src/smtp_in.c b/src/src/smtp_in.c
index 86f87eae1..76784c15f 100644
--- a/src/src/smtp_in.c
+++ b/src/src/smtp_in.c
@@ -602,6 +602,10 @@ if (n > 0)
#endif
}
+/* Forward declarations */
+static inline void bdat_push_receive_functions(void);
+static inline void bdat_pop_receive_functions(void);
+
/* Get a byte from the smtp input, in CHUNKING mode. Handle ack of the
previous BDAT chunk and getting new ones when we run out. Uses the
@@ -634,9 +638,7 @@ for(;;)
if (chunking_data_left > 0)
return lwr_receive_getc(chunking_data_left--);
- receive_getc = lwr_receive_getc;
- receive_getbuf = lwr_receive_getbuf;
- receive_ungetc = lwr_receive_ungetc;
+ bdat_pop_receive_functions();
#ifndef DISABLE_DKIM
dkim_save = dkim_collect_input;
dkim_collect_input = 0;
@@ -740,9 +742,7 @@ next_cmd:
goto repeat_until_rset;
}
- receive_getc = bdat_getc;
- receive_getbuf = bdat_getbuf; /* r~getbuf is never actually used */
- receive_ungetc = bdat_ungetc;
+ bdat_push_receive_functions();
#ifndef DISABLE_DKIM
dkim_collect_input = dkim_save;
#endif
@@ -775,9 +775,7 @@ while (chunking_data_left)
if (!bdat_getbuf(&n)) break;
}
-receive_getc = lwr_receive_getc;
-receive_getbuf = lwr_receive_getbuf;
-receive_ungetc = lwr_receive_ungetc;
+bdat_pop_receive_functions();
if (chunking_state != CHUNKING_LAST)
{
@@ -787,6 +785,45 @@ if (chunking_state != CHUNKING_LAST)
}
+static inline void
+bdat_push_receive_functions(void)
+{
+/* push the current receive_* function on the "stack", and
+replace them by bdat_getc(), which in turn will use the lwr_receive_*
+functions to do the dirty work. */
+if (lwr_receive_getc == NULL)
+ {
+ lwr_receive_getc = receive_getc;
+ lwr_receive_getbuf = receive_getbuf;
+ lwr_receive_ungetc = receive_ungetc;
+ }
+else
+ {
+ DEBUG(D_receive) debug_printf("chunking double-push receive functions\n");
+ }
+
+receive_getc = bdat_getc;
+receive_getbuf = bdat_getbuf;
+receive_ungetc = bdat_ungetc;
+}
+
+static inline void
+bdat_pop_receive_functions(void)
+{
+if (lwr_receive_getc == NULL)
+ {
+ DEBUG(D_receive) debug_printf("chunking double-pop receive functions\n");
+ return;
+ }
+
+receive_getc = lwr_receive_getc;
+receive_getbuf = lwr_receive_getbuf;
+receive_ungetc = lwr_receive_ungetc;
+
+lwr_receive_getc = NULL;
+lwr_receive_getbuf = NULL;
+lwr_receive_ungetc = NULL;
+}
/*************************************************
@@ -805,6 +842,9 @@ Returns: the character
int
smtp_ungetc(int ch)
{
+if (smtp_inptr <= smtp_inbuffer)
+ log_write(0, LOG_MAIN|LOG_PANIC_DIE, "buffer underflow in smtp_ungetc");
+
*--smtp_inptr = ch;
return ch;
}
@@ -814,6 +854,7 @@ int
bdat_ungetc(int ch)
{
chunking_data_left++;
+bdat_push_receive_functions(); /* we're not done yet, calling push is safe, because it checks the state before pushing anything */
return lwr_receive_ungetc(ch);
}
@@ -1984,29 +2025,35 @@ static BOOL
extract_option(uschar **name, uschar **value)
{
uschar *n;
-uschar *v = smtp_cmd_data + Ustrlen(smtp_cmd_data) - 1;
-while (isspace(*v)) v--;
+uschar *v;
+if (Ustrlen(smtp_cmd_data) <= 0) return FALSE;
+v = smtp_cmd_data + Ustrlen(smtp_cmd_data) - 1;
+while (v > smtp_cmd_data && isspace(*v)) v--;
v[1] = 0;
+
while (v > smtp_cmd_data && *v != '=' && !isspace(*v))
{
/* Take care to not stop at a space embedded in a quoted local-part */
-
- if (*v == '"') do v--; while (*v != '"' && v > smtp_cmd_data+1);
+ if (*v == '"')
+ {
+ do v--; while (v > smtp_cmd_data && *v != '"');
+ if (v <= smtp_cmd_data) return FALSE;
+ }
v--;
}
+if (v <= smtp_cmd_data) return FALSE;
n = v;
if (*v == '=')
{
- while(isalpha(n[-1])) n--;
+ while (n > smtp_cmd_data && isalpha(n[-1])) n--;
/* RFC says SP, but TAB seen in wild and other major MTAs accept it */
- if (!isspace(n[-1])) return FALSE;
+ if (n <= smtp_cmd_data || !isspace(n[-1])) return FALSE;
n[-1] = 0;
}
else
{
n++;
- if (v == smtp_cmd_data) return FALSE;
}
*v++ = 0;
*name = n;
@@ -4984,6 +5031,8 @@ while (done <= 0)
case RCPT_CMD:
HAD(SCH_RCPT);
+ if (rcpt_count < 0 || rcpt_count >= INT_MAX/2)
+ log_write(0, LOG_MAIN|LOG_PANIC_DIE, "Too many recipients: %d", rcpt_count);
rcpt_count++;
was_rcpt = fl.rcpt_in_progress = TRUE;
@@ -5322,10 +5371,10 @@ while (done <= 0)
}
if (f.smtp_in_pipelining_advertised && last_was_rcpt)
smtp_printf("503 Valid RCPT command must precede %s\r\n", FALSE,
- smtp_names[smtp_connection_had[smtp_ch_index-1]]);
+ smtp_names[smtp_connection_had[SMTP_HBUFF_PREV(smtp_ch_index)]]);
else
done = synprot_error(L_smtp_protocol_error, 503, NULL,
- smtp_connection_had[smtp_ch_index-1] == SCH_DATA
+ smtp_connection_had[SMTP_HBUFF_PREV(smtp_ch_index)] == SCH_DATA
? US"valid RCPT command must precede DATA"
: US"valid RCPT command must precede BDAT");
diff --git a/src/src/smtp_out.c b/src/src/smtp_out.c
index b194e8043..c5f3a6a76 100644
--- a/src/src/smtp_out.c
+++ b/src/src/smtp_out.c
@@ -389,7 +389,7 @@ if (ob->socks_proxy)
{
int sock = socks_sock_connect(sc->host, sc->host_af, port, sc->interface,
sc->tblock, ob->connect_timeout);
-
+
if (sock >= 0)
{
if (early_data && early_data->data && early_data->len)
@@ -590,7 +590,7 @@ Arguments:
timeout the timeout to use when reading a packet
Returns: length of a line that has been put in the buffer
- -1 otherwise, with errno set
+ -1 otherwise, with errno set, and inblock->ptr adjusted
*/
static int
@@ -631,6 +631,7 @@ for (;;)
{
*p = 0; /* Leave malformed line for error message */
errno = ERRNO_SMTPFORMAT;
+ inblock->ptr = ptr;
return -1;
}
}
@@ -656,6 +657,7 @@ for (;;)
/* Get here if there has been some kind of recv() error; errno is set, but we
ensure that the result buffer is empty before returning. */
+inblock->ptr = inblock->ptrend = inblock->buffer;
*buffer = 0;
return -1;
}
diff --git a/src/src/spool_in.c b/src/src/spool_in.c
index 2d349778c..dbbcf23ee 100644
--- a/src/src/spool_in.c
+++ b/src/src/spool_in.c
@@ -307,6 +307,36 @@ dsn_ret = 0;
dsn_envid = NULL;
}
+static void *
+fgets_big_buffer(FILE *fp)
+{
+int len = 0;
+
+big_buffer[0] = 0;
+if (Ufgets(big_buffer, big_buffer_size, fp) == NULL) return NULL;
+
+while ((len = Ustrlen(big_buffer)) == big_buffer_size-1
+ && big_buffer[len-1] != '\n')
+ {
+ uschar *newbuffer;
+ int newsize;
+
+ if (big_buffer_size >= BIG_BUFFER_SIZE * 4) return NULL;
+ newsize = big_buffer_size * 2;
+ newbuffer = store_get_perm(newsize);
+ memcpy(newbuffer, big_buffer, len);
+
+ big_buffer = newbuffer;
+ big_buffer_size = newsize;
+ if (Ufgets(big_buffer + len, big_buffer_size - len, fp) == NULL) return NULL;
+ }
+
+if (len <= 0 || big_buffer[len-1] != '\n') return NULL;
+return big_buffer;
+}
+
+
+
/*************************************************
* Read spool header file *
@@ -450,21 +480,9 @@ p = big_buffer + 2;
for (;;)
{
int len;
- if (Ufgets(big_buffer, big_buffer_size, fp) == NULL) goto SPOOL_READ_ERROR;
+ if (fgets_big_buffer(fp) == NULL) goto SPOOL_READ_ERROR;
if (big_buffer[0] != '-') break;
- while ( (len = Ustrlen(big_buffer)) == big_buffer_size-1
- && big_buffer[len-1] != '\n'
- )
- { /* buffer not big enough for line; certs make this possible */
- uschar * buf;
- if (big_buffer_size >= BIG_BUFFER_SIZE*4) goto SPOOL_READ_ERROR;
- buf = store_get_perm(big_buffer_size *= 2);
- memcpy(buf, big_buffer, --len);
- big_buffer = buf;
- if (Ufgets(big_buffer+len, big_buffer_size-len, fp) == NULL)
- goto SPOOL_READ_ERROR;
- }
- big_buffer[len-1] = 0;
+ big_buffer[Ustrlen(big_buffer)-1] = 0;
switch(big_buffer[1])
{
@@ -724,7 +742,7 @@ DEBUG(D_deliver)
buffer. It contains the count of recipients which follow on separate lines.
Apply an arbitrary sanity check.*/
-if (Ufgets(big_buffer, big_buffer_size, fp) == NULL) goto SPOOL_READ_ERROR;
+if (fgets_big_buffer(fp) == NULL) goto SPOOL_READ_ERROR;
if (sscanf(CS big_buffer, "%d", &rcount) != 1 || rcount > 16384)
goto SPOOL_FORMAT_ERROR;
diff --git a/src/src/spool_out.c b/src/src/spool_out.c
index d55895202..9394393d5 100644
--- a/src/src/spool_out.c
+++ b/src/src/spool_out.c
@@ -108,6 +108,18 @@ return fd;
* Write the header spool file *
*************************************************/
+static const uschar *
+zap_newlines(const uschar *s)
+{
+uschar *z, *p;
+
+if (Ustrchr(s, '\n') == NULL) return s;
+
+p = z = string_copy(s);
+while ((p = Ustrchr(p, '\n')) != NULL) *p++ = ' ';
+return z;
+}
+
/* Returns the size of the file for success; zero for failure. The file is
written under a temporary name, and then renamed. It's done this way so that it
works with re-writing the file on message deferral as well as for the initial
@@ -210,7 +222,7 @@ if (body_zerocount > 0) fprintf(fp, "-body_zerocount %d\n", body_zerocount);
if (authenticated_id)
fprintf(fp, "-auth_id %s\n", authenticated_id);
if (authenticated_sender)
- fprintf(fp, "-auth_sender %s\n", authenticated_sender);
+ fprintf(fp, "-auth_sender %s\n", zap_newlines(authenticated_sender));
if (f.allow_unqualified_recipient) fprintf(fp, "-allow_unqualified_recipient\n");
if (f.allow_unqualified_sender) fprintf(fp, "-allow_unqualified_sender\n");
@@ -283,19 +295,20 @@ fprintf(fp, "%d\n", recipients_count);
for (i = 0; i < recipients_count; i++)
{
recipient_item *r = recipients_list + i;
+ const uschar *address = zap_newlines(r->address);
DEBUG(D_deliver) debug_printf("DSN: Flags :%d\n", r->dsn_flags);
if (r->pno < 0 && r->errors_to == NULL && r->dsn_flags == 0)
- fprintf(fp, "%s\n", r->address);
+ fprintf(fp, "%s\n", address);
else
{
- uschar * errors_to = r->errors_to ? r->errors_to : US"";
+ const uschar * errors_to = r->errors_to ? zap_newlines(r->errors_to) : US"";
/* for DSN SUPPORT extend exim 4 spool in a compatible way by
adding new values upfront and add flag 0x02 */
uschar * orcpt = r->orcpt ? r->orcpt : US"";
- fprintf(fp, "%s %s %d,%d %s %d,%d#3\n", r->address, orcpt, Ustrlen(orcpt),
+ fprintf(fp, "%s %s %d,%d %s %d,%d#3\n", address, orcpt, Ustrlen(orcpt),
r->dsn_flags, errors_to, Ustrlen(errors_to), r->pno);
}
diff --git a/src/src/store.c b/src/src/store.c
index b52799132..a2a80f631 100644
--- a/src/src/store.c
+++ b/src/src/store.c
@@ -128,6 +128,12 @@ Returns: pointer to store (panic on malloc failure)
void *
store_get_3(int size, const char *filename, int linenumber)
{
+if (size < 0 || size > INT_MAX/2)
+ {
+ log_write(0, LOG_MAIN|LOG_PANIC_DIE,
+ "bad memory allocation requested (%d bytes)",
+ size);
+ }
/* Round up the size to a multiple of the alignment. Although this looks a
messy statement, because "alignment" is a constant expression, the compiler can
do a reasonable job of optimizing, especially if the value of "alignment" is a
@@ -270,6 +276,13 @@ store_extend_3(void *ptr, int oldsize, int newsize, const char *filename,
int inc = newsize - oldsize;
int rounded_oldsize = oldsize;
+if (oldsize < 0 || newsize < oldsize || newsize >= INT_MAX/2)
+ {
+ log_write(0, LOG_MAIN|LOG_PANIC_DIE,
+ "bad memory extension requested (%d -> %d bytes)",
+ oldsize, newsize);
+ }
+
if (rounded_oldsize % alignment != 0)
rounded_oldsize += alignment - (rounded_oldsize % alignment);
@@ -508,7 +521,16 @@ store_newblock_3(void * block, int newsize, int len,
const char * filename, int linenumber)
{
BOOL release_ok = store_last_get[store_pool] == block;
-uschar * newtext = store_get(newsize);
+uschar * newtext;
+
+if (len < 0 || len > newsize)
+ {
+ log_write(0, LOG_MAIN|LOG_PANIC_DIE,
+ "bad memory extension requested (%d -> %d bytes)",
+ len, newsize);
+ }
+
+newtext = store_get(newsize);
memcpy(newtext, block, len);
if (release_ok) store_release_3(block, filename, linenumber);
@@ -539,6 +561,11 @@ store_malloc_3(int size, const char *filename, int linenumber)
{
void *yield;
+if (size < 0 || size >= INT_MAX/2)
+ log_write(0, LOG_MAIN|LOG_PANIC_DIE,
+ "bad memory allocation requested (%d bytes)",
+ size);
+
if (size < 16) size = 16;
if (!(yield = malloc((size_t)size)))
diff --git a/src/src/string.c b/src/src/string.c
index 3445f8a42..2cdbe7c75 100644
--- a/src/src/string.c
+++ b/src/src/string.c
@@ -1147,6 +1147,18 @@ To try to keep things reasonable, we use increments whose size depends on the
existing length of the string. */
unsigned inc = oldsize < 4096 ? 127 : 1023;
+
+if (g->ptr < 0 || g->ptr > g->size || g->size >= INT_MAX/2)
+ log_write(0, LOG_MAIN|LOG_PANIC_DIE,
+ "internal error in gstring_grow (ptr %d size %d)", g->ptr, g->size);
+
+if (count <= 0) return;
+
+if (count >= INT_MAX/2 - g->ptr)
+ log_write(0, LOG_MAIN|LOG_PANIC_DIE,
+ "internal error in gstring_grow (ptr %d count %d)", g->ptr, count);
+
+
g->size = ((p + count + inc) & ~inc) + 1;
/* Try to extend an existing allocation. If the result of calling
@@ -1194,6 +1206,10 @@ string_catn(gstring * g, const uschar *s, int count)
{
int p;
+if (count < 0)
+ log_write(0, LOG_MAIN|LOG_PANIC_DIE,
+ "internal error in string_catn (count %d)", count);
+
if (!g)
{
unsigned inc = count < 4096 ? 127 : 1023;
@@ -1201,8 +1217,13 @@ if (!g)
g = string_get(size);
}
+if (g->ptr < 0 || g->ptr > g->size)
+ log_write(0, LOG_MAIN|LOG_PANIC_DIE,
+ "internal error in string_catn (ptr %d size %d)", g->ptr, g->size);
+
p = g->ptr;
-if (p + count >= g->size)
+
+if (count >= g->size - p)
gstring_grow(g, p, count);
/* Because we always specify the exact number of characters to copy, we can
diff --git a/src/src/tls-openssl.c b/src/src/tls-openssl.c
index e751edd9a..2a8d4cabd 100644
--- a/src/src/tls-openssl.c
+++ b/src/src/tls-openssl.c
@@ -2910,16 +2910,12 @@ a store reset there, so use POOL_PERM. */
if (!ct_ctx && (more || corked))
{
-#ifdef EXPERIMENTAL_PIPE_CONNECT
int save_pool = store_pool;
store_pool = POOL_PERM;
-#endif
corked = string_catn(corked, buff, len);
-#ifdef EXPERIMENTAL_PIPE_CONNECT
store_pool = save_pool;
-#endif
if (more)
{
diff --git a/src/src/tls.c b/src/src/tls.c
index f79bc3193..2a316fe59 100644
--- a/src/src/tls.c
+++ b/src/src/tls.c
@@ -151,6 +151,9 @@ Returns: the character
int
tls_ungetc(int ch)
{
+if (ssl_xfer_buffer_lwm <= 0)
+ log_write(0, LOG_MAIN|LOG_PANIC_DIE, "buffer underflow in tls_ungetc");
+
ssl_xfer_buffer[--ssl_xfer_buffer_lwm] = ch;
return ch;
}
diff --git a/src/src/transports/smtp.c b/src/src/transports/smtp.c
index cc37e73f3..07b63a2aa 100644
--- a/src/src/transports/smtp.c
+++ b/src/src/transports/smtp.c
@@ -2328,8 +2328,8 @@ goto SEND_QUIT;
int n = sizeof(sx->buffer);
uschar * rsp = sx->buffer;
- if (sx->esmtp_sent && (n = Ustrlen(sx->buffer)) < sizeof(sx->buffer)/2)
- { rsp = sx->buffer + n + 1; n = sizeof(sx->buffer) - n; }
+ if (sx->esmtp_sent && (n = Ustrlen(sx->buffer) + 1) < sizeof(sx->buffer)/2)
+ { rsp = sx->buffer + n; n = sizeof(sx->buffer) - n; }
if (smtp_write_command(sx, SCMD_FLUSH, "HELO %s\r\n", sx->helo_data) < 0)
goto SEND_FAILED;
diff --git a/test/log/0900 b/test/log/0900
index cf02d983a..a0af7a41d 100644
--- a/test/log/0900
+++ b/test/log/0900
@@ -10,3 +10,4 @@
1999-03-02 09:44:33 10HmbB-0005vi-00 <= someone@some.domain H=(tester) [127.0.0.1] P=esmtp K S=sss for CALLER@test.ex
1999-03-02 09:44:33 H=(tester) [127.0.0.1] F=<someone@some.domain> rejected RCPT <dummy@reject.ex>: relay not permitted
1999-03-02 09:44:33 rejected from <someone@some.domain> H=(tester) [127.0.0.1]: Non-CRLF-terminated header, under CHUNKING: message abandoned
+1999-03-02 09:44:33 10HmbC-0005vi-00 <= someone@some.domain H=(tester) [127.0.0.1] P=esmtp K S=sss for CALLER@test.ex
diff --git a/test/scripts/0000-Basic/0900 b/test/scripts/0000-Basic/0900
index 4503ae0c0..1c0e43552 100644
--- a/test/scripts/0000-Basic/0900
+++ b/test/scripts/0000-Basic/0900
@@ -38,7 +38,7 @@ ehlo tester
??? 250-
??? 250-
??? 250-
-??? 250
+??? 250
mail from:someone@some.domain
??? 250
rcpt to:CALLER@test.ex
@@ -82,7 +82,7 @@ ehlo tester
??? 250-
??? 250-
??? 250-
-??? 250
+??? 250
mail from:someone@some.domain
??? 250
rcpt to:CALLER@test.ex
@@ -105,7 +105,7 @@ ehlo tester
??? 250-
??? 250-
??? 250-
-??? 250
+??? 250
mail from:someone@some.domain
??? 250
rcpt to:CALLER@test.ex
@@ -131,7 +131,7 @@ ehlo tester
??? 250-
??? 250-
??? 250-
-??? 250
+??? 250
mail from:someone@some.domain
??? 250
rcpt to:CALLER@test.ex
@@ -152,7 +152,7 @@ EHLO tester
??? 250-
??? 250-
??? 250-
-??? 250
+??? 250
mail from:someone@some.domain
??? 250
rcpt to:CALLER@test.ex
@@ -240,6 +240,32 @@ quit
??? 221
****
#
+# plain, small message (body w/o lineend)
+client 127.0.0.1 PORT_D
+??? 220
+ehlo tester
+??? 250-
+??? 250-SIZE
+??? 250-8BITMIME
+??? 250-PIPELINING
+??? 250-CHUNKING
+??? 250 HELP
+mail from:someone@some.domain
+??? 250
+rcpt to:CALLER@test.ex
+??? 250
+bdat 98 last
+To: Susan@random.com
+From: Sam@random.com
+Subject: This is a bodyfull test message
+
+>>> 1234567890
+??? 250-
+??? 250
+quit
+??? 221
+****
+#
#
killdaemon
no_msglog_check
diff --git a/test/stderr/0275 b/test/stderr/0275
index b4d4a037b..5354a5698 100644
--- a/test/stderr/0275
+++ b/test/stderr/0275
@@ -171,8 +171,7 @@ Delivery address list:
locked TESTSUITE/spool/db/retry.lockfile
EXIM_DBOPEN: file <TESTSUITE/spool/db/retry> dir <TESTSUITE/spool/db> flags=O_RDONLY
returned from EXIM_DBOPEN: (nil)
- ensuring TESTSUITE/spool/db/retry.lockfile is owned by exim
- failed to open DB file TESTSUITE/spool/db/retry.lockfile: No such file or directory
+ failed to open DB file TESTSUITE/spool/db/retry: No such file or directory
no retry data available
>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
Considering: userx@test.ex
diff --git a/test/stderr/0278 b/test/stderr/0278
index ab72a1aba..f71c630c0 100644
--- a/test/stderr/0278
+++ b/test/stderr/0278
@@ -130,8 +130,7 @@ Delivery address list:
locked TESTSUITE/spool/db/retry.lockfile
EXIM_DBOPEN: file <TESTSUITE/spool/db/retry> dir <TESTSUITE/spool/db> flags=O_RDONLY
returned from EXIM_DBOPEN: (nil)
- ensuring TESTSUITE/spool/db/retry.lockfile is owned by exim
- failed to open DB file TESTSUITE/spool/db/retry.lockfile: No such file or directory
+ failed to open DB file TESTSUITE/spool/db/retry: No such file or directory
no retry data available
>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
Considering: CALLER@test.ex
diff --git a/test/stderr/0386 b/test/stderr/0386
index 2f9b0e2a9..b141e618f 100644
--- a/test/stderr/0386
+++ b/test/stderr/0386
@@ -249,8 +249,7 @@ Delivery address list:
locked TESTSUITE/spool/db/retry.lockfile
EXIM_DBOPEN: file <TESTSUITE/spool/db/retry> dir <TESTSUITE/spool/db> flags=O_RDONLY
returned from EXIM_DBOPEN: (nil)
- ensuring TESTSUITE/spool/db/retry.lockfile is owned by exim
- failed to open DB file TESTSUITE/spool/db/retry.lockfile: No such file or directory
+ failed to open DB file TESTSUITE/spool/db/retry: No such file or directory
no retry data available
>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
Considering: 2@b
diff --git a/test/stderr/0388 b/test/stderr/0388
index de7f6e3c9..52c76a48b 100644
--- a/test/stderr/0388
+++ b/test/stderr/0388
@@ -9,8 +9,7 @@ set_process_info: pppp delivering 10HmaX-0005vi-00
locked TESTSUITE/spool/db/retry.lockfile
EXIM_DBOPEN: file <TESTSUITE/spool/db/retry> dir <TESTSUITE/spool/db> flags=O_RDONLY
returned from EXIM_DBOPEN: (nil)
- ensuring TESTSUITE/spool/db/retry.lockfile is owned by exim
- failed to open DB file TESTSUITE/spool/db/retry.lockfile: No such file or directory
+ failed to open DB file TESTSUITE/spool/db/retry: No such file or directory
no retry data available
>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
Considering: x@y
diff --git a/test/stderr/0397 b/test/stderr/0397
index 82f143787..4d9dcaa47 100644
--- a/test/stderr/0397
+++ b/test/stderr/0397
@@ -1,7 +1,7 @@
-1999-03-02 09:44:33 Cannot open main log file "/non/existent/path/to/force/failure/main": No such file or directory: euid=uuuu egid=EXIM_GID
+1999-03-02 09:44:33 Cannot open main log file "/non/existent/path/to/force/failure/main": Permission denied: euid=uuuu egid=EXIM_GID
1999-03-02 09:44:33 Start queue run: pid=pppp
-1999-03-02 09:44:33 Cannot open main log file "/non/existent/path/to/force/failure/main": No such file or directory: euid=uuuu egid=EXIM_GID
+1999-03-02 09:44:33 Cannot open main log file "/non/existent/path/to/force/failure/main": Permission denied: euid=uuuu egid=EXIM_GID
SYSLOG: '2017-07-30 18:51:05 Start queue run: pid=pppp'
-SYSLOG: '2017-07-30 18:51:05 Cannot open main log file "/non/existent/path/to/force/failure/main": No such file or directory: euid=uuuu egid=EXIM_GID'
+SYSLOG: '2017-07-30 18:51:05 Cannot open main log file "/non/existent/path/to/force/failure/main": Permission denied: euid=uuuu egid=EXIM_GID'
SYSLOG: 'exim: could not open panic log - aborting: see message(s) above'
exim: could not open panic log - aborting: see message(s) above
diff --git a/test/stderr/0402 b/test/stderr/0402
index 6c2e92211..9a6f7820d 100644
--- a/test/stderr/0402
+++ b/test/stderr/0402
@@ -199,8 +199,7 @@ Delivery address list:
locked TESTSUITE/spool/db/retry.lockfile
EXIM_DBOPEN: file <TESTSUITE/spool/db/retry> dir <TESTSUITE/spool/db> flags=O_RDONLY
returned from EXIM_DBOPEN: (nil)
- ensuring TESTSUITE/spool/db/retry.lockfile is owned by exim
- failed to open DB file TESTSUITE/spool/db/retry.lockfile: No such file or directory
+ failed to open DB file TESTSUITE/spool/db/retry: No such file or directory
no retry data available
>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
Considering: CALLER@test.ex
diff --git a/test/stderr/0403 b/test/stderr/0403
index dbdf77957..ca3ae14e5 100644
--- a/test/stderr/0403
+++ b/test/stderr/0403
@@ -72,8 +72,7 @@ Delivery address list:
locked TESTSUITE/spool/db/retry.lockfile
EXIM_DBOPEN: file <TESTSUITE/spool/db/retry> dir <TESTSUITE/spool/db> flags=O_RDONLY
returned from EXIM_DBOPEN: (nil)
- ensuring TESTSUITE/spool/db/retry.lockfile is owned by exim
- failed to open DB file TESTSUITE/spool/db/retry.lockfile: No such file or directory
+ failed to open DB file TESTSUITE/spool/db/retry: No such file or directory
no retry data available
>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
Considering: userx@test.ex
diff --git a/test/stderr/0404 b/test/stderr/0404
index 3fa39a137..7caadcb31 100644
--- a/test/stderr/0404
+++ b/test/stderr/0404
@@ -173,8 +173,7 @@ Delivery address list:
locked TESTSUITE/spool/db/retry.lockfile
EXIM_DBOPEN: file <TESTSUITE/spool/db/retry> dir <TESTSUITE/spool/db> flags=O_RDONLY
returned from EXIM_DBOPEN: (nil)
- ensuring TESTSUITE/spool/db/retry.lockfile is owned by exim
- failed to open DB file TESTSUITE/spool/db/retry.lockfile: No such file or directory
+ failed to open DB file TESTSUITE/spool/db/retry: No such file or directory
no retry data available
>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
Considering: userx@test.ex
diff --git a/test/stderr/0408 b/test/stderr/0408
index a2c15bbf6..1f5b9e297 100644
--- a/test/stderr/0408
+++ b/test/stderr/0408
@@ -72,8 +72,7 @@ Delivery address list:
locked TESTSUITE/spool/db/retry.lockfile
EXIM_DBOPEN: file <TESTSUITE/spool/db/retry> dir <TESTSUITE/spool/db> flags=O_RDONLY
returned from EXIM_DBOPEN: (nil)
- ensuring TESTSUITE/spool/db/retry.lockfile is owned by exim
- failed to open DB file TESTSUITE/spool/db/retry.lockfile: No such file or directory
+ failed to open DB file TESTSUITE/spool/db/retry: No such file or directory
no retry data available
>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
Considering: userx@test.ex
diff --git a/test/stderr/0487 b/test/stderr/0487
index 8eb7435d3..ec4ba0268 100644
--- a/test/stderr/0487
+++ b/test/stderr/0487
@@ -101,8 +101,7 @@ Delivery address list:
locked TESTSUITE/spool/db/retry.lockfile
EXIM_DBOPEN: file <TESTSUITE/spool/db/retry> dir <TESTSUITE/spool/db> flags=O_RDONLY
returned from EXIM_DBOPEN: (nil)
- ensuring TESTSUITE/spool/db/retry.lockfile is owned by exim
- failed to open DB file TESTSUITE/spool/db/retry.lockfile: No such file or directory
+ failed to open DB file TESTSUITE/spool/db/retry: No such file or directory
no retry data available
>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
Considering: userx@test.ex
diff --git a/test/stdout/0900 b/test/stdout/0900
index 72269fad9..ccd10f0a1 100644
--- a/test/stdout/0900
+++ b/test/stdout/0900
@@ -47,7 +47,7 @@ Connecting to 127.0.0.1 port 1225 ... connected
<<< 250-PIPELINING
??? 250-
<<< 250-CHUNKING
-??? 250
+??? 250
<<< 250 HELP
>>> mail from:someone@some.domain
??? 250
@@ -107,7 +107,7 @@ Connecting to 127.0.0.1 port 1225 ... connected
<<< 250-PIPELINING
??? 250-
<<< 250-CHUNKING
-??? 250
+??? 250
<<< 250 HELP
>>> mail from:someone@some.domain
??? 250
@@ -137,7 +137,7 @@ Connecting to 127.0.0.1 port 1225 ... connected
<<< 250-PIPELINING
??? 250-
<<< 250-CHUNKING
-??? 250
+??? 250
<<< 250 HELP
>>> mail from:someone@some.domain
??? 250
@@ -173,7 +173,7 @@ Connecting to 127.0.0.1 port 1225 ... connected
<<< 250-PIPELINING
??? 250-
<<< 250-CHUNKING
-??? 250
+??? 250
<<< 250 HELP
>>> mail from:someone@some.domain
??? 250
@@ -205,7 +205,7 @@ Connecting to 127.0.0.1 port 1225 ... connected
<<< 250-PIPELINING
??? 250-
<<< 250-CHUNKING
-??? 250
+??? 250
<<< 250 HELP
>>> mail from:someone@some.domain
??? 250
@@ -329,3 +329,39 @@ Connecting to 127.0.0.1 port 1225 ... connected
??? 221
<<< 221 testhost.test.ex closing connection
End of script
+Connecting to 127.0.0.1 port 1225 ... connected
+??? 220
+<<< 220 testhost.test.ex ESMTP Exim x.yz Tue, 2 Mar 1999 09:44:33 +0000
+>>> ehlo tester
+??? 250-
+<<< 250-testhost.test.ex Hello tester [127.0.0.1]
+??? 250-SIZE
+<<< 250-SIZE 52428800
+??? 250-8BITMIME
+<<< 250-8BITMIME
+??? 250-PIPELINING
+<<< 250-PIPELINING
+??? 250-CHUNKING
+<<< 250-CHUNKING
+??? 250 HELP
+<<< 250 HELP
+>>> mail from:someone@some.domain
+??? 250
+<<< 250 OK
+>>> rcpt to:CALLER@test.ex
+??? 250
+<<< 250 Accepted
+>>> bdat 98 last
+>>> To: Susan@random.com
+>>> From: Sam@random.com
+>>> Subject: This is a bodyfull test message
+>>>
+>>> 1234567890
+??? 250-
+<<< 250- 98 byte chunk, total 100
+??? 250
+<<< 250 OK id=10HmbC-0005vi-00
+>>> quit
+??? 221
+<<< 221 testhost.test.ex closing connection
+End of script