diff options
author | Heiko Schlittermann (HS12-RIPE) <hs@schlittermann.de> | 2021-05-01 12:04:41 +0200 |
---|---|---|
committer | Heiko Schlittermann (HS12-RIPE) <hs@schlittermann.de> | 2021-05-01 12:06:40 +0200 |
commit | b2070b15251231e4841499e8c599676308ce394a (patch) | |
tree | be26cbb4acb9d9f25086c0951b2833d6e164cd79 | |
parent | 54a3c57cd134d235e1daec3b6e2dcf3dc1b778ff (diff) | |
parent | 5220dc30120bd79319d465bd7a6e4b21a0881f9a (diff) | |
download | exim4-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
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 |