diff -urN ../exim-4.94.orig/OS/Makefile-Base ./OS/Makefile-Base --- ../exim-4.94.orig/OS/Makefile-Base 2020-05-30 23:35:38.000000000 +0300 +++ ./OS/Makefile-Base 2021-05-15 15:06:16.060315000 +0300 @@ -486,7 +486,7 @@ directory.o dns.o drtables.o enq.o exim.o expand.o filter.o \ filtertest.o globals.o dkim.o dkim_transport.o hash.o \ header.o host.o ip.o log.o lss.o match.o md5.o moan.o \ - os.o parse.o queue.o \ + os.o parse.o priv.o queue.o \ rda.o readconf.o receive.o retry.o rewrite.o rfc2047.o \ route.o search.o sieve.o smtp_in.o smtp_out.o spool_in.o spool_out.o \ std-crypto.o store.o string.o tls.o tod.o transport.o tree.o verify.o \ @@ -792,6 +792,7 @@ moan.o: $(HDRS) moan.c os.o: $(HDRS) $(OS_C_INCLUDES) os.c parse.o: $(HDRS) parse.c +priv.o: $(HDRS) priv.c queue.o: $(HDRS) queue.c rda.o: $(HDRS) rda.c readconf.o: $(HDRS) readconf.c diff -urN ../exim-4.94.orig/OS/os.h-GNU ./OS/os.h-GNU --- ../exim-4.94.orig/OS/os.h-GNU 2020-05-30 23:35:38.000000000 +0300 +++ ./OS/os.h-GNU 2021-05-15 15:06:16.061621000 +0300 @@ -1,6 +1,8 @@ /* Exim: OS-specific C header file for GNU/Hurd */ /* Copyright (c) The Exim Maintainers 2020 */ +#include + #define CRYPT_H #define GLIBC_IP_OPTIONS #define HAVE_BSD_GETLOADAVG @@ -24,5 +26,9 @@ /* setgroups(0, NULL) succeeds, and drops the gid group as well as any supplementary groups*/ #define OS_SETGROUPS_ZERO_DROPS_ALL + +#if _POSIX_C_SOURCE >= 200809L || _ATFILE_SOURCE +# define EXIM_HAVE_OPENAT +#endif /* End */ diff -urN ../exim-4.94.orig/README.UPDATING ./README.UPDATING --- ../exim-4.94.orig/README.UPDATING 2020-05-30 23:35:38.000000000 +0300 +++ ./README.UPDATING 2021-05-15 15:06:16.067204000 +0300 @@ -26,14 +26,32 @@ that might affect a running system. +Exim version 4.95 +----------------- + +Various length limits have been applied to Exim's parsing of its command-line. +These are all set to be at least as long as any valid input, so we do not believe +that any real use-cases have been affected by this. + +The names of various drivers (authenticators, routers, transports, ...) have +always been limited to 64 characters, but before this release the names were +silently truncated, inviting problems. Now the length limit should be enforced. +If this affects you, then please rename to use shorter names. + +The default maximum number of recipients of a single email has changed from +"unlimited" (ie: as much as CPU and memory will allow, until something breaks +badly) to 50,000. You can raise or lower this as you see fit, but we strongly +caution against using zero/unlimited. + + Exim version 4.94 ----------------- Some Transports now refuse to use tainted data in constructing their delivery location; this WILL BREAK configurations which are not updated accordingly. -In particular: any Transport use of $local_user which has been relying upon +In particular: any Transport use of $local_part which has been relying upon check_local_user far away in the Router to make it safe, should be updated to -replace $local_user with $local_part_data. +replace $local_part with $local_part_data. Attempting to remove, in router or transport, a header name that ends with an asterisk (which is a standards-legal name) will now result in all headers diff -urN ../exim-4.94.orig/exim_monitor/em_globals.c ./exim_monitor/em_globals.c --- ../exim-4.94.orig/exim_monitor/em_globals.c 2020-05-30 23:35:38.000000000 +0300 +++ ./exim_monitor/em_globals.c 2021-05-15 15:06:16.067998000 +0300 @@ -205,6 +205,7 @@ uschar *sender_fullhost = NULL; uschar *sender_helo_name = NULL; uschar *sender_host_address = NULL; +uschar *sender_host_auth_pubname = NULL; uschar *sender_host_authenticated = NULL; uschar *sender_host_name = NULL; int sender_host_port = 0; diff -urN ../exim-4.94.orig/exim_monitor/em_menu.c ./exim_monitor/em_menu.c --- ../exim-4.94.orig/exim_monitor/em_menu.c 2020-05-30 23:35:38.000000000 +0300 +++ ./exim_monitor/em_menu.c 2021-05-15 15:06:16.069154000 +0300 @@ -670,7 +670,7 @@ sprintf(CS big_buffer, "%s/input/%s", spool_directory, buffer); if (Ustat(big_buffer, &statbuf) == 0) text_showf(text, "Format error in spool file %s: size=%lu\n", buffer, - (ulong)statbuf.st_size); + (unsigned long)statbuf.st_size); else text_showf(text, "Format error in spool file %s\n", buffer); } else text_showf(text, "Read error for spool file %s\n", buffer); diff -urN ../exim-4.94.orig/exim_monitor/em_version.c ./exim_monitor/em_version.c --- ../exim-4.94.orig/exim_monitor/em_version.c 2020-05-30 23:35:38.000000000 +0300 +++ ./exim_monitor/em_version.c 2021-05-15 15:06:16.069697000 +0300 @@ -8,6 +8,17 @@ #define EM_VERSION_C +/* Needed by macros.h */ +/* Some systems have PATH_MAX and some have MAX_PATH_LEN. */ + +#ifndef PATH_MAX +# ifdef MAX_PATH_LEN +# define PATH_MAX MAX_PATH_LEN +# else +# define PATH_MAX 1024 +# endif +#endif + #include "mytypes.h" #include "store.h" #include "macros.h" diff -urN ../exim-4.94.orig/scripts/MakeLinks ./scripts/MakeLinks --- ../exim-4.94.orig/scripts/MakeLinks 2020-05-30 23:35:38.000000000 +0300 +++ ./scripts/MakeLinks 2021-05-15 15:06:16.070386000 +0300 @@ -103,7 +103,7 @@ deliver.c directory.c dns.c drtables.c dummies.c enq.c exim.c \ exim_dbmbuild.c exim_dbutil.c exim_lock.c expand.c filter.c filtertest.c \ globals.c hash.c header.c host.c ip.c log.c lss.c match.c md5.c moan.c \ - parse.c perl.c queue.c rda.c readconf.c receive.c retry.c rewrite.c \ + parse.c perl.c priv.c queue.c rda.c readconf.c receive.c retry.c rewrite.c \ rfc2047.c route.c search.c setenv.c environment.c \ sieve.c smtp_in.c smtp_out.c spool_in.c spool_out.c std-crypto.c store.c \ string.c tls.c tlscert-gnu.c tlscert-openssl.c tls-cipher-stdname.c \ diff -urN ../exim-4.94.orig/src/EDITME ./src/EDITME --- ../exim-4.94.orig/src/EDITME 2020-05-30 23:35:38.000000000 +0300 +++ ./src/EDITME 2021-05-15 15:06:16.071764000 +0300 @@ -564,9 +564,9 @@ # DISABLE_EVENT=yes -# Uncomment this line to include support for early pipelining, per +# Uncomment this line to remove support for early pipelining, per # https://datatracker.ietf.org/doc/draft-harris-early-pipe/ -# SUPPORT_PIPE_CONNECT=yes +# DISABLE_PIPE_CONNECT=yes #------------------------------------------------------------------------------ diff -urN ../exim-4.94.orig/src/acl.c ./src/acl.c --- ../exim-4.94.orig/src/acl.c 2020-05-30 23:35:38.000000000 +0300 +++ ./src/acl.c 2021-05-15 15:06:16.072907000 +0300 @@ -480,7 +480,7 @@ { US"no_delay_flush", FALSE, ACL_BIT_NOTSMTP | ACL_BIT_NOTSMTP_START }, - + [CONTROL_NO_ENFORCE_SYNC] = { US"no_enforce_sync", FALSE, ACL_BIT_NOTSMTP | ACL_BIT_NOTSMTP_START @@ -738,7 +738,7 @@ int v, c; BOOL negated = FALSE; uschar *saveline = s; - uschar name[64]; + uschar name[EXIM_DRIVERNAME_MAX]; /* Conditions (but not verbs) are allowed to be negated by an initial exclamation mark. */ @@ -1767,7 +1767,7 @@ /* Remaining items are optional; they apply to sender and recipient verification, including "header sender" verification. */ -while ((ss = string_nextinlist(&list, &sep, big_buffer, big_buffer_size))) +while ((ss = string_nextinlist(&list, &sep, NULL, 0))) { if (strcmpic(ss, US"defer_ok") == 0) defer_ok = TRUE; else if (strcmpic(ss, US"no_details") == 0) no_details = TRUE; @@ -1804,7 +1804,7 @@ uschar * opt; while (isspace(*sublist)) sublist++; - while ((opt = string_nextinlist(&sublist, &optsep, buffer, sizeof(buffer)))) + while ((opt = string_nextinlist(&sublist, &optsep, NULL, 0))) { callout_opt_t * op; double period = 1.0F; @@ -3199,8 +3199,7 @@ { const uschar *pp = p + 6; while (*pp) pp++; - submission_name = string_copy(parse_fix_phrase(p+6, pp-p-6, - big_buffer, big_buffer_size)); + submission_name = parse_fix_phrase(p+6, pp-p-6); p = pp; } else break; @@ -3264,37 +3263,41 @@ the case where both sides handle prdr and this-node prdr acl is "accept" */ ignored = US"PRDR active"; + else if (f.deliver_freeze) + ignored = US"frozen"; + else if (f.queue_only_policy) + ignored = US"queue-only"; + else if (fake_response == FAIL) + ignored = US"fakereject"; + else if (rcpt_count != 1) + ignored = US"nonfirst rcpt"; + else if (cutthrough.delivery) + ignored = US"repeated"; + else if (cutthrough.callout_hold_only) + { + DEBUG(D_acl) + debug_printf_indent(" cutthrough request upgrades callout hold\n"); + cutthrough.callout_hold_only = FALSE; + cutthrough.delivery = TRUE; /* control accepted */ + } else { - if (f.deliver_freeze) - ignored = US"frozen"; - else if (f.queue_only_policy) - ignored = US"queue-only"; - else if (fake_response == FAIL) - ignored = US"fakereject"; - else + cutthrough.delivery = TRUE; /* control accepted */ + while (*p == '/') { - if (rcpt_count == 1) + const uschar * pp = p+1; + if (Ustrncmp(pp, "defer=", 6) == 0) { - cutthrough.delivery = TRUE; /* control accepted */ - while (*p == '/') - { - const uschar * pp = p+1; - if (Ustrncmp(pp, "defer=", 6) == 0) - { - pp += 6; - if (Ustrncmp(pp, "pass", 4) == 0) cutthrough.defer_pass = TRUE; - /* else if (Ustrncmp(pp, "spool") == 0) ; default */ - } - else - while (*pp && *pp != '/') pp++; - p = pp; - } + pp += 6; + if (Ustrncmp(pp, "pass", 4) == 0) cutthrough.defer_pass = TRUE; + /* else if (Ustrncmp(pp, "spool") == 0) ; default */ } else - ignored = US"nonfirst rcpt"; + while (*pp && *pp != '/') pp++; + p = pp; } } + DEBUG(D_acl) if (ignored) debug_printf(" cutthrough request ignored on %s item\n", ignored); } @@ -3349,11 +3352,11 @@ { /* Separate the regular expression and any optional parameters. */ const uschar * list = arg; - uschar *ss = string_nextinlist(&list, &sep, big_buffer, big_buffer_size); + uschar *ss = string_nextinlist(&list, &sep, NULL, 0); /* Run the dcc backend. */ rc = dcc_process(&ss); /* Modify return code based upon the existence of options. */ - while ((ss = string_nextinlist(&list, &sep, big_buffer, big_buffer_size))) + while ((ss = string_nextinlist(&list, &sep, NULL, 0))) if (strcmpic(ss, US"defer_ok") == 0 && rc == DEFER) rc = FAIL; /* FAIL so that the message is passed to the next ACL */ } @@ -3435,14 +3438,14 @@ case ACLC_DKIM_SIGNER: if (dkim_cur_signer) rc = match_isinlist(dkim_cur_signer, - &arg,0,NULL,NULL,MCL_STRING,TRUE,NULL); + &arg, 0, NULL, NULL, MCL_STRING, TRUE, NULL); else rc = FAIL; break; case ACLC_DKIM_STATUS: rc = match_isinlist(dkim_verify_status, - &arg,0,NULL,NULL,MCL_STRING,TRUE,NULL); + &arg, 0, NULL, NULL, MCL_STRING, TRUE, NULL); break; #endif @@ -3454,7 +3457,7 @@ /* used long way of dmarc_exim_expand_query() in case we need more * view into the process in the future. */ rc = match_isinlist(dmarc_exim_expand_query(DMARC_VERIFY_STATUS), - &arg,0,NULL,NULL,MCL_STRING,TRUE,NULL); + &arg, 0, NULL, NULL, MCL_STRING, TRUE, NULL); break; #endif @@ -3514,7 +3517,7 @@ int sep = 0; const uschar *s = arg; uschar * ss; - while ((ss = string_nextinlist(&s, &sep, big_buffer, big_buffer_size))) + while ((ss = string_nextinlist(&s, &sep, NULL, 0))) { if (Ustrcmp(ss, "main") == 0) logbits |= LOG_MAIN; else if (Ustrcmp(ss, "panic") == 0) logbits |= LOG_PANIC; @@ -3567,7 +3570,7 @@ { /* Separate the regular expression and any optional parameters. */ const uschar * list = arg; - uschar * ss = string_nextinlist(&list, &sep, big_buffer, big_buffer_size); + uschar * ss = string_nextinlist(&list, &sep, NULL, 0); uschar * opt; BOOL defer_ok = FALSE; int timeout = 0; @@ -3672,11 +3675,11 @@ { /* Separate the regular expression and any optional parameters. */ const uschar * list = arg; - uschar *ss = string_nextinlist(&list, &sep, big_buffer, big_buffer_size); + uschar *ss = string_nextinlist(&list, &sep, NULL, 0); rc = spam(CUSS &ss); /* Modify return code based upon the existence of options. */ - while ((ss = string_nextinlist(&list, &sep, big_buffer, big_buffer_size))) + while ((ss = string_nextinlist(&list, &sep, NULL, 0))) if (strcmpic(ss, US"defer_ok") == 0 && rc == DEFER) rc = FAIL; /* FAIL so that the message is passed to the next ACL */ } @@ -4470,7 +4473,8 @@ /* 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 -urN ../exim-4.94.orig/src/auths/call_pam.c ./src/auths/call_pam.c --- ../exim-4.94.orig/src/auths/call_pam.c 2020-05-30 23:35:38.000000000 +0300 +++ ./src/auths/call_pam.c 2021-05-15 15:06:16.074333000 +0300 @@ -83,8 +83,7 @@ { case PAM_PROMPT_ECHO_ON: case PAM_PROMPT_ECHO_OFF: - arg = string_nextinlist(&pam_args, &sep, big_buffer, big_buffer_size); - if (!arg) + if (!(arg = string_nextinlist(&pam_args, &sep, NULL, 0))) { arg = US""; pam_arg_ended = TRUE; @@ -155,7 +154,7 @@ fail. PAM doesn't support authentication with an empty user (it prompts for it, causing a potential mis-interpretation). */ -user = string_nextinlist(&pam_args, &sep, big_buffer, big_buffer_size); +user = string_nextinlist(&pam_args, &sep, NULL, 0); if (user == NULL || user[0] == 0) return FAIL; /* Start off PAM interaction */ diff -urN ../exim-4.94.orig/src/auths/call_radius.c ./src/auths/call_radius.c --- ../exim-4.94.orig/src/auths/call_radius.c 2020-05-30 23:35:38.000000000 +0300 +++ ./src/auths/call_radius.c 2021-05-15 15:06:16.074516000 +0300 @@ -96,8 +96,7 @@ #endif -user = string_nextinlist(&radius_args, &sep, big_buffer, big_buffer_size); -if (!user) user = US""; +if (!(user = string_nextinlist(&radius_args, &sep, NULL, 0))) user = US""; DEBUG(D_auth) debug_printf("Running RADIUS authentication for user \"%s\" " "and \"%s\"\n", user, radius_args); diff -urN ../exim-4.94.orig/src/auths/get_data.c ./src/auths/get_data.c --- ../exim-4.94.orig/src/auths/get_data.c 2020-05-30 23:35:38.000000000 +0300 +++ ./src/auths/get_data.c 2021-05-15 15:06:16.075693000 +0300 @@ -168,14 +168,20 @@ len = Ustrlen(ss); /* The character ^ is used as an escape for a binary zero character, which is -needed for the PLAIN mechanism. It must be doubled if really needed. */ +needed for the PLAIN mechanism. It must be doubled if really needed. +The parsing ambiguity of ^^^ is taken as ^^ -> ^ ; ^ -> NUL - and there is +no way to get a leading ^ after a NUL. We would need to intro new syntax to +support that (probably preferring to take a more-standard exim list as a source +and concat the elements with intervening NULs. Either a magic marker on the +source string for client_send, or a new option). */ + for (int i = 0; i < len; i++) if (ss[i] == '^') if (ss[i+1] != '^') ss[i] = 0; else - if (--len > ++i) memmove(ss + i, ss + i + 1, len - i); + if (--len > i+1) memmove(ss + i + 1, ss + i + 2, len - i); /* The first string is attached to the AUTH command; others are sent unembellished. */ diff -urN ../exim-4.94.orig/src/configure.default ./src/configure.default --- ../exim-4.94.orig/src/configure.default 2020-05-30 23:35:38.000000000 +0300 +++ ./src/configure.default 2021-05-15 15:06:16.078738000 +0300 @@ -449,6 +449,20 @@ require verify = sender + # Reject all RCPT commands after too many bad recipients + # This is partly a defense against spam abuse and partly attacker abuse. + # Real senders should manage, by the time they get to 10 RCPT directives, + # to have had at least half of them be real addresses. + # + # This is a lightweight check and can protect you against repeated + # invocations of more heavy-weight checks which would come after it. + + deny condition = ${if and {\ + {>{$rcpt_count}{10}}\ + {<{$recipients_count}{${eval:$rcpt_count/2}}} }} + message = Rejected for too many bad recipients + logwrite = REJECT [$sender_host_address]: bad recipient count high [${eval:$rcpt_count-$recipients_count}] + # Accept if the message comes from one of the hosts for which we are an # outgoing relay. It is assumed that such hosts are most likely to be MUAs, # so we set control=submission to make Exim treat the message as a diff -urN ../exim-4.94.orig/src/daemon.c ./src/daemon.c --- ../exim-4.94.orig/src/daemon.c 2020-05-30 23:35:38.000000000 +0300 +++ ./src/daemon.c 2021-05-15 15:06:16.080436000 +0300 @@ -130,11 +130,30 @@ /************************************************* *************************************************/ +#ifndef EXIM_HAVE_ABSTRACT_UNIX_SOCKETS static void +unlink_notifier_socket(void) +{ +uschar * s = expand_string(notifier_socket); +DEBUG(D_any) debug_printf("unlinking notifier socket %s\n", s); +Uunlink(s); +} +#endif + + +static void close_daemon_sockets(int daemon_notifier_fd, int * listen_sockets, int listen_socket_count) { -if (daemon_notifier_fd >= 0) (void) close(daemon_notifier_fd); +if (daemon_notifier_fd >= 0) + { + (void) close(daemon_notifier_fd); + daemon_notifier_fd = -1; +#ifndef EXIM_HAVE_ABSTRACT_UNIX_SOCKETS + unlink_notifier_socket(); +#endif + } + for (int i = 0; i < listen_socket_count; i++) (void) close(listen_sockets[i]); } @@ -336,6 +355,7 @@ log_write(L_connection_reject, LOG_MAIN, "Connection from %s refused: too many connections " "from that IP address", whofrom->s); + search_tidyup(); goto ERROR_RETURN; } } @@ -454,6 +474,7 @@ signal(SIGCHLD, SIG_IGN); #endif signal(SIGTERM, SIG_DFL); + signal(SIGINT, SIG_DFL); /* Attempt to get an id from the sending machine via the RFC 1413 protocol. We do this in the sub-process in order not to hold up the @@ -680,6 +701,7 @@ signal(SIGHUP, SIG_DFL); signal(SIGCHLD, SIG_DFL); signal(SIGTERM, SIG_DFL); + signal(SIGINT, SIG_DFL); if (geteuid() != root_uid && !deliver_drop_privilege) { @@ -913,7 +935,6 @@ } - static void set_pid_file_path(void) { @@ -922,38 +943,150 @@ 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); } -/* Remove the daemon's pidfile. Note: runs with root privilege, -as a direct child of the daemon. Does not return. */ +enum pid_op { PID_WRITE, PID_CHECK, PID_DELETE }; -void -delete_pid_file(void) +/* 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) { -uschar * daemon_pid = string_sprintf("%d\n", (int)getppid()); -FILE * f; +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 = Ufopen(pid_file_path, "rb"))) +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) { - if ( fgets(CS big_buffer, big_buffer_size, f) - && Ustrcmp(daemon_pid, big_buffer) == 0 - ) - if (Uunlink(pid_file_path) == 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) { - DEBUG(D_any) - debug_printf("%s unlink: %s\n", pid_file_path, strerror(errno)); - } - else - DEBUG(D_any) - debug_printf("unlinked %s\n", pid_file_path); - fclose(f); + 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 - DEBUG(D_any) - debug_printf("%s\n", string_open_failed(errno, "pid file %s", - pid_file_path)); + { + 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); } @@ -966,16 +1099,17 @@ { int pid; +DEBUG(D_any) debug_printf("SIGTERM/SIGINT seen\n"); +#if !defined(DISABLE_TLS) && (defined(EXIM_HAVE_INOTIFY) || defined(EXIM_HAVE_KEVENT)) +tls_watch_invalidate(); +#endif + if (daemon_notifier_fd >= 0) { close(daemon_notifier_fd); daemon_notifier_fd = -1; #ifndef EXIM_HAVE_ABSTRACT_UNIX_SOCKETS - { - uschar * s = expand_string(notifier_socket); - DEBUG(D_any) debug_printf("unlinking notifier socket %s\n", s); - Uunlink(s); - } + unlink_notifier_socket(); #endif } @@ -1036,7 +1170,7 @@ DEBUG(D_any) debug_printf(" @%s\n", sa_un.sun_path+1); #else /* filesystem-visible and persistent; will neeed removal */ len = offsetof(struct sockaddr_un, sun_path) - + snprintf(sa_un.sun_path, sizeof(sa_un.sun_path), "%s", + + snprintf(sa_un.sun_path, sizeof(sa_un.sun_path), "%s", expand_string(notifier_socket)); DEBUG(D_any) debug_printf(" %s\n", sa_un.sun_path); #endif @@ -1813,23 +1947,14 @@ if (f.running_in_test_harness || write_pid) { - FILE *f; - - set_pid_file_path(); - 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); @@ -1866,6 +1991,7 @@ sigterm_seen = FALSE; os_non_restarting_signal(SIGTERM, main_sigterm_handler); +os_non_restarting_signal(SIGINT, main_sigterm_handler); /* If we are to run the queue periodically, pretend the alarm has just gone off. This will cause the first queue-runner to get kicked off straight away. */ @@ -2150,6 +2276,7 @@ signal(SIGHUP, SIG_DFL); signal(SIGCHLD, SIG_DFL); signal(SIGTERM, SIG_DFL); + signal(SIGINT, SIG_DFL); /* Re-exec if privilege has been given up, unless deliver_drop_ privilege is set. Reset SIGALRM before exec(). */ @@ -2369,27 +2496,23 @@ accept_retry_errno = errno; accept_retry_select_failed = select_failed; } - else - { - if (errno != accept_retry_errno || - select_failed != accept_retry_select_failed || - accept_retry_count >= 50) + else if ( errno != accept_retry_errno + || select_failed != accept_retry_select_failed + || accept_retry_count >= 50) { - log_write(0, LOG_MAIN | ((accept_retry_count >= 50)? LOG_PANIC : 0), + log_write(0, LOG_MAIN | (accept_retry_count >= 50? LOG_PANIC : 0), "%d %s() failure%s: %s", accept_retry_count, accept_retry_select_failed? "select" : "accept", - (accept_retry_count == 1)? "" : "s", + accept_retry_count == 1 ? "" : "s", strerror(accept_retry_errno)); log_close_all(); accept_retry_count = 0; accept_retry_errno = errno; accept_retry_select_failed = select_failed; } - } accept_retry_count++; } - else { if (accept_retry_count > 0) diff -urN ../exim-4.94.orig/src/dbfn.c ./src/dbfn.c --- ../exim-4.94.orig/src/dbfn.c 2020-05-30 23:35:38.000000000 +0300 +++ ./src/dbfn.c 2021-05-15 15:06:16.081228000 +0300 @@ -58,8 +58,6 @@ #endif - - /************************************************* * Open and lock a database file * *************************************************/ @@ -91,7 +89,6 @@ { int rc, save_errno; BOOL read_only = flags == O_RDONLY; -BOOL created = FALSE; flock_t lock_data; uschar dirname[256], filename[256]; @@ -113,12 +110,13 @@ 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, panic); dbblock->lockfd = Uopen(filename, O_RDWR|O_CREAT, EXIMDB_LOCKFILE_MODE); } +priv_restore(); if (dbblock->lockfd < 0) { @@ -167,60 +165,17 @@ 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; - uschar *lastname = Ustrrchr(filename, '/') + 1; - int namelen = Ustrlen(name); - - *lastname = 0; - - if ((dd = exim_opendir(filename))) - for (struct dirent *ent; ent = readdir(dd); ) - if (Ustrncmp(ent->d_name, name, namelen) == 0) - { - struct stat statbuf; - /* Filenames from readdir() are trusted, - so use a taint-nonchecking copy */ - strcpy(CS lastname, CCS 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 (exim_chown(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 -urN ../exim-4.94.orig/src/debug.c ./src/debug.c --- ../exim-4.94.orig/src/debug.c 2020-05-30 23:35:38.000000000 +0300 +++ ./src/debug.c 2021-05-15 15:06:16.082034000 +0300 @@ -30,10 +30,19 @@ [UNEXPECTED] = US"UNEXPECTED", [CANCELLED] = US"CANCELLED", [FAIL_SEND] = US"FAIL_SEND", - [FAIL_DROP] = US"FAIL_DROP" + [FAIL_DROP] = US"FAIL_DROP", + [DANE] = US"DANE", }; +const uschar * dns_rc_names[] = { + [DNS_SUCCEED] = US"DNS_SUCCEED", + [DNS_NOMATCH] = US"DNS_NOMATCH", + [DNS_NODATA] = US"DNS_NODATA", + [DNS_AGAIN] = US"DNS_AGAIN", + [DNS_FAIL] = US"DNS_FAIL", +}; + /************************************************* * Print tree * *************************************************/ @@ -328,48 +337,52 @@ gstring * g = NULL; int val; socklen_t vlen = sizeof(val); - struct sockaddr a; + struct sockaddr_storage a; socklen_t alen = sizeof(a); struct sockaddr_in * sinp = (struct sockaddr_in *)&a; struct sockaddr_in6 * sin6p = (struct sockaddr_in6 *)&a; - struct sockaddr_un * sa_unp ; (struct sockaddr_un *)&a; + struct sockaddr_un * sunp = (struct sockaddr_un *)&a; - if (getsockname(fd, &a, &alen) == 0) - switch (sinp->sin_family) + if (getsockname(fd, (struct sockaddr*)&a, &alen) == 0) + switch (a.ss_family) { case AF_INET: - g = string_cat(g, US" domain AF_INET"); + g = string_cat(g, US"domain AF_INET"); g = string_fmt_append(g, " lcl [%s]:%u", inet_ntoa(sinp->sin_addr), ntohs(sinp->sin_port)); - if (getpeername(fd, &a, &alen) == 0) + alen = sizeof(*sinp); + if (getpeername(fd, sinp, &alen) == 0) g = string_fmt_append(g, " rmt [%s]:%u", inet_ntoa(sinp->sin_addr), ntohs(sinp->sin_port)); break; case AF_INET6: { uschar buf[46]; - g = string_cat(g, US" domain AF_INET6"); + g = string_cat(g, US"domain AF_INET6"); g = string_fmt_append(g, " lcl [%s]:%u", inet_ntop(AF_INET6, &sin6p->sin6_addr, CS buf, sizeof(buf)), ntohs(sin6p->sin6_port)); - if (getpeername(fd, &a, &alen) == 0) + alen = sizeof(*sin6p); + if (getpeername(fd, sin6p, &alen) == 0) g = string_fmt_append(g, " rmt [%s]:%u", inet_ntop(AF_INET6, &sin6p->sin6_addr, CS buf, sizeof(buf)), ntohs(sin6p->sin6_port)); break; } case AF_UNIX: - g = string_cat(g, US" domain AF_UNIX"); - g = string_fmt_append(g, " lcl %s%s", - sa_unp->sun_path[0] ? US"" : US"@", - sa_unp->sun_path[0] ? sa_unp->sun_path : sa_unp->sun_path+1); - if (getpeername(fd, &a, &alen) == 0) - g = string_fmt_append(g, " rmt %s%s", - sa_unp->sun_path[0] ? US"" : US"@", - sa_unp->sun_path[0] ? sa_unp->sun_path : sa_unp->sun_path+1); - break; + g = string_cat(g, US"domain AF_UNIX"); + if (alen > sizeof(sa_family_t)) /* not unix(7) "unnamed socket" */ + g = string_fmt_append(g, " lcl %s%s", + sunp->sun_path[0] ? US"" : US"@", + sunp->sun_path[0] ? sunp->sun_path : sunp->sun_path+1); + alen = sizeof(*sunp); + if (getpeername(fd, sunp, &alen) == 0) + g = string_fmt_append(g, " rmt %s%s", + sunp->sun_path[0] ? US"" : US"@", + sunp->sun_path[0] ? sunp->sun_path : sunp->sun_path+1); + break; default: - g = string_fmt_append(g, " domain %u", sinp->sin_family); + g = string_fmt_append(g, "domain %u", sinp->sin_family); break; } if (getsockopt(fd, SOL_SOCKET, SO_TYPE, &val, &vlen) == 0) @@ -384,7 +397,7 @@ { struct protoent * p = getprotobynumber(val); g = p - ? string_fmt_append(g, " proto %s\n", p->p_name) + ? string_fmt_append(g, " proto %s", p->p_name) : string_fmt_append(g, " proto %d", val); } #endif diff -urN ../exim-4.94.orig/src/deliver.c ./src/deliver.c --- ../exim-4.94.orig/src/deliver.c 2020-05-30 23:35:38.000000000 +0300 +++ ./src/deliver.c 2021-05-15 15:06:16.084088000 +0300 @@ -334,7 +334,7 @@ open_msglog_file(uschar *filename, int mode, uschar **error) { if (Ustrstr(filename, US"/../")) - log_write(0, LOG_MAIN|LOG_PANIC, + log_write(0, LOG_MAIN|LOG_PANIC_DIE, "Attempt to open msglog file path with upward-traversal: '%s'\n", filename); for (int i = 2; i > 0; i--) @@ -460,6 +460,9 @@ This enables Exim to use a single SMTP transaction for sending to two entirely different domains that happen to end up pointing at the same hosts. +We do not try to batch up different A-record host names that refer to the +same IP. + Arguments: one points to the first host list two points to the second host list @@ -1192,7 +1195,7 @@ if (addr->host_used) { g = d_hostlog(g, addr); - if (continue_sequence > 1) + if (continue_sequence > 1) /*XXX this is wrong for a dropped proxyconn. Would have to pass back from transport */ g = string_catn(g, US"*", 1); #ifndef DISABLE_EVENT @@ -2093,9 +2096,9 @@ /* Each local delivery is performed in a separate process which sets its uid and gid as specified. This is a safer way than simply changing and -restoring using seteuid(); there is a body of opinion that seteuid() cannot be -used safely. From release 4, Exim no longer makes any use of it. Besides, not -all systems have seteuid(). +restoring using seteuid(); there is a body of opinion that seteuid() +cannot be used safely. From release 4, Exim no longer makes any use of +it for delivery. Besides, not all systems have seteuid(). If the uid/gid are specified in the transport_instance, they are used; the transport initialization must ensure that either both or neither are set. @@ -4272,6 +4275,10 @@ } } +/*XXX need to defeat this when DANE is used - but we don't know that yet. +So look out for the place it gets used. +*/ + /* Get the flag which specifies whether the transport can handle different domains that nevertheless resolve to the same set of hosts. If it needs expanding, get variables set: $address_data, $domain_data, $localpart_data, @@ -4350,6 +4357,11 @@ /************************************************************************/ +/*XXX don't know yet if DANE will be used. So tpt will have to +check at the point if gets next addr from list, and skip/defer any +nonmatch domains +*/ + /* Pick off all addresses which have the same transport, errors address, destination, and extra headers. In some cases they point to the same host list, but we also need to check for identical host lists generated from @@ -4496,6 +4508,7 @@ if (continue_transport) { BOOL ok = Ustrcmp(continue_transport, tp->name) == 0; +/*XXX do we need to check for a DANEd conn vs. a change of domain? */ /* If the transport is about to override the host list do not check it here but take the cost of running the transport process to discover @@ -7066,10 +7079,20 @@ /* If this is a run to continue deliveries to an external channel that is -already set up, defer any local deliveries. */ +already set up, defer any local deliveries. -if (continue_transport) +jgh 2020/12/20: I don't see why; locals should be quick. +The defer goes back to version 1.62 in 1997. A local being still deliverable +during a continued run might result from something like a defer during the +original delivery, eg. in a DB lookup. Unlikely but possible. + +To avoid delaying a local when combined with a callout-hold for a remote +delivery, test continue_sequence rather than continue_transport. */ + +if (continue_sequence > 1 && addr_local) { + DEBUG(D_deliver|D_retry|D_route) + debug_printf("deferring local deliveries due to continued-transport\n"); if (addr_defer) { address_item *addr = addr_defer; diff -urN ../exim-4.94.orig/src/dmarc.c ./src/dmarc.c --- ../exim-4.94.orig/src/dmarc.c 2020-05-30 23:35:38.000000000 +0300 +++ ./src/dmarc.c 2021-05-15 15:06:16.084926000 +0300 @@ -223,6 +223,96 @@ } +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. */ @@ -513,94 +603,6 @@ 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 -urN ../exim-4.94.orig/src/dns.c ./src/dns.c --- ../exim-4.94.orig/src/dns.c 2020-05-30 23:35:38.000000000 +0300 +++ ./src/dns.c 2021-05-15 15:06:16.085434000 +0300 @@ -516,7 +516,7 @@ || !(trusted = expand_string(dns_trust_aa)) || !*trusted || !(auth_name = dns_extract_auth_name(dnsa)) - || OK != match_isinlist(auth_name, &trusted, 0, NULL, NULL, + || OK != match_isinlist(auth_name, &trusted, 0, &domainlist_anchor, NULL, MCL_DOMAIN, TRUE, NULL) ) return FALSE; @@ -908,8 +908,8 @@ #ifndef STAND_ALONE save_domain = deliver_domain; deliver_domain = string_copy(name); /* set $domain */ - rc = match_isinlist(name, (const uschar **)&dns_again_means_nonexist, 0, NULL, NULL, - MCL_DOMAIN, TRUE, NULL); + rc = match_isinlist(name, CUSS &dns_again_means_nonexist, 0, + &domainlist_anchor, NULL, MCL_DOMAIN, TRUE, NULL); deliver_domain = save_domain; if (rc != OK) { diff -urN ../exim-4.94.orig/src/exim.c ./src/exim.c --- ../exim-4.94.orig/src/exim.c 2020-05-30 23:35:38.000000000 +0300 +++ ./src/exim.c 2021-05-15 15:06:16.087709000 +0300 @@ -233,19 +233,9 @@ 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. */ +if (!process_log_path) return; +fd = log_open_as_exim(process_log_path); - 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 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 to disrupt whatever is going on outside the signal handler. */ @@ -384,14 +374,20 @@ *************************************************/ #ifdef _POSIX_MONOTONIC_CLOCK -/* Amount CLOCK_MONOTONIC is behind realtime, at startup. */ +# ifdef CLOCK_BOOTTIME +# define EXIM_CLOCKTYPE CLOCK_BOOTTIME +# else +# define EXIM_CLOCKTYPE CLOCK_MONOTONIC +# endif + +/* Amount EXIM_CLOCK is behind realtime, at startup. */ static struct timespec offset_ts; static void exim_clock_init(void) { struct timeval tv; -if (clock_gettime(CLOCK_MONOTONIC, &offset_ts) != 0) return; +if (clock_gettime(EXIM_CLOCKTYPE, &offset_ts) != 0) return; (void)gettimeofday(&tv, NULL); offset_ts.tv_sec = tv.tv_sec - offset_ts.tv_sec; offset_ts.tv_nsec = tv.tv_usec * 1000 - offset_ts.tv_nsec; @@ -402,6 +398,29 @@ #endif +void +exim_gettime(struct timeval * tv) +{ +#ifdef _POSIX_MONOTONIC_CLOCK +struct timespec now_ts; + +if (clock_gettime(EXIM_CLOCKTYPE, &now_ts) == 0) + { + now_ts.tv_sec += offset_ts.tv_sec; + if ((now_ts.tv_nsec += offset_ts.tv_nsec) >= 1000*1000*1000) + { + now_ts.tv_sec++; + now_ts.tv_nsec -= 1000*1000*1000; + } + tv->tv_sec = now_ts.tv_sec; + tv->tv_usec = now_ts.tv_nsec / 1000; + } +else +#endif + (void)gettimeofday(tv, NULL); +} + + /* Exim uses a time + a pid to generate a unique identifier in two places: its message IDs, and in file names for maildir deliveries. Because some OS now re-use pids within the same second, sub-second times are now being used. @@ -428,29 +447,10 @@ struct timeval now_tv; long int now_true_usec; -#ifdef _POSIX_MONOTONIC_CLOCK -struct timespec now_ts; +exim_gettime(&now_tv); +now_true_usec = now_tv.tv_usec; +now_tv.tv_usec = (now_true_usec/resolution) * resolution; -if (clock_gettime(CLOCK_MONOTONIC, &now_ts) == 0) - { - now_ts.tv_sec += offset_ts.tv_sec; - if ((now_ts.tv_nsec += offset_ts.tv_nsec) >= 1000*1000*1000) - { - now_ts.tv_sec++; - now_ts.tv_nsec -= 1000*1000*1000; - } - now_tv.tv_sec = now_ts.tv_sec; - now_true_usec = (now_ts.tv_nsec / (resolution * 1000)) * resolution; - now_tv.tv_usec = now_true_usec; - } -else -#endif - { - (void)gettimeofday(&now_tv, NULL); - now_true_usec = now_tv.tv_usec; - now_tv.tv_usec = (now_true_usec/resolution) * resolution; - } - while (exim_tvcmp(&now_tv, tgt_tv) <= 0) { struct itimerval itval; @@ -751,6 +751,25 @@ exit(EXIT_FAILURE); } +/* fail if a length is too long */ +static inline void +exim_len_fail_toolong(int itemlen, int maxlen, const char *description) +{ +if (itemlen <= maxlen) + return; +fprintf(stderr, "exim: length limit exceeded (%d > %d) for: %s\n", + itemlen, maxlen, description); +exit(EXIT_FAILURE); +} + +/* only pass through the string item back to the caller if it's short enough */ +static inline const uschar * +exim_str_fail_toolong(const uschar *item, int maxlen, const char *description) +{ +exim_len_fail_toolong(Ustrlen(item), maxlen, description); +return item; +} + /* exim_chown_failure() called from exim_chown()/exim_fchown() on failure of chown()/fchown(). See src/functions.h for more explanation */ int @@ -1525,10 +1544,11 @@ */ static void -expansion_test_line(uschar * line) +expansion_test_line(const uschar * line) { int len; BOOL dummy_macexp; +uschar * s; Ustrncpy(big_buffer, line, big_buffer_size); big_buffer[big_buffer_size-1] = '\0'; @@ -1542,7 +1562,7 @@ printf("Defined macro '%s'\n", mlast->name); } else - if ((line = expand_string(big_buffer))) printf("%s\n", CS line); + if ((s = expand_string(big_buffer))) printf("%s\n", CS s); else printf("Failed: %s\n", expand_string_message); } @@ -1625,10 +1645,10 @@ uschar *start_queue_run_id = NULL; uschar *stop_queue_run_id = NULL; uschar *expansion_test_message = NULL; -uschar *ftest_domain = NULL; -uschar *ftest_localpart = NULL; -uschar *ftest_prefix = NULL; -uschar *ftest_suffix = NULL; +const uschar *ftest_domain = NULL; +const uschar *ftest_localpart = NULL; +const uschar *ftest_prefix = NULL; +const uschar *ftest_suffix = NULL; uschar *log_oneline = NULL; uschar *malware_test_file = NULL; uschar *real_sender_address; @@ -1721,6 +1741,9 @@ if (f.running_in_test_harness) debug_store = TRUE; +/* Protect against abusive argv[0] */ +exim_str_fail_toolong(argv[0], PATH_MAX, "argv[0]"); + /* The C standard says that the equivalent of setlocale(LC_ALL, "C") is obeyed at the start of a program; however, it seems that some environments do not follow this. A "strange" locale can affect the formatting of timestamps, so we @@ -1728,7 +1751,7 @@ setlocale(LC_ALL, "C"); -/* Get the offset between CLOCK_MONOTONIC and wallclock */ +/* Get the offset between CLOCK_MONOTONIC/CLOCK_BOOTTIME and wallclock */ #ifdef _POSIX_MONOTONIC_CLOCK exim_clock_init(); @@ -2122,10 +2145,10 @@ { if (++i >= argc) exim_fail("exim: string expected after %s\n", arg); - if (Ustrcmp(argrest, "d") == 0) ftest_domain = argv[i]; - else if (Ustrcmp(argrest, "l") == 0) ftest_localpart = argv[i]; - else if (Ustrcmp(argrest, "p") == 0) ftest_prefix = argv[i]; - else if (Ustrcmp(argrest, "s") == 0) ftest_suffix = argv[i]; + if (Ustrcmp(argrest, "d") == 0) ftest_domain = exim_str_fail_toolong(argv[i], EXIM_DOMAINNAME_MAX, "-bfd"); + else if (Ustrcmp(argrest, "l") == 0) ftest_localpart = exim_str_fail_toolong(argv[i], EXIM_LOCALPART_MAX, "-bfl"); + else if (Ustrcmp(argrest, "p") == 0) ftest_prefix = exim_str_fail_toolong(argv[i], EXIM_LOCALPART_MAX, "-bfp"); + else if (Ustrcmp(argrest, "s") == 0) ftest_suffix = exim_str_fail_toolong(argv[i], EXIM_LOCALPART_MAX, "-bfs"); else badarg = TRUE; } break; @@ -2135,7 +2158,7 @@ if (!*argrest || Ustrcmp(argrest, "c") == 0) { if (++i >= argc) { badarg = TRUE; break; } - sender_host_address = string_copy_taint(argv[i], TRUE); + sender_host_address = string_copy_taint(exim_str_fail_toolong(argv[i], EXIM_IPADDR_MAX, "-bh"), TRUE); host_checking = checking = f.log_testing_mode = TRUE; f.host_checking_callout = *argrest == 'c'; message_logs = FALSE; @@ -2148,7 +2171,7 @@ concept of *the* alias file, but since Sun's YP make script calls sendmail this way, some support must be provided. */ case 'i': - if (!*++argrest) bi_option = TRUE; + if (!*argrest) bi_option = TRUE; else badarg = TRUE; break; @@ -2592,7 +2615,7 @@ case 'F': if (!*argrest) if (++i < argc) argrest = argv[i]; else { badarg = TRUE; break; } - originator_name = string_copy_taint(argrest, TRUE); + originator_name = string_copy_taint(exim_str_fail_toolong(argrest, EXIM_HUMANNAME_MAX, "-F"), TRUE); f.sender_name_forced = TRUE; break; @@ -2618,6 +2641,7 @@ uschar *errmess; if (!*argrest) if (i+1 < argc) argrest = argv[++i]; else { badarg = TRUE; break; } + (void) exim_str_fail_toolong(argrest, EXIM_DISPLAYMAIL_MAX, "-f"); if (!*argrest) *(sender_address = store_get(1, FALSE)) = '\0'; /* Ensure writeable memory */ else @@ -2714,9 +2738,9 @@ if (msg_action_arg >= 0) exim_fail("exim: incompatible arguments\n"); - continue_transport = string_copy_taint(argv[++i], TRUE); - continue_hostname = string_copy_taint(argv[++i], TRUE); - continue_host_address = string_copy_taint(argv[++i], TRUE); + continue_transport = string_copy_taint(exim_str_fail_toolong(argv[++i], EXIM_DRIVERNAME_MAX, "-C internal transport"), TRUE); + continue_hostname = string_copy_taint(exim_str_fail_toolong(argv[++i], EXIM_HOSTNAME_MAX, "-C internal hostname"), TRUE); + continue_host_address = string_copy_taint(exim_str_fail_toolong(argv[++i], EXIM_IPADDR_MAX, "-C internal hostaddr"), TRUE); continue_sequence = Uatoi(argv[++i]); msg_action = MSG_DELIVER; msg_action_arg = ++i; @@ -2761,13 +2785,15 @@ /* -MCd: for debug, set a process-purpose string */ case 'd': if (++i < argc) - process_purpose = string_copy_taint(argv[i], TRUE); + process_purpose = string_copy_taint(exim_str_fail_toolong(argv[i], EXIM_DRIVERNAME_MAX, "-MCd"), TRUE); else badarg = TRUE; break; - /* -MCG: set the queue name, to a non-default value */ + /* -MCG: set the queue name, to a non-default value. Arguably, anything + from the commandline should be tainted - but we will need an untainted + value for the spoolfile when doing a -odi delivery process. */ - case 'G': if (++i < argc) queue_name = string_copy_taint(argv[i], TRUE); + case 'G': if (++i < argc) queue_name = string_copy_taint(exim_str_fail_toolong(argv[i], EXIM_DRIVERNAME_MAX, "-MCG"), FALSE); else badarg = TRUE; break; @@ -2796,19 +2822,31 @@ case 'S': smtp_peer_options |= OPTION_SIZE; break; #ifndef DISABLE_TLS + /* -MCs: used with -MCt; SNI was sent */ + /* -MCr: ditto, DANE */ + + case 'r': + case 's': if (++i < argc) + { + continue_proxy_sni = string_copy_taint(exim_str_fail_toolong(argv[i], EXIM_HOSTNAME_MAX, "-MCr/-MCs"), TRUE); + if (argrest[1] == 'r') continue_proxy_dane = TRUE; + } + else badarg = TRUE; + break; + /* -MCt: similar to -MCT below but the connection is still open via a proxy process which handles the TLS context and coding. Require three arguments for the proxied local address and port, - and the TLS cipher. */ + and the TLS cipher. */ case 't': if (++i < argc) - sending_ip_address = string_copy_taint(argv[i], TRUE); + sending_ip_address = string_copy_taint(exim_str_fail_toolong(argv[i], EXIM_IPADDR_MAX, "-MCt IP"), TRUE); else badarg = TRUE; if (++i < argc) sending_port = (int)(Uatol(argv[i])); else badarg = TRUE; if (++i < argc) - continue_proxy_cipher = string_copy_taint(argv[i], TRUE); + continue_proxy_cipher = string_copy_taint(exim_str_fail_toolong(argv[i], EXIM_CIPHERNAME_MAX, "-MCt cipher"), TRUE); else badarg = TRUE; /*FALLTHROUGH*/ @@ -2835,6 +2873,7 @@ following options which are followed by a single message id, and which act on that message. Some of them use the "recipient" addresses as well. -Mar add recipient(s) + -MG move to a different queue -Mmad mark all recipients delivered -Mmd mark recipients(s) delivered -Mes edit sender @@ -2870,7 +2909,7 @@ else if (Ustrcmp(argrest, "G") == 0) { msg_action = MSG_SETQUEUE; - queue_name_dest = string_copy_taint(argv[++i], TRUE); + queue_name_dest = string_copy_taint(exim_str_fail_toolong(argv[++i], EXIM_DRIVERNAME_MAX, "-MG"), TRUE); } else if (Ustrcmp(argrest, "mad") == 0) { @@ -3083,27 +3122,27 @@ /* -oMa: Set sender host address */ if (Ustrcmp(argrest, "a") == 0) - sender_host_address = string_copy_taint(argv[++i], TRUE); + sender_host_address = string_copy_taint(exim_str_fail_toolong(argv[++i], EXIM_IPADDR_MAX, "-oMa"), TRUE); /* -oMaa: Set authenticator name */ else if (Ustrcmp(argrest, "aa") == 0) - sender_host_authenticated = string_copy_taint(argv[++i], TRUE); + sender_host_authenticated = string_copy_taint(exim_str_fail_toolong(argv[++i], EXIM_DRIVERNAME_MAX, "-oMaa"), TRUE); /* -oMas: setting authenticated sender */ else if (Ustrcmp(argrest, "as") == 0) - authenticated_sender = string_copy_taint(argv[++i], TRUE); + authenticated_sender = string_copy_taint(exim_str_fail_toolong(argv[++i], EXIM_EMAILADDR_MAX, "-oMas"), TRUE); /* -oMai: setting authenticated id */ else if (Ustrcmp(argrest, "ai") == 0) - authenticated_id = string_copy_taint(argv[++i], TRUE); + authenticated_id = string_copy_taint(exim_str_fail_toolong(argv[++i], EXIM_EMAILADDR_MAX, "-oMas"), TRUE); /* -oMi: Set incoming interface address */ else if (Ustrcmp(argrest, "i") == 0) - interface_address = string_copy_taint(argv[++i], TRUE); + interface_address = string_copy_taint(exim_str_fail_toolong(argv[++i], EXIM_IPADDR_MAX, "-oMi"), TRUE); /* -oMm: Message reference */ @@ -3123,19 +3162,19 @@ if (received_protocol) exim_fail("received_protocol is set already\n"); else - received_protocol = string_copy_taint(argv[++i], TRUE); + received_protocol = string_copy_taint(exim_str_fail_toolong(argv[++i], EXIM_DRIVERNAME_MAX, "-oMr"), TRUE); /* -oMs: Set sender host name */ else if (Ustrcmp(argrest, "s") == 0) - sender_host_name = string_copy_taint(argv[++i], TRUE); + sender_host_name = string_copy_taint(exim_str_fail_toolong(argv[++i], EXIM_HOSTNAME_MAX, "-oMs"), TRUE); /* -oMt: Set sender ident */ else if (Ustrcmp(argrest, "t") == 0) { sender_ident_set = TRUE; - sender_ident = string_copy_taint(argv[++i], TRUE); + sender_ident = string_copy_taint(exim_str_fail_toolong(argv[++i], EXIM_IDENTUSER_MAX, "-oMt"), TRUE); } /* Else a bad argument */ @@ -3160,6 +3199,10 @@ -oPX: delete pid file of daemon */ case '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 (!*argrest) override_pid_file_path = argv[++i]; else if (Ustrcmp(argrest, "X") == 0) delete_pid_file(); else badarg = TRUE; @@ -3185,10 +3228,11 @@ break; /* -oX : Override local_interfaces and/or default daemon ports */ + /* Limits: Is there a real limit we want here? 1024 is very arbitrary. */ case 'X': if (*argrest) badarg = TRUE; - else override_local_interfaces = string_copy_taint(argv[++i], TRUE); + else override_local_interfaces = string_copy_taint(exim_str_fail_toolong(argv[++i], 1024, "-oX"), TRUE); break; /* Unknown -o argument */ @@ -3229,9 +3273,10 @@ exim_fail("received_protocol is set already\n"); if (!hn) - received_protocol = string_copy_taint(argrest, TRUE); + received_protocol = string_copy_taint(exim_str_fail_toolong(argrest, EXIM_DRIVERNAME_MAX, "-p"), TRUE); else { + (void) exim_str_fail_toolong(argrest, (EXIM_DRIVERNAME_MAX+1+EXIM_HOSTNAME_MAX), "-p:"); received_protocol = string_copyn_taint(argrest, hn - argrest, TRUE); sender_host_name = string_copy_taint(hn + 1, TRUE); } @@ -3287,6 +3332,7 @@ { int i; for (argrest++, i = 0; argrest[i] && argrest[i] != '/'; ) i++; + exim_len_fail_toolong(i, EXIM_DRIVERNAME_MAX, "-q*G"); queue_name = string_copyn(argrest, i); argrest += i; if (*argrest == '/') argrest++; @@ -3316,8 +3362,11 @@ case 'R': /* Synonymous with -qR... */ - receiving_message = FALSE; + { + const uschar *tainted_selectstr; + receiving_message = FALSE; + /* -Rf: As -R (below) but force all deliveries, -Rff: Ditto, but also thaw all frozen messages, -Rr: String is regex @@ -3327,36 +3376,42 @@ in all cases provided there are no further characters in this argument. */ - if (*argrest) - for (int i = 0; i < nelem(rsopts); i++) - if (Ustrcmp(argrest, rsopts[i]) == 0) - { - if (i != 2) f.queue_run_force = TRUE; - if (i >= 2) f.deliver_selectstring_regex = TRUE; - if (i == 1 || i == 4) f.deliver_force_thaw = TRUE; - argrest += Ustrlen(rsopts[i]); - } + if (*argrest) + for (int i = 0; i < nelem(rsopts); i++) + if (Ustrcmp(argrest, rsopts[i]) == 0) + { + if (i != 2) f.queue_run_force = TRUE; + if (i >= 2) f.deliver_selectstring_regex = TRUE; + if (i == 1 || i == 4) f.deliver_force_thaw = TRUE; + argrest += Ustrlen(rsopts[i]); + } /* -R: Set string to match in addresses for forced queue run to pick out particular messages. */ - if (*argrest) - deliver_selectstring = string_copy_taint(argrest, TRUE); - else if (i+1 < argc) - deliver_selectstring = string_copy_taint(argv[++i], TRUE); - else - exim_fail("exim: string expected after -R\n"); + /* Avoid attacks from people providing very long strings, and do so before + we make copies. */ + if (*argrest) + tainted_selectstr = argrest; + else if (i+1 < argc) + tainted_selectstr = argv[++i]; + else + exim_fail("exim: string expected after -R\n"); + deliver_selectstring = string_copy_taint(exim_str_fail_toolong(tainted_selectstr, EXIM_EMAILADDR_MAX, "-R"), TRUE); + } break; - /* -r: an obsolete synonym for -f (see above) */ /* -S: Like -R but works on sender. */ case 'S': /* Synonymous with -qS... */ - receiving_message = FALSE; + { + const uschar *tainted_selectstr; + receiving_message = FALSE; + /* -Sf: As -S (below) but force all deliveries, -Sff: Ditto, but also thaw all frozen messages, -Sr: String is regex @@ -3366,25 +3421,27 @@ in all cases provided there are no further characters in this argument. */ - if (*argrest) - for (int i = 0; i < nelem(rsopts); i++) - if (Ustrcmp(argrest, rsopts[i]) == 0) - { - if (i != 2) f.queue_run_force = TRUE; - if (i >= 2) f.deliver_selectstring_sender_regex = TRUE; - if (i == 1 || i == 4) f.deliver_force_thaw = TRUE; - argrest += Ustrlen(rsopts[i]); - } + if (*argrest) + for (int i = 0; i < nelem(rsopts); i++) + if (Ustrcmp(argrest, rsopts[i]) == 0) + { + if (i != 2) f.queue_run_force = TRUE; + if (i >= 2) f.deliver_selectstring_sender_regex = TRUE; + if (i == 1 || i == 4) f.deliver_force_thaw = TRUE; + argrest += Ustrlen(rsopts[i]); + } /* -S: Set string to match in addresses for forced queue run to pick out particular messages. */ - if (*argrest) - deliver_selectstring_sender = string_copy_taint(argrest, TRUE); - else if (i+1 < argc) - deliver_selectstring_sender = string_copy_taint(argv[++i], TRUE); - else - exim_fail("exim: string expected after -S\n"); + if (*argrest) + tainted_selectstr = argrest; + else if (i+1 < argc) + tainted_selectstr = argv[++i]; + else + exim_fail("exim: string expected after -S\n"); + deliver_selectstring_sender = string_copy_taint(exim_str_fail_toolong(tainted_selectstr, EXIM_EMAILADDR_MAX, "-S"), TRUE); + } break; /* -Tqt is an option that is exclusively for use by the testing suite. @@ -3466,10 +3523,12 @@ exim_fail("exim: string expected after -X\n"); break; + /* -z: a line of text to log */ + case 'z': if (!*argrest) if (++i < argc) - log_oneline = string_copy_taint(argv[i], TRUE); + log_oneline = string_copy_taint(exim_str_fail_toolong(argv[i], 2048, "-z logtext"), TRUE); else exim_fail("exim: file name expected after %s\n", argv[i-1]); break; @@ -3773,7 +3832,12 @@ /* 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 && errno) + exim_fail("exim: getting initial cwd failed: %s\n", strerror(errno)); +if (initial_cwd && (strlen(CCS initial_cwd) >= BIG_BUFFER_SIZE)) + exim_fail("exim: initial cwd is far too long (%d)\n", Ustrlen(CCS initial_cwd)); + /* checking: -be[m] expansion test - -b[fF] filter test new @@ -4060,11 +4124,9 @@ 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 += Ustrlen(CCS p); } (void)string_format(p, big_buffer_size - (p - big_buffer), " %d args:", argc); @@ -4438,14 +4500,14 @@ retry_config *yield; int basic_errno = 0; int more_errno = 0; - uschar *s1, *s2; + const uschar *s1, *s2; if (test_retry_arg >= argc) { printf("-brt needs a domain or address argument\n"); exim_exit(EXIT_FAILURE); } - s1 = argv[test_retry_arg++]; + s1 = exim_str_fail_toolong(argv[test_retry_arg++], EXIM_EMAILADDR_MAX, "-brt"); s2 = NULL; /* If the first argument contains no @ and no . it might be a local user @@ -4461,13 +4523,13 @@ /* There may be an optional second domain arg. */ if (test_retry_arg < argc && Ustrchr(argv[test_retry_arg], '.') != NULL) - s2 = argv[test_retry_arg++]; + s2 = exim_str_fail_toolong(argv[test_retry_arg++], EXIM_DOMAINNAME_MAX, "-brt 2nd"); /* The final arg is an error name */ if (test_retry_arg < argc) { - uschar *ss = argv[test_retry_arg]; + const uschar *ss = exim_str_fail_toolong(argv[test_retry_arg], EXIM_DRIVERNAME_MAX, "-brt 3rd"); uschar *error = readconf_retry_error(ss, ss + Ustrlen(ss), &basic_errno, &more_errno); if (error != NULL) @@ -4569,11 +4631,11 @@ Ustrcmp(argv[i], "macro") == 0 || Ustrcmp(argv[i], "environment") == 0)) { - fail |= !readconf_print(argv[i+1], argv[i], flag_n); + fail |= !readconf_print(exim_str_fail_toolong(argv[i+1], EXIM_DRIVERNAME_MAX, "-bP name"), argv[i], flag_n); i++; } else - fail = !readconf_print(argv[i], NULL, flag_n); + fail = !readconf_print(exim_str_fail_toolong(argv[i], EXIM_DRIVERNAME_MAX, "-bP item"), NULL, flag_n); } exim_exit(fail ? EXIT_FAILURE : EXIT_SUCCESS); } @@ -4618,6 +4680,7 @@ pid_t pid; /*XXX This use of argv[i] for msg_id should really be tainted, but doing that runs into a later copy into the untainted global message_id[] */ + /*XXX Do we need a length limit check here? */ if (i == argc - 1) (void)deliver_message(argv[i], forced_delivery, deliver_give_up); else if ((pid = exim_fork(US"cmdline-delivery")) == 0) @@ -4759,8 +4822,7 @@ /* Ensure that the user name is in a suitable form for use as a "phrase" in an RFC822 address.*/ -originator_name = string_copy(parse_fix_phrase(originator_name, - Ustrlen(originator_name), big_buffer, big_buffer_size)); +originator_name = parse_fix_phrase(originator_name, Ustrlen(originator_name)); /* If a message is created by this call of Exim, the uid/gid of its originator are those of the caller. These values are overridden if an existing message is @@ -4824,7 +4886,7 @@ printf("-brw needs an address argument\n"); exim_exit(EXIT_FAILURE); } - rewrite_test(argv[test_rewrite_arg]); + rewrite_test(exim_str_fail_toolong(argv[test_rewrite_arg], EXIM_EMAILADDR_MAX, "-brw")); exim_exit(EXIT_SUCCESS); } @@ -4918,7 +4980,7 @@ while (recipients_arg < argc) { /* Supplied addresses are tainted since they come from a user */ - uschar * s = string_copy_taint(argv[recipients_arg++], TRUE); + uschar * s = string_copy_taint(exim_str_fail_toolong(argv[recipients_arg++], EXIM_DISPLAYMAIL_MAX, "address verification"), TRUE); while (*s) { BOOL finished = FALSE; @@ -4936,7 +4998,7 @@ { uschar * s = get_stdinput(NULL, NULL); if (!s) break; - test_address(string_copy_taint(s, TRUE), flags, &exit_value); + test_address(string_copy_taint(exim_str_fail_toolong(s, EXIM_DISPLAYMAIL_MAX, "address verification (stdin)"), TRUE), flags, &exit_value); } route_tidyup(); @@ -4953,11 +5015,15 @@ dns_init(FALSE, FALSE, FALSE); if (msg_action_arg > 0 && msg_action == MSG_LOAD) { - uschar spoolname[256]; /* Not big_buffer; used in spool_read_header() */ + uschar * spoolname; if (!f.admin_user) exim_fail("exim: permission denied\n"); - message_id = argv[msg_action_arg]; - (void)string_format(spoolname, sizeof(spoolname), "%s-H", message_id); + message_id = US exim_str_fail_toolong(argv[msg_action_arg], MESSAGE_ID_LENGTH, "message-id"); + /* Checking the length of the ID is sufficient to validate it. + Get an untainted version so file opens can be done. */ + message_id = string_copy_taint(message_id, FALSE); + + spoolname = string_sprintf("%s-H", message_id); if ((deliver_datafile = spool_open_datafile(message_id)) < 0) printf ("Failed to load message datafile %s\n", message_id); if (spool_read_header(spoolname, TRUE, FALSE) != spool_read_OK) @@ -4996,7 +5062,7 @@ if (recipients_arg < argc) while (recipients_arg < argc) - expansion_test_line(argv[recipients_arg++]); + expansion_test_line(exim_str_fail_toolong(argv[recipients_arg++], EXIM_EMAILADDR_MAX, "recipient")); /* Read stdin */ @@ -5439,7 +5505,9 @@ { int start, end, domain; uschar * errmess; - uschar * s = string_copy_taint(list[i], TRUE); + /* There can be multiple addresses, so EXIM_DISPLAYMAIL_MAX (tuned for 1) is too short. + * We'll still want to cap it to something, just in case. */ + uschar * s = string_copy_taint(exim_str_fail_toolong(list[i], BIG_BUFFER_SIZE, "address argument"), TRUE); /* Loop for each comma-separated address */ @@ -5474,11 +5542,12 @@ parse_extract_address(s, &errmess, &start, &end, &domain, FALSE); #ifdef SUPPORT_I18N - if (string_is_utf8(recipient)) - message_smtputf8 = TRUE; - else - allow_utf8_domains = b; + if (recipient) + if (string_is_utf8(recipient)) message_smtputf8 = TRUE; + else allow_utf8_domains = b; } +#else + ; #endif if (domain == 0 && !f.allow_unqualified_recipient) { @@ -5576,10 +5645,10 @@ { deliver_domain = ftest_domain ? ftest_domain : qualify_domain_recipient; deliver_domain_orig = deliver_domain; - deliver_localpart = ftest_localpart ? ftest_localpart : originator_login; + deliver_localpart = ftest_localpart ? US ftest_localpart : originator_login; deliver_localpart_orig = deliver_localpart; - deliver_localpart_prefix = ftest_prefix; - deliver_localpart_suffix = ftest_suffix; + deliver_localpart_prefix = US ftest_prefix; + deliver_localpart_suffix = US ftest_suffix; deliver_home = originator_home; if (!return_path) diff -urN ../exim-4.94.orig/src/exim.h ./src/exim.h --- ../exim-4.94.orig/src/exim.h 2020-05-30 23:35:38.000000000 +0300 +++ ./src/exim.h 2021-05-15 15:06:16.087908000 +0300 @@ -128,6 +128,51 @@ # endif #endif +/* RFC 5321 specifies that the maximum length of a local-part is 64 octets +and the maximum length of a domain is 255 octets, but then also defines +the maximum length of a forward/reverse path as 256 not 64+1+255. +For an IP address, the maximum is 45 without a scope and we don't work +with scoped addresses, so go with that. (IPv6 with mapped IPv4). + +A hostname maximum length is in practice the same as the domainname, for +the same core reasons (maximum length of a DNS name), but the semantics +are different and seeing "DOMAIN" in source is confusing when talking about +hostnames; so we define a second macro. We'll use RFC 2181 as the reference +for this one. + +There is no known (to me) specification on the maximum length of a human name +in email addresses and we should be careful about imposing such a limit on +received email, but in terms of limiting what untrusted callers specify, or +local generation, having a limit makes sense. Err on the side of generosity. + +For a display mail address, we have a human name, an email in brackets, +possibly some (Comments), so it needs to be at least 512+3 and some more to +avoid extraneous errors. +Since the sane SMTP line length limit is 998, constraining such parameters to +be 1024 seems generous and unlikely to spuriously reject legitimate +invocations. + +The driver name is a name of a router/transport/authenticator etc in the +configuration file. We also use this for some other short strings, such +as queue names. +Also TLS ciphersuite name (no real known limit since the protocols use +integers, but max seen in reality is 45 octets). + +RFC 1413 gives us the 512 limit on IDENT protocol userids. +*/ + +#define EXIM_EMAILADDR_MAX 256 +#define EXIM_LOCALPART_MAX 64 +#define EXIM_DOMAINNAME_MAX 255 +#define EXIM_IPADDR_MAX 45 +#define EXIM_HOSTNAME_MAX 255 +#define EXIM_HUMANNAME_MAX 256 +#define EXIM_DISPLAYMAIL_MAX 1024 +#define EXIM_DRIVERNAME_MAX 64 +#define EXIM_CIPHERNAME_MAX 64 +#define EXIM_IDENTUSER_MAX 512 + + #include #include #include diff -urN ../exim-4.94.orig/src/exim_dbutil.c ./src/exim_dbutil.c --- ../exim-4.94.orig/src/exim_dbutil.c 2020-05-30 23:35:38.000000000 +0300 +++ ./src/exim_dbutil.c 2021-05-15 15:06:16.088504000 +0300 @@ -391,7 +391,7 @@ Arguments: dbblock a pointer to an open database block key the key of the record to be read - length where to put the length (or NULL if length not wanted) + length where to put the length (or NULL if length not wanted). Includes overhead. Returns: a pointer to the retrieved record, or NULL if the record is not found @@ -419,7 +419,7 @@ yield = store_get(EXIM_DATUM_SIZE(result_datum), TRUE); memcpy(yield, EXIM_DATUM_DATA(result_datum), EXIM_DATUM_SIZE(result_datum)); -if (length != NULL) *length = EXIM_DATUM_SIZE(result_datum); +if (length) *length = EXIM_DATUM_SIZE(result_datum); EXIM_DATUM_FREE(result_datum); /* Some DBM libs require freeing */ return yield; @@ -620,6 +620,7 @@ t = wait->text; name[MESSAGE_ID_LENGTH] = 0; + /* Leave corrupt records alone */ if (wait->count > WAIT_NAME_MAX) { fprintf(stderr, @@ -1222,7 +1223,7 @@ /* A continuation record may have been deleted or renamed already, so non-existence is not serious. */ - if (value == NULL) continue; + if (!value) continue; /* Delete if too old */ @@ -1243,10 +1244,31 @@ /* Leave corrupt records alone */ + if (wait->time_stamp > time(NULL)) + { + printf("**** Data for '%s' corrupted\n time in future: %s\n", + key, print_time(((dbdata_generic *)value)->time_stamp)); + continue; + } if (wait->count > WAIT_NAME_MAX) { - printf("**** Data for %s corrupted\n count=%d=0x%x max=%d\n", + printf("**** Data for '%s' corrupted\n count=%d=0x%x max=%d\n", key, wait->count, wait->count, WAIT_NAME_MAX); + continue; + } + if (wait->sequence > WAIT_CONT_MAX) + { + printf("**** Data for '%s' corrupted\n sequence=%d=0x%x max=%d\n", + key, wait->sequence, wait->sequence, WAIT_CONT_MAX); + continue; + } + + /* Record over 1 year old; just remove it */ + + if (wait->time_stamp < time(NULL) - 365*24*60*60) + { + dbfn_delete(dbm, key); + printf("deleted %s (too old)\n", key); continue; } diff -urN ../exim-4.94.orig/src/expand.c ./src/expand.c --- ../exim-4.94.orig/src/expand.c 2020-05-30 23:35:38.000000000 +0300 +++ ./src/expand.c 2021-05-15 15:06:16.093610000 +0300 @@ -1298,15 +1298,16 @@ { const uschar * tlist = list; int sep = 0; -uschar dummy; +/* Tainted mem for the throwaway element copies */ +uschar * dummy = store_get(2, TRUE); if (field < 0) { - for (field++; string_nextinlist(&tlist, &sep, &dummy, 1); ) field++; + for (field++; string_nextinlist(&tlist, &sep, dummy, 1); ) field++; sep = 0; } if (field == 0) return NULL; -while (--field > 0 && (string_nextinlist(&list, &sep, &dummy, 1))) ; +while (--field > 0 && (string_nextinlist(&list, &sep, dummy, 1))) ; return string_nextinlist(&list, &sep, NULL, 0); } @@ -4920,7 +4921,7 @@ { expand_string_message = string_sprintf("lookup of \"%s\" gave DEFER: %s", - string_printing2(key, FALSE), search_error_message); + string_printing2(key, SP_TAB), search_error_message); goto EXPAND_FAILED; } if (expand_setup > 0) expand_nmax = expand_setup; @@ -5334,11 +5335,14 @@ while ((item = string_nextinlist(&list, &sep, NULL, 0))) g = string_append_listele(g, ',', item); - /* possibly plus an EOL string */ + /* possibly plus an EOL string. Process with escapes, to protect + from list-processing. The only current user of eol= in search + options is the readsock expansion. */ + if (sub_arg[3] && *sub_arg[3]) g = string_append_listele(g, ',', - string_sprintf("eol=%s", sub_arg[3])); - + string_sprintf("eol=%s", + string_printing2(sub_arg[3], SP_TAB|SP_SPACE))); } /* Gat a (possibly cached) handle for the connection */ @@ -7208,9 +7212,8 @@ { int cnt = 0; int sep = 0; - uschar buffer[256]; - while (string_nextinlist(CUSS &sub, &sep, buffer, sizeof(buffer))) cnt++; + while (string_nextinlist(CUSS &sub, &sep, NULL, 0)) cnt++; yield = string_fmt_append(yield, "%d", cnt); continue; } @@ -7572,13 +7575,10 @@ prescribed by the RFC, if there are characters that need to be encoded */ case EOP_RFC2047: - { - uschar buffer[2048]; yield = string_cat(yield, parse_quote_2047(sub, Ustrlen(sub), headers_charset, - buffer, sizeof(buffer), FALSE)); + FALSE)); continue; - } /* RFC 2047 decode */ diff -urN ../exim-4.94.orig/src/filter.c ./src/filter.c --- ../exim-4.94.orig/src/filter.c 2020-05-30 23:35:38.000000000 +0300 +++ ./src/filter.c 2021-05-15 15:06:16.094040000 +0300 @@ -51,7 +51,7 @@ /* Miscellaneous other declarations */ static uschar **error_pointer; -static uschar *log_filename; +static const uschar *log_filename; static int filter_options; static int line_number; static int expect_endif; @@ -1668,7 +1668,7 @@ static int interpret_commands(filter_cmd *commands, address_item **generated) { -uschar *s; +const uschar *s; int mode; address_item *addr; BOOL condition_value; @@ -1677,7 +1677,7 @@ { int ff_ret; uschar *fmsg, *ff_name; - uschar *expargs[MAILARGS_STRING_COUNT]; + const uschar *expargs[MAILARGS_STRING_COUNT]; int i, n[2]; @@ -1709,7 +1709,7 @@ case add_command: for (i = 0; i < 2; i++) { - uschar *ss = expargs[i]; + const uschar *ss = expargs[i]; uschar *end; if (i == 1 && (*ss++ != 'n' || ss[1] != 0)) @@ -1806,9 +1806,8 @@ af_ignore_error flag if necessary, and the errors address, which can be set in a system filter and to the local address in user filters. */ - addr = deliver_make_addr(expargs[0], TRUE); /* TRUE => copy s */ - addr->prop.errors_address = (s == NULL)? - s : string_copy(s); /* Default is NULL */ + addr = deliver_make_addr(US expargs[0], TRUE); /* TRUE => copy s, so deconst ok */ + addr->prop.errors_address = !s ? NULL : string_copy(s); /* Default is NULL */ if (commands->noerror) addr->prop.ignore_error = TRUE; addr->next = *generated; *generated = addr; @@ -1848,7 +1847,7 @@ af_pfr and af_file flags, the af_ignore_error flag if necessary, and the mode value. */ - addr = deliver_make_addr(s, TRUE); /* TRUE => copy s */ + addr = deliver_make_addr(US s, TRUE); /* TRUE => copy s, so deconst ok */ setflag(addr, af_pfr); setflag(addr, af_file); if (commands->noerror) addr->prop.ignore_error = TRUE; @@ -1878,7 +1877,7 @@ each command argument is expanded in the transport after the command has been split up into separate arguments. */ - addr = deliver_make_addr(s, TRUE); /* TRUE => copy s */ + addr = deliver_make_addr(US s, TRUE); /* TRUE => copy s, so deconst ok */ setflag(addr, af_pfr); setflag(addr, af_expand_pipe); if (commands->noerror) addr->prop.ignore_error = TRUE; @@ -2019,7 +2018,7 @@ /* This setting lasts only while the filter is running; on exit, the variable is reset to the previous value. */ - else headers_charset = s; + else headers_charset = s; /*XXX loses track of const */ } break; @@ -2043,7 +2042,7 @@ ff_ret = FF_FREEZE; DEFERFREEZEFAIL: - fmsg = expargs[0]; + fmsg = expargs[0]; /*XXX loses track of const */ if (Ustrlen(fmsg) > 1024) Ustrcpy(fmsg + 1000, US" ... (truncated)"); fmsg = US string_printing(fmsg); *error_pointer = fmsg; @@ -2126,7 +2125,7 @@ for (i = 0; i < MAILARGS_STRING_COUNT; i++) { uschar *p; - uschar *s = expargs[i]; + const uschar *s = expargs[i]; if (s == NULL) continue; @@ -2180,7 +2179,7 @@ /* The string is OK */ - commands->args[i].u = s; + commands->args[i].u = s; /*XXX loses track of const */ } /* Proceed with mail or vacation command */ @@ -2368,8 +2367,9 @@ BOOL filter_personal(string_item *aliases, BOOL scan_cc) { -uschar *self, *self_from, *self_to; -uschar *psself = NULL, *psself_from = NULL, *psself_to = NULL; +const uschar *self, *self_from, *self_to; +uschar *psself = NULL; +const uschar *psself_from = NULL, *psself_to = NULL; rmark reset_point = store_mark(); BOOL yield; header_line *h; diff -urN ../exim-4.94.orig/src/functions.h ./src/functions.h --- ../exim-4.94.orig/src/functions.h 2020-05-30 23:35:38.000000000 +0300 +++ ./src/functions.h 2021-05-15 15:06:16.094511000 +0300 @@ -234,6 +234,7 @@ extern int exim_chown_failure(int, const uschar*, uid_t, gid_t); extern const uschar * exim_errstr(int); extern void exim_exit(int) NORETURN; +extern void exim_gettime(struct timeval *); extern void exim_nullstd(void); extern void exim_setugid(uid_t, gid_t, BOOL, uschar *); extern void exim_underbar_exit(int) NORETURN; @@ -307,8 +308,7 @@ 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); @@ -359,16 +359,18 @@ extern int open_cutthrough_connection( address_item * addr ); -extern uschar *parse_extract_address(uschar *, uschar **, int *, int *, int *, +extern uschar *parse_extract_address(const uschar *, uschar **, int *, int *, int *, BOOL); extern int parse_forward_list(uschar *, int, address_item **, uschar **, const uschar *, uschar *, error_block **); extern uschar *parse_find_address_end(uschar *, BOOL); -extern uschar *parse_find_at(uschar *); -extern const uschar *parse_fix_phrase(const uschar *, int, uschar *, int); -extern uschar *parse_message_id(uschar *, uschar **, uschar **); -extern const uschar *parse_quote_2047(const uschar *, int, uschar *, uschar *, int, BOOL); -extern uschar *parse_date_time(uschar *str, time_t *t); +extern const uschar *parse_find_at(const uschar *); +extern const uschar *parse_fix_phrase(const uschar *, int); +extern const uschar *parse_message_id(const uschar *, uschar **, uschar **); +extern const uschar *parse_quote_2047(const uschar *, int, uschar *, BOOL); +extern const uschar *parse_date_time(const uschar *str, time_t *t); +extern void priv_drop_temp(const uid_t, const gid_t); +extern void priv_restore(void); extern int vaguely_random_number(int); #ifndef DISABLE_TLS extern int vaguely_random_number_fallback(int); @@ -396,7 +398,7 @@ extern uschar *readconf_find_option(void *); extern void readconf_main(BOOL); extern void readconf_options_from_list(optionlist *, unsigned, const uschar *, uschar *); -extern BOOL readconf_print(uschar *, uschar *, BOOL); +extern BOOL readconf_print(const uschar *, uschar *, BOOL); extern uschar *readconf_printtime(int); extern uschar *readconf_readname(uschar *, int, uschar *); extern int readconf_readtime(const uschar *, int, BOOL); @@ -422,14 +424,14 @@ extern BOOL retry_ultimate_address_timeout(uschar *, const uschar *, dbdata_retry *, time_t); extern void retry_update(address_item **, address_item **, address_item **); -extern uschar *rewrite_address(uschar *, BOOL, BOOL, rewrite_rule *, int); -extern uschar *rewrite_address_qualify(uschar *, BOOL); +extern const uschar *rewrite_address(const uschar *, BOOL, BOOL, rewrite_rule *, int); +extern const uschar *rewrite_address_qualify(const uschar *, BOOL); extern header_line *rewrite_header(header_line *, const uschar *, const uschar *, rewrite_rule *, int, BOOL); -extern uschar *rewrite_one(uschar *, int, BOOL *, BOOL, uschar *, +extern const uschar *rewrite_one(const uschar *, int, BOOL *, BOOL, uschar *, rewrite_rule *); -extern void rewrite_test(uschar *); +extern void rewrite_test(const uschar *); extern uschar *rfc2047_decode2(uschar *, BOOL, uschar *, int, int *, int *, uschar **); extern int route_address(address_item *, address_item **, address_item **, @@ -528,7 +530,7 @@ #ifdef SUPPORT_I18N extern BOOL string_is_utf8(const uschar *); #endif -extern const uschar *string_printing2(const uschar *, BOOL); +extern const uschar *string_printing2(const uschar *, int); extern uschar *string_split_message(uschar *); extern uschar *string_unprinting(uschar *); #ifdef SUPPORT_I18N @@ -590,9 +592,9 @@ BOOL (*)(transport_ctx *, uschar *, int)); extern gstring * transport_show_supported(gstring *); extern BOOL transport_write_message(transport_ctx *, int); -extern void tree_add_duplicate(uschar *, address_item *); -extern void tree_add_nonrecipient(uschar *); -extern void tree_add_unusable(host_item *); +extern void tree_add_duplicate(const uschar *, address_item *); +extern void tree_add_nonrecipient(const uschar *); +extern void tree_add_unusable(const host_item *); extern void tree_dup(tree_node **, tree_node *); extern int tree_insertnode(tree_node **, tree_node *); extern tree_node *tree_search(tree_node *, const uschar *); @@ -767,9 +769,9 @@ /* Simple string-copy functions maintaining the taint */ #define string_copyn(s, len) \ - string_copyn_taint_trc((s), (len), is_tainted(s), __FUNCTION__, __LINE__) + string_copyn_trc((s), (len), __FUNCTION__, __LINE__) #define string_copy(s) \ - string_copy_taint_trc((s), is_tainted(s), __FUNCTION__, __LINE__) + string_copy_trc((s), __FUNCTION__, __LINE__) /************************************************* diff -urN ../exim-4.94.orig/src/globals.c ./src/globals.c --- ../exim-4.94.orig/src/globals.c 2020-05-30 23:35:38.000000000 +0300 +++ ./src/globals.c 2021-05-15 15:06:16.094902000 +0300 @@ -221,6 +221,7 @@ .authentication_local = FALSE, .background_daemon = TRUE, + .bdat_readers_wanted = FALSE, .chunking_offered = FALSE, .config_changed = FALSE, @@ -729,6 +730,8 @@ int connection_max_messages= -1; uschar *continue_proxy_cipher = NULL; +BOOL continue_proxy_dane = FALSE; +uschar *continue_proxy_sni = NULL; uschar *continue_hostname = NULL; uschar *continue_host_address = NULL; int continue_sequence = 1; @@ -1270,7 +1273,7 @@ int recipients_count = 0; recipient_item *recipients_list = NULL; int recipients_list_max = 0; -int recipients_max = 0; +int recipients_max = 50000; const pcre *regex_AUTH = NULL; const pcre *regex_check_dns_names = NULL; const pcre *regex_From = NULL; diff -urN ../exim-4.94.orig/src/globals.h ./src/globals.h --- ../exim-4.94.orig/src/globals.h 2020-05-30 23:35:38.000000000 +0300 +++ ./src/globals.h 2021-05-15 15:06:16.095405000 +0300 @@ -183,6 +183,7 @@ 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 */ @@ -425,6 +426,8 @@ extern uschar *config_main_directory; /* Directory where the main config file was found */ extern uid_t config_uid; /* Additional owner */ extern uschar *continue_proxy_cipher; /* TLS cipher for proxied continued delivery */ +extern BOOL continue_proxy_dane; /* proxied conn is DANE */ +extern uschar *continue_proxy_sni; /* proxied conn SNI */ extern uschar *continue_hostname; /* Host for continued delivery */ extern uschar *continue_host_address; /* IP address for ditto */ extern int continue_sequence; /* Sequence num for continued delivery */ diff -urN ../exim-4.94.orig/src/host.c ./src/host.c --- ../exim-4.94.orig/src/host.c 2020-05-30 23:35:38.000000000 +0300 +++ ./src/host.c 2021-05-15 15:06:16.096811000 +0300 @@ -1197,9 +1197,9 @@ c++; } -c[-1] = '\0'; /* drop trailing colon */ +*--c = '\0'; /* drop trailing colon */ -/* debug_printf("%s: D k %d <%s> <%s>\n", __FUNCTION__, k, d, d + 2*(k+1)); */ +/* debug_printf("%s: D k %d <%s> <%s>\n", __FUNCTION__, k, buffer, buffer + 2*(k+1)); */ if (k >= 0) { /* collapse */ c = d + 2*(k+1); @@ -1946,8 +1946,13 @@ int yield, times; host_item *last = NULL; BOOL temp_error = FALSE; -#if HAVE_IPV6 int af; + +#ifndef DISABLE_TLS +/* Copy the host name at this point to the value which is used for +TLS certificate name checking, before anything modifies it. */ + +host->certname = host->name; #endif /* Make sure DNS options are set as required. This appears to be necessary in @@ -1967,10 +1972,10 @@ #ifdef STAND_ALONE if (disable_ipv6) #else - if (disable_ipv6 || - (dns_ipv4_lookup != NULL && - match_isinlist(host->name, CUSS &dns_ipv4_lookup, 0, NULL, NULL, - MCL_DOMAIN, TRUE, NULL) == OK)) + if ( disable_ipv6 + || dns_ipv4_lookup + && match_isinlist(host->name, CUSS &dns_ipv4_lookup, 0, + &domainlist_anchor, NULL, MCL_DOMAIN, TRUE, NULL) == OK) #endif { af = AF_INET; times = 1; } @@ -1980,7 +1985,7 @@ /* No IPv6 support */ #else /* HAVE_IPV6 */ - times = 1; + af = AF_INET; times = 1; #endif /* HAVE_IPV6 */ /* Initialize the flag that gets set for DNS syntax check errors, so that the @@ -2022,7 +2027,7 @@ #else /* not HAVE_IPV6 */ if (f.running_in_test_harness) - hostdata = host_fake_gethostbyname(host->name, AF_INET, &error_num); + hostdata = host_fake_gethostbyname(host->name, af, &error_num); else { hostdata = gethostbyname(CS host->name); @@ -2117,6 +2122,9 @@ { host_item *next = store_get(sizeof(host_item), FALSE); next->name = host->name; +#ifndef DISABLE_TLS + next->certname = host->certname; +#endif next->mx = host->mx; next->address = text_address; next->port = PORT_NONE; @@ -2192,8 +2200,8 @@ int rc; const uschar *save = deliver_domain; deliver_domain = host->name; /* set $domain */ - rc = match_isinlist(host->name, CUSS &dns_again_means_nonexist, 0, NULL, NULL, - MCL_DOMAIN, TRUE, NULL); + rc = match_isinlist(host->name, CUSS &dns_again_means_nonexist, 0, + &domainlist_anchor, NULL, MCL_DOMAIN, TRUE, NULL); deliver_domain = save; if (rc == OK) { @@ -2260,6 +2268,13 @@ BOOL dnssec_fail = FALSE; int i; +#ifndef DISABLE_TLS +/* Copy the host name at this point to the value which is used for +TLS certificate name checking, before any CNAME-following modifies it. */ + +host->certname = host->name; +#endif + /* If allow_ip is set, a name which is an IP address returns that value as its address. This is used for MX records when allow_mx_to_ip is set, for those sites that feel they have to flaunt the RFC rules. */ @@ -2286,9 +2301,9 @@ #ifndef STAND_ALONE if ( disable_ipv6 || !(whichrrs & HOST_FIND_BY_AAAA) - || (dns_ipv4_lookup - && match_isinlist(host->name, CUSS &dns_ipv4_lookup, 0, NULL, NULL, - MCL_DOMAIN, TRUE, NULL) == OK) + || dns_ipv4_lookup + && match_isinlist(host->name, CUSS &dns_ipv4_lookup, 0, + &domainlist_anchor, NULL, MCL_DOMAIN, TRUE, NULL) == OK ) i = 0; /* look up A records only */ else @@ -2546,12 +2561,12 @@ dns_answer * dnsa = store_get_dns_answer(); dns_scan dnss; BOOL dnssec_require = dnssec_d - && match_isinlist(host->name, CUSS &dnssec_d->require, - 0, NULL, NULL, MCL_DOMAIN, TRUE, NULL) == OK; + && match_isinlist(host->name, CUSS &dnssec_d->require, + 0, &domainlist_anchor, NULL, MCL_DOMAIN, TRUE, NULL) == OK; BOOL dnssec_request = dnssec_require - || ( dnssec_d - && match_isinlist(host->name, CUSS &dnssec_d->request, - 0, NULL, NULL, MCL_DOMAIN, TRUE, NULL) == OK); + || ( dnssec_d + && match_isinlist(host->name, CUSS &dnssec_d->request, + 0, &domainlist_anchor, NULL, MCL_DOMAIN, TRUE, NULL) == OK); dnssec_status_t dnssec; /* Set the default fully qualified name to the incoming name, initialize the @@ -2616,10 +2631,10 @@ } if (rc == DNS_FAIL || rc == DNS_AGAIN) { - #ifndef STAND_ALONE - if (match_isinlist(host->name, CUSS &srv_fail_domains, 0, NULL, NULL, - MCL_DOMAIN, TRUE, NULL) != OK) - #endif +#ifndef STAND_ALONE + if (match_isinlist(host->name, CUSS &srv_fail_domains, 0, + &domainlist_anchor, NULL, MCL_DOMAIN, TRUE, NULL) != OK) +#endif { yield = HOST_FIND_AGAIN; goto out; } DEBUG(D_host_lookup) debug_printf("DNS_%s treated as DNS_NODATA " "(domain in srv_fail_domains)\n", (rc == DNS_FAIL)? "FAIL":"AGAIN"); @@ -2668,8 +2683,8 @@ DEBUG(D_host_lookup) debug_printf("dnssec fail on MX for %.256s", host->name); #ifndef STAND_ALONE - if (match_isinlist(host->name, CUSS &mx_fail_domains, 0, NULL, NULL, - MCL_DOMAIN, TRUE, NULL) != OK) + if (match_isinlist(host->name, CUSS &mx_fail_domains, 0, + &domainlist_anchor, NULL, MCL_DOMAIN, TRUE, NULL) != OK) { yield = HOST_FIND_SECURITY; goto out; } #endif rc = DNS_FAIL; @@ -2678,8 +2693,8 @@ case DNS_FAIL: case DNS_AGAIN: #ifndef STAND_ALONE - if (match_isinlist(host->name, CUSS &mx_fail_domains, 0, NULL, NULL, - MCL_DOMAIN, TRUE, NULL) != OK) + if (match_isinlist(host->name, CUSS &mx_fail_domains, 0, + &domainlist_anchor, NULL, MCL_DOMAIN, TRUE, NULL) != OK) #endif { yield = HOST_FIND_AGAIN; goto out; } DEBUG(D_host_lookup) debug_printf("DNS_%s treated as DNS_NODATA " diff -urN ../exim-4.94.orig/src/log.c ./src/log.c --- ../exim-4.94.orig/src/log.c 2020-05-30 23:35:38.000000000 +0300 +++ ./src/log.c 2021-05-15 15:06:16.097646000 +0300 @@ -264,15 +264,20 @@ 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 @@ 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 @@ +/* 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,42 +388,61 @@ */ int -log_create_as_exim(uschar *name) +log_open_as_exim(uschar * const name) { -pid_t pid = exim_fork(US"logfile-create"); -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 = exim_fork(US"logfile-open"); + 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 we created a subprocess, wait for it. If it succeeded, try the open. */ + if (getuid() != exim_uid || geteuid() != exim_uid) _exit(EXIT_FAILURE); + if (getgid() != exim_gid || getegid() != exim_gid) _exit(EXIT_FAILURE); -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); + 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 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. */ + (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 (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 @@ 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 @@ -900,6 +960,7 @@ 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); return; } diff -urN ../exim-4.94.orig/src/lookups/dsearch.c ./src/lookups/dsearch.c --- ../exim-4.94.orig/src/lookups/dsearch.c 2020-05-30 23:35:38.000000000 +0300 +++ ./src/lookups/dsearch.c 2021-05-15 15:06:16.098731000 +0300 @@ -125,8 +125,7 @@ && S_ISDIR(statbuf.st_mode) && ( flags & FILTER_DIR || keystring[0] != '.' - || keystring[1] != '.' - || keystring[1] && keystring[2] + || keystring[1] && keystring[1] != '.' ) ) ) ) { /* Since the filename exists in the filesystem, we can return a @@ -135,10 +134,10 @@ return OK; } -if (errno == ENOENT) return FAIL; +if (errno == ENOENT || errno == 0) return FAIL; save_errno = errno; -*errmsg = string_sprintf("%s: lstat failed", filename); +*errmsg = string_sprintf("%s: lstat: %s", filename, strerror(errno)); errno = save_errno; return DEFER; } diff -urN ../exim-4.94.orig/src/lookups/ldap.c ./src/lookups/ldap.c --- ../exim-4.94.orig/src/lookups/ldap.c 2020-05-30 23:35:38.000000000 +0300 +++ ./src/lookups/ldap.c 2021-05-15 15:06:16.099608000 +0300 @@ -1093,7 +1093,6 @@ uschar *local_servers = NULL; uschar *server; const uschar *list; -uschar buffer[512]; while (isspace(*url)) url++; @@ -1254,7 +1253,7 @@ /* Loop through the default servers until OK or FAIL. Use local_servers list * if defined in the lookup, otherwise use the global default list */ list = !local_servers ? eldap_default_servers : local_servers; -while ((server = string_nextinlist(&list, &sep, buffer, sizeof(buffer)))) +while ((server = string_nextinlist(&list, &sep, NULL, 0))) { int rc; int port = 0; diff -urN ../exim-4.94.orig/src/lookups/readsock.c ./src/lookups/readsock.c --- ../exim-4.94.orig/src/lookups/readsock.c 2020-05-30 23:35:38.000000000 +0300 +++ ./src/lookups/readsock.c 2021-05-15 15:06:16.101375000 +0300 @@ -186,7 +186,9 @@ gstring * yield; int ret = DEFER; -DEBUG(D_lookup) debug_printf_indent("readsock: file=\"%s\" key=\"%s\" len=%d opts=\"%s\"\n", filename, keystring, length, opts); +DEBUG(D_lookup) + debug_printf_indent("readsock: file=\"%s\" key=\"%s\" len=%d opts=\"%s\"\n", + filename, keystring, length, opts); /* Parse options */ @@ -200,7 +202,7 @@ lf.do_tls = TRUE; #endif else if (Ustrncmp(s, "eol=", 4) == 0) - eol = s + 4; + eol = string_unprinting(s + 4); else if (Ustrcmp(s, "cache=yes") == 0) lf.cache = TRUE; else if (Ustrcmp(s, "send=no") == 0) diff -urN ../exim-4.94.orig/src/lookups/sqlite.c ./src/lookups/sqlite.c --- ../exim-4.94.orig/src/lookups/sqlite.c 2020-05-30 23:35:38.000000000 +0300 +++ ./src/lookups/sqlite.c 2021-05-15 15:06:16.101812000 +0300 @@ -24,16 +24,23 @@ sqlite3 *db = NULL; int ret; -if (!filename || !*filename) filename = sqlite_dbfile; -if (*filename != '/') +if (!filename || !*filename) + { + DEBUG(D_lookup) debug_printf_indent("Using sqlite_dbfile: %s\n", sqlite_dbfile); + filename = sqlite_dbfile; + } +if (!filename || *filename != '/') *errmsg = US"absolute file name expected for \"sqlite\" lookup"; else if ((ret = sqlite3_open(CCS filename, &db)) != 0) { *errmsg = (void *)sqlite3_errmsg(db); + sqlite3_close(db); + db = NULL; DEBUG(D_lookup) debug_printf_indent("Error opening database: %s\n", *errmsg); } -sqlite3_busy_timeout(db, 1000 * sqlite_lock_timeout); +if (db) + sqlite3_busy_timeout(db, 1000 * sqlite_lock_timeout); return db; } diff -urN ../exim-4.94.orig/src/macro_predef.c ./src/macro_predef.c --- ../exim-4.94.orig/src/macro_predef.c 2020-05-30 23:35:38.000000000 +0300 +++ ./src/macro_predef.c 2021-05-15 15:06:16.102176000 +0300 @@ -72,7 +72,7 @@ const uschar * section, uschar * group) { const uschar * s; -uschar buf[64]; +uschar buf[EXIM_DRIVERNAME_MAX]; /* The 'previously-defined-substring' rule for macros in config file lines is done thus for these builtin macros: we know that the table diff -urN ../exim-4.94.orig/src/macros.h ./src/macros.h --- ../exim-4.94.orig/src/macros.h 2020-05-30 23:35:38.000000000 +0300 +++ ./src/macros.h 2021-05-15 15:06:16.102837000 +0300 @@ -41,9 +41,11 @@ /* For almost all calls to convert things to printing characters, we want to -allow tabs. A macro just makes life a bit easier. */ +allow tabs & spaces. A macro just makes life a bit easier. */ -#define string_printing(s) string_printing2((s), TRUE) +#define string_printing(s) string_printing2((s), 0) +#define SP_TAB BIT(0) +#define SP_SPACE BIT(1) /* We need a special return code for "no recipients and failed to send an error @@ -148,10 +150,13 @@ /* 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 -as long as the maximum path length. */ +as long as the maximum path length PLUS room for string additions. +Let's go with "at least twice as large as maximum path length". +*/ #ifdef AUTH_HEIMDAL_GSSAPI /* RFC 4121 section 5.2, SHOULD support 64K input buffers */ @@ -160,10 +165,12 @@ # define __BIG_BUFFER_SIZE 16384 #endif -#if defined PATH_MAX && PATH_MAX > __BIG_BUFFER_SIZE -# define BIG_BUFFER_SIZE PATH_MAX -#elif defined MAXPATHLEN && MAXPATHLEN > __BIG_BUFFER_SIZE -# define BIG_BUFFER_SIZE MAXPATHLEN +#ifndef PATH_MAX +/* exim.h will have ensured this exists before including us. */ +# error headers confusion, PATH_MAX missing in macros.h +#endif +#if (PATH_MAX*2) > __BIG_BUFFER_SIZE +# define BIG_BUFFER_SIZE (PATH_MAX*2) #else # define BIG_BUFFER_SIZE __BIG_BUFFER_SIZE #endif @@ -177,21 +184,16 @@ #define LOCAL_SCAN_MAX_RETURN (BIG_BUFFER_SIZE - 24) -/* A limit to the length of an address. RFC 2821 limits the local part to 64 -and the domain to 255, so this should be adequate, taking into account quotings -etc. */ - -#define ADDRESS_MAXLENGTH 512 - /* The length of the base names of spool files, which consist of an internal message id with a trailing "-H" or "-D" added. */ #define SPOOL_NAME_LENGTH (MESSAGE_ID_LENGTH+2) /* The maximum number of message ids to store in a waiting database -record. */ +record, and the max number of continuation records allowed. */ #define WAIT_NAME_MAX 50 +#define WAIT_CONT_MAX 1000 /* Wait this long before determining that a Proxy Protocol configured host isn't speaking the protocol, and so is disallowed. Can be moved to @@ -302,6 +304,7 @@ #define CANCELLED 13 /* Authentication cancelled */ #define FAIL_SEND 14 /* send() failed in authenticator */ #define FAIL_DROP 15 /* Fail and drop connection (used in ACL) */ +#define DANE 16 /* Deferred for domain mismatch (used in transport) */ /* Returns from the deliver_message() function */ @@ -1067,8 +1070,8 @@ #define AUTHS_REGEX US"\\n250[\\s\\-]AUTH\\s+([\\-\\w \\t]+)(?:\\n|$)" -#define EARLY_PIPE_FEATURE_NAME "X_PIPE_CONNECT" -#define EARLY_PIPE_FEATURE_LEN 14 +#define EARLY_PIPE_FEATURE_NAME "PIPE_CONNECT" +#define EARLY_PIPE_FEATURE_LEN 12 /* Flags for auth_client_item() */ diff -urN ../exim-4.94.orig/src/malware.c ./src/malware.c --- ../exim-4.94.orig/src/malware.c 2020-05-30 23:35:38.000000000 +0300 +++ ./src/malware.c 2021-05-15 15:06:16.103305000 +0300 @@ -109,7 +109,7 @@ { const uschar * s; uschar * t; -uschar buf[64]; +uschar buf[EXIM_DRIVERNAME_MAX]; spf(buf, sizeof(buf), US"_HAVE_MALWARE_"); diff -urN ../exim-4.94.orig/src/match.c ./src/match.c --- ../exim-4.94.orig/src/match.c 2020-05-30 23:35:38.000000000 +0300 +++ ./src/match.c 2021-05-15 15:06:16.103762000 +0300 @@ -850,6 +850,11 @@ (void)fclose(f); HDEBUG(D_lists) debug_printf("%s %s (matched \"%s\" in %s)\n", ot, yield == OK ? "yes" : "no", sss, filename); + + /* The "pattern" being matched came from the file; we use a stack-local. + Copy it to allocated memory now we know it matched. */ + + if (valueptr) *valueptr = string_copy(ss); return file_yield; case DEFER: @@ -1288,9 +1293,11 @@ provided that "caseless" is set. (It is FALSE for calls for matching rewriting patterns.) Otherwise just the domain is lower cases. A magic item "+caseful" in the list can be used to restore a caseful copy of the local part from the -original address. */ +original address. +Limit the subject address size to avoid mem-exhastion attacks. The size chosen +is historical (we used to use big_buffer her). */ -if ((len = Ustrlen(address)) > 255) len = 255; +if ((len = Ustrlen(address)) > BIG_BUFFER_SIZE) len = BIG_BUFFER_SIZE; ab.address = string_copyn(address, len); for (uschar * p = ab.address + len - 1; p >= ab.address; p--) diff -urN ../exim-4.94.orig/src/moan.c ./src/moan.c --- ../exim-4.94.orig/src/moan.c 2020-05-30 23:35:38.000000000 +0300 +++ ./src/moan.c 2021-05-15 15:06:16.104616000 +0300 @@ -86,7 +86,8 @@ fprintf(fp, "References:"); if (h) { - uschar * s, * id, * error; + const uschar * s; + uschar * id, * error; uschar * referenced_ids[12]; int reference_count = 0; diff -urN ../exim-4.94.orig/src/parse.c ./src/parse.c --- ../exim-4.94.orig/src/parse.c 2020-05-30 23:35:38.000000000 +0300 +++ ./src/parse.c 2021-05-15 15:06:16.105806000 +0300 @@ -12,7 +12,7 @@ #include "exim.h" -static uschar *last_comment_position; +static const uschar *last_comment_position; @@ -143,21 +143,21 @@ Returns: pointer to the last @ in an address, or NULL if none */ -uschar * -parse_find_at(uschar *s) +const uschar * +parse_find_at(const uschar *s) { -uschar *t = s + Ustrlen(s); +const uschar * t = s + Ustrlen(s); while (--t >= s) - { if (*t == '@') { int backslash_count = 0; - uschar *tt = t - 1; + const uschar *tt = t - 1; while (tt > s && *tt-- == '\\') backslash_count++; if ((backslash_count & 1) == 0) return t; } - else if (*t == '\"') return NULL; - } + else if (*t == '\"') + return NULL; + return NULL; } @@ -191,8 +191,8 @@ Returns: new character pointer */ -static uschar * -skip_comment(uschar *s) +static const uschar * +skip_comment(const uschar *s) { last_comment_position = s; while (*s) @@ -232,8 +232,8 @@ Returns: new character pointer */ -static uschar * -read_domain(uschar *s, uschar *t, uschar **errorptr) +static const uschar * +read_domain(const uschar *s, uschar *t, uschar **errorptr) { uschar *tt = t; s = skip_comment(s); @@ -406,8 +406,8 @@ Returns: new character pointer */ -static uschar * -read_local_part(uschar *s, uschar *t, uschar **error, BOOL allow_null) +static const uschar * +read_local_part(const uschar *s, uschar *t, uschar **error, BOOL allow_null) { uschar *tt = t; *error = NULL; @@ -491,8 +491,8 @@ Returns: new character pointer */ -static uschar * -read_route(uschar *s, uschar *t, uschar **errorptr) +static const uschar * +read_route(const uschar *s, uschar *t, uschar **errorptr) { BOOL commas = FALSE; *errorptr = NULL; @@ -545,8 +545,8 @@ Returns: new character pointer */ -static uschar * -read_addr_spec(uschar *s, uschar *t, int term, uschar **errorptr, +static const uschar * +read_addr_spec(const uschar *s, uschar *t, int term, uschar **errorptr, uschar **domainptr) { s = read_local_part(s, t, errorptr, FALSE); @@ -616,12 +616,12 @@ #define FAILED(s) { *errorptr = s; goto PARSE_FAILED; } uschar * -parse_extract_address(uschar *mailbox, uschar **errorptr, int *start, int *end, +parse_extract_address(const uschar *mailbox, uschar **errorptr, int *start, int *end, int *domain, BOOL allow_null) { uschar *yield = store_get(Ustrlen(mailbox) + 1, is_tainted(mailbox)); -uschar *startptr, *endptr; -uschar *s = US mailbox; +const uschar *startptr, *endptr; +const uschar *s = US mailbox; uschar *t = US yield; *domain = 0; @@ -808,11 +808,11 @@ *end = endptr - US mailbox; /* Although this code has no limitation on the length of address extracted, -other parts of Exim may have limits, and in any case, RFC 2821 limits local -parts to 64 and domains to 255, so we do a check here, giving an error if the -address is ridiculously long. */ +other parts of Exim may have limits, and in any case, RFC 5321 limits email +addresses to 256, so we do a check here, giving an error if the address is +ridiculously long. */ -if (*end - *start > ADDRESS_MAXLENGTH) +if (*end - *start > EXIM_EMAILADDR_MAX) { *errorptr = string_sprintf("address is ridiculously long: %.64s...", yield); return NULL; @@ -843,8 +843,7 @@ /* This function is used for quoting text in headers according to RFC 2047. If the only characters that strictly need quoting are spaces, we return the -original string, unmodified. If a quoted string is too long for the buffer, it -is truncated. (This shouldn't happen: this is normally handling short strings.) +original string, unmodified. Hmmph. As always, things get perverted for other uses. This function was originally for the "phrase" part of addresses. Now it is being used for much @@ -856,77 +855,57 @@ chars len the length of the string charset the name of the character set; NULL => iso-8859-1 - buffer the buffer to put the answer in - buffer_size the size of the buffer fold if TRUE, a newline is inserted before the separating space when more than one encoded-word is generated Returns: pointer to the original string, if no quoting needed, or - pointer to buffer containing the quoted string, or - a pointer to "String too long" if the buffer can't even hold - the introduction + pointer to allocated memory containing the quoted string */ const uschar * -parse_quote_2047(const uschar *string, int len, uschar *charset, uschar *buffer, - int buffer_size, BOOL fold) +parse_quote_2047(const uschar *string, int len, uschar *charset, BOOL fold) { -const uschar *s = string; -uschar *p, *t; -int hlen; +const uschar * s = string; +int hlen, l; BOOL coded = FALSE; BOOL first_byte = FALSE; +gstring * g = + string_fmt_append(NULL, "=?%s?Q?", charset ? charset : US"iso-8859-1"); -if (!charset) charset = US"iso-8859-1"; +hlen = l = g->ptr; -/* We don't expect this to fail! */ - -if (!string_format(buffer, buffer_size, "=?%s?Q?", charset)) - return US"String too long"; - -hlen = Ustrlen(buffer); -t = buffer + hlen; -p = buffer; - -for (; len > 0; len--) +for (s = string; len > 0; s++, len--) { - int ch = *s++; - if (t > buffer + buffer_size - hlen - 8) break; + int ch = *s; - if ((t - p > 67) && !first_byte) + if (g->ptr - l > 67 && !first_byte) { - *t++ = '?'; - *t++ = '='; - if (fold) *t++ = '\n'; - *t++ = ' '; - p = t; - Ustrncpy(p, buffer, hlen); - t += hlen; + g = fold ? string_catn(g, US"?=\n ", 4) : string_catn(g, US"?= ", 3); + l = g->ptr; + g = string_catn(g, g->s, hlen); } - if (ch < 33 || ch > 126 || - Ustrchr("?=()<>@,;:\\\".[]_", ch) != NULL) + if ( ch < 33 || ch > 126 + || Ustrchr("?=()<>@,;:\\\".[]_", ch) != NULL) { if (ch == ' ') { - *t++ = '_'; + g = string_catn(g, US"_", 1); first_byte = FALSE; } else { - t += sprintf(CS t, "=%02X", ch); + g = string_fmt_append(g, "=%02X", ch); coded = TRUE; first_byte = !first_byte; } } - else { *t++ = ch; first_byte = FALSE; } + else + { g = string_catn(g, s, 1); first_byte = FALSE; } } -*t++ = '?'; -*t++ = '='; -*t = 0; - -return coded ? buffer : string; +g = string_catn(g, US"?=", 2); +return coded ? string_from_gstring(g) : string; } @@ -969,32 +948,25 @@ We *could* use this for all cases, getting rid of the messy original code, but leave it for now. It would complicate simple cases like "John Q. Smith". -The result is passed back in the buffer; it is usually going to be added to -some other string. In order to be sure there is going to be no overflow, -restrict the length of the input to 1/4 of the buffer size - this allows for -every single character to be quoted or encoded without overflowing, and that -wouldn't happen because of amalgamation. If the phrase is too long, return a -fixed string. +The result is passed back in allocated memory. Arguments: phrase an RFC822 phrase len the length of the phrase - buffer a buffer to put the result in - buffer_size the size of the buffer Returns: the fixed RFC822 phrase */ const uschar * -parse_fix_phrase(const uschar *phrase, int len, uschar *buffer, int buffer_size) +parse_fix_phrase(const uschar *phrase, int len) { int ch, i; BOOL quoted = FALSE; const uschar *s, *end; +uschar * buffer; uschar *t, *yield; while (len > 0 && isspace(*phrase)) { phrase++; len--; } -if (len > buffer_size/4) return US"Name too long"; /* See if there are any non-printing characters, and if so, use the RFC 2047 encoding for the whole thing. */ @@ -1002,11 +974,18 @@ for (i = 0, s = phrase; i < len; i++, s++) if ((*s < 32 && *s != '\t') || *s > 126) break; -if (i < len) return parse_quote_2047(phrase, len, headers_charset, buffer, - buffer_size, FALSE); +if (i < len) + return parse_quote_2047(phrase, len, headers_charset, FALSE); /* No non-printers; use the RFC 822 quoting rules */ +if (len <= 0 || len >= INT_MAX/4) + { + return string_copy_taint(CUS"", is_tainted(phrase)); + } + +buffer = store_get((len+1)*4, is_tainted(phrase)); + s = phrase; end = s + len; yield = t = buffer + 1; @@ -1150,9 +1129,12 @@ { 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; + } } } @@ -1173,6 +1155,7 @@ } *t = 0; +store_release_above(t+1); return yield; } @@ -1602,7 +1585,7 @@ else { int start, end, domain; - uschar *recipient = NULL; + const uschar *recipient = NULL; int save = s[len]; s[len] = 0; @@ -1698,8 +1681,8 @@ recipient = ((options & RDO_REWRITE) != 0)? rewrite_address(recipient, TRUE, FALSE, global_rewrite_rules, rewrite_existflags) : - rewrite_address_qualify(recipient, TRUE); - addr = deliver_make_addr(recipient, TRUE); /* TRUE => copy recipient */ + rewrite_address_qualify(recipient, TRUE); /*XXX loses track of const */ + addr = deliver_make_addr(US recipient, TRUE); /* TRUE => copy recipient, so deconst ok */ } /* Restore the final character in the original data, and add to the @@ -1733,8 +1716,8 @@ Returns: points after the processed message-id or NULL on error */ -uschar * -parse_message_id(uschar *str, uschar **yield, uschar **error) +const uschar * +parse_message_id(const uschar *str, uschar **yield, uschar **error) { uschar *domain = NULL; uschar *id; @@ -1774,8 +1757,7 @@ *id++ = 0; store_release_above(id); -str = skip_comment(str); -return str; +return skip_comment(str); } @@ -1793,16 +1775,16 @@ Returns: points after the processed date or NULL on error */ -static uschar * -parse_number(uschar *str, int *n, int digits) +static const uschar * +parse_number(const uschar *str, int *n, int digits) { - *n=0; - while (digits--) +*n=0; +while (digits--) { - if (*str<'0' || *str>'9') return NULL; - *n=10*(*n)+(*str++-'0'); + if (*str<'0' || *str>'9') return NULL; + *n=10*(*n)+(*str++-'0'); } - return str; +return str; } @@ -1819,8 +1801,8 @@ Returns: points after the parsed day or NULL on error */ -static uschar * -parse_day_of_week(uschar *str) +static const uschar * +parse_day_of_week(const uschar * str) { /* day-of-week = ([FWS] day-name) / obs-day-of-week @@ -1835,17 +1817,16 @@ int i; uschar day[4]; -str=skip_comment(str); -for (i=0; i<3; ++i) +str = skip_comment(str); +for (i = 0; i < 3; ++i) { - if ((day[i]=tolower(*str))=='\0') return NULL; + if ((day[i] = tolower(*str)) == '\0') return NULL; ++str; } -day[3]='\0'; -for (i=0; i<7; ++i) if (Ustrcmp(day,day_name[i])==0) break; -if (i==7) return NULL; -str=skip_comment(str); -return str; +day[3] = '\0'; +for (i = 0; i<7; ++i) if (Ustrcmp(day,day_name[i]) == 0) break; +if (i == 7) return NULL; +return skip_comment(str); } @@ -1865,8 +1846,8 @@ Returns: points after the processed date or NULL on error */ -static uschar * -parse_date(uschar *str, int *d, int *m, int *y) +static const uschar * +parse_date(const uschar *str, int *d, int *m, int *y) { /* date = day month year @@ -1888,36 +1869,39 @@ obs-day = [CFWS] 1*2DIGIT [CFWS] */ -uschar *c,*n; +const uschar * s, * n; static const uschar *month_name[]={ US"jan", US"feb", US"mar", US"apr", US"may", US"jun", US"jul", US"aug", US"sep", US"oct", US"nov", US"dec" }; int i; uschar month[4]; -str=skip_comment(str); -if ((str=parse_number(str,d,1))==NULL) return NULL; -if (*str>='0' && *str<='9') *d=10*(*d)+(*str++-'0'); -c=skip_comment(str); -if (c==str) return NULL; -else str=c; -for (i=0; i<3; ++i) if ((month[i]=tolower(*(str+i)))=='\0') return NULL; -month[3]='\0'; -for (i=0; i<12; ++i) if (Ustrcmp(month,month_name[i])==0) break; -if (i==12) return NULL; +str = skip_comment(str); +if ((str = parse_number(str,d,1)) == NULL) return NULL; + +if (*str>='0' && *str<='9') *d = 10*(*d)+(*str++-'0'); +s = skip_comment(str); +if (s == str) return NULL; +str = s; + +for (i = 0; i<3; ++i) if ((month[i]=tolower(*(str+i))) == '\0') return NULL; +month[3] = '\0'; +for (i = 0; i<12; ++i) if (Ustrcmp(month,month_name[i]) == 0) break; +if (i == 12) return NULL; str+=3; -*m=i; -c=skip_comment(str); -if (c==str) return NULL; -else str=c; -if ((n=parse_number(str,y,4))) +*m = i; +s = skip_comment(str); +if (s == str) return NULL; +str=s; + +if ((n = parse_number(str,y,4))) { - str=n; + str = n; if (*y<1900) return NULL; - *y=*y-1900; + *y = *y-1900; } -else if ((n=parse_number(str,y,2))) +else if ((n = parse_number(str,y,2))) { - str=skip_comment(n); - while (*(str-1)==' ' || *(str-1)=='\t') --str; /* match last FWS later */ + str = skip_comment(n); + while (*(str-1) == ' ' || *(str-1) == '\t') --str; /* match last FWS later */ if (*y<50) *y+=100; } else return NULL; @@ -1942,8 +1926,8 @@ Returns: points after the processed time or NULL on error */ -static uschar * -parse_time(uschar *str, int *h, int *m, int *s, int *z) +static const uschar * +parse_time(const uschar *str, int *h, int *m, int *s, int *z) { /* time = time-of-day FWS zone @@ -1978,61 +1962,61 @@ %d107-122 ; upper and lower case */ -uschar *c; +const uschar * c; -str=skip_comment(str); -if ((str=parse_number(str,h,2))==NULL) return NULL; -str=skip_comment(str); +str = skip_comment(str); +if ((str = parse_number(str,h,2)) == NULL) return NULL; +str = skip_comment(str); if (*str!=':') return NULL; ++str; -str=skip_comment(str); -if ((str=parse_number(str,m,2))==NULL) return NULL; -c=skip_comment(str); -if (*str==':') +str = skip_comment(str); +if ((str = parse_number(str,m,2)) == NULL) return NULL; +c = skip_comment(str); +if (*str == ':') { ++str; - str=skip_comment(str); - if ((str=parse_number(str,s,2))==NULL) return NULL; - c=skip_comment(str); + str = skip_comment(str); + if ((str = parse_number(str,s,2)) == NULL) return NULL; + c = skip_comment(str); } -if (c==str) return NULL; +if (c == str) return NULL; else str=c; -if (*str=='+' || *str=='-') +if (*str == '+' || *str == '-') { int neg; - neg=(*str=='-'); + neg = (*str == '-'); ++str; - if ((str=parse_number(str,z,4))==NULL) return NULL; - *z=(*z/100)*3600+(*z%100)*60; - if (neg) *z=-*z; + if ((str = parse_number(str,z,4)) == NULL) return NULL; + *z = (*z/100)*3600+(*z%100)*60; + if (neg) *z = -*z; } else { char zone[5]; - struct { const char *name; int off; } zone_name[10]= + struct { const char *name; int off; } zone_name[10] = { {"gmt",0}, {"ut",0}, {"est",-5}, {"edt",-4}, {"cst",-6}, {"cdt",-5}, {"mst",-7}, {"mdt",-6}, {"pst",-8}, {"pdt",-7}}; int i,j; - for (i=0; i<4; ++i) + for (i = 0; i<4; ++i) { - zone[i]=tolower(*(str+i)); + zone[i] = tolower(*(str+i)); if (zone[i]<'a' || zone[i]>'z') break; } - zone[i]='\0'; - for (j=0; j<10 && strcmp(zone,zone_name[j].name); ++j); + zone[i] = '\0'; + for (j = 0; j<10 && strcmp(zone,zone_name[j].name); ++j); /* Besides zones named in the grammar, RFC 2822 says other alphabetic */ /* time zones should be treated as unknown offsets. */ if (j<10) { - *z=zone_name[j].off*3600; + *z = zone_name[j].off*3600; str+=i; } else if (zone[0]<'a' || zone[1]>'z') return 0; else { while ((*str>='a' && *str<='z') || (*str>='A' && *str<='Z')) ++str; - *z=0; + *z = 0; } } return str; @@ -2052,8 +2036,8 @@ Returns: points after the processed date-time or NULL on error */ -uschar * -parse_date_time(uschar *str, time_t *t) +const uschar * +parse_date_time(const uschar *str, time_t *t) { /* date-time = [ day-of-week "," ] date FWS time [CFWS] @@ -2065,27 +2049,26 @@ char **old_environ; static char gmt0[]="TZ=GMT0"; static char *gmt_env[]={ gmt0, (char*)0 }; -uschar *try; +const uschar * try; -if ((try=parse_day_of_week(str))) +if ((try = parse_day_of_week(str))) { - str=try; + str = try; if (*str!=',') return 0; ++str; } -if ((str=parse_date(str,&tm.tm_mday,&tm.tm_mon,&tm.tm_year))==NULL) return NULL; +if ((str = parse_date(str,&tm.tm_mday,&tm.tm_mon,&tm.tm_year)) == NULL) return NULL; if (*str!=' ' && *str!='\t') return NULL; -while (*str==' ' || *str=='\t') ++str; -if ((str=parse_time(str,&tm.tm_hour,&tm.tm_min,&tm.tm_sec,&zone))==NULL) return NULL; -tm.tm_isdst=0; -old_environ=environ; -environ=gmt_env; -*t=mktime(&tm); -environ=old_environ; -if (*t==-1) return NULL; +while (*str == ' ' || *str == '\t') ++str; +if ((str = parse_time(str,&tm.tm_hour,&tm.tm_min,&tm.tm_sec,&zone)) == NULL) return NULL; +tm.tm_isdst = 0; +old_environ = environ; +environ = gmt_env; +*t = mktime(&tm); +environ = old_environ; +if (*t == -1) return NULL; *t-=zone; -str=skip_comment(str); -return str; +return skip_comment(str); } @@ -2102,7 +2085,6 @@ { int start, end, domain; uschar buffer[1024]; -uschar outbuff[1024]; big_buffer = store_malloc(big_buffer_size); @@ -2115,8 +2097,7 @@ { buffer[Ustrlen(buffer)-1] = 0; if (buffer[0] == 0) break; - printf("%s\n", CS parse_fix_phrase(buffer, Ustrlen(buffer), outbuff, - sizeof(outbuff))); + printf("%s\n", CS parse_fix_phrase(buffer, Ustrlen(buffer))); } printf("Testing parse_extract_address without group syntax and without UTF-8\n"); diff -urN ../exim-4.94.orig/src/pdkim/pdkim.c ./src/pdkim/pdkim.c --- ../exim-4.94.orig/src/pdkim/pdkim.c 2020-05-30 23:35:38.000000000 +0300 +++ ./src/pdkim/pdkim.c 2021-05-15 15:06:16.106605000 +0300 @@ -107,7 +107,7 @@ }; -static blob lineending = {.data = US"\r\n", .len = 2}; +static const blob lineending = {.data = US"\r\n", .len = 2}; /* -------------------------------------------------------------------------- */ uschar * @@ -720,9 +720,11 @@ 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 @@ } /* 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; @@ -822,7 +825,7 @@ /* 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("DKIM [%s] Body hash compared OK\n", sig->domain); @@ -1003,7 +1006,7 @@ 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'; @@ -1111,14 +1114,14 @@ /* * RFC 5322 specifies that header line length SHOULD be no more than 78 - * lets make it so! * pdkim_headcat * - * returns uschar * (not nul-terminated) + * Returns gstring (not nul-terminated) appending to one supplied * * col: this int holds and receives column number (octets since last '\n') * str: partial string to append to - * pad: padding, split line or space after before or after eg: ";" + * pad: padding, split line or space after before or after eg: ";". + * Only the initial charater is used. * intro: - must join to payload eg "h=", usually the tag name * payload: eg base64 data - long data can be split arbitrarily. * @@ -1127,7 +1130,7 @@ * pairs and inside long values. it also always spaces or breaks after the * "pad" * - * no guarantees are made for output given out-of range input. like tag + * No guarantees are made for output given out-of range input. like tag * names longer than 78, or bogus col. Input is assumed to be free of line breaks. */ @@ -1135,93 +1138,65 @@ pdkim_headcat(int * col, gstring * str, const uschar * pad, const uschar * intro, const uschar * payload) { -size_t l; +int len, chomp, padded = 0; -if (pad) - { - l = Ustrlen(pad); - if (*col + l > 78) - str = pdkim_hdr_cont(str, col); - str = string_catn(str, pad, l); - *col += l; - } +/* If we can fit at least the pad at the end of current line, do it now. +Otherwise, wrap if there is a pad. */ -l = (pad?1:0) + (intro?Ustrlen(intro):0); - -if (*col + l > 78) - { /*can't fit intro - start a new line to make room.*/ - str = pdkim_hdr_cont(str, col); - l = intro?Ustrlen(intro):0; - } - -l += payload ? Ustrlen(payload):0 ; - -while (l>77) - { /* this fragment will not fit on a single line */ - if (pad) +if (pad) + if (*col + 1 <= 78) { - str = string_catn(str, US" ", 1); - *col += 1; - pad = NULL; /* only want this once */ - l--; + str = string_catn(str, pad, 1); + (*col)++; + pad = NULL; + padded = 1; } + else + str = pdkim_hdr_cont(str, col); - if (intro) - { - size_t sl = Ustrlen(intro); +/* Special case: if the whole addition does not fit at the end of the current +line, but could fit on a new line, wrap to give it its full, dedicated line. */ - str = string_catn(str, intro, sl); - *col += sl; - l -= sl; - intro = NULL; /* only want this once */ - } - - if (payload) - { - size_t sl = Ustrlen(payload); - size_t chomp = *col+sl < 77 ? sl : 78-*col; - - str = string_catn(str, payload, chomp); - *col += chomp; - payload += chomp; - l -= chomp-1; - } - - /* the while precondition tells us it didn't fit. */ - str = pdkim_hdr_cont(str, col); - } - -if (*col + l > 78) +len = (pad ? 2 : padded) + + (intro ? Ustrlen(intro) : 0) + + (payload ? Ustrlen(payload) : 0); +if (len <= 77 && *col+len > 78) { str = pdkim_hdr_cont(str, col); - pad = NULL; + padded = 0; } +/* Either we already dealt with the pad or we know there is room */ + if (pad) { + str = string_catn(str, pad, 1); str = string_catn(str, US" ", 1); - *col += 1; - pad = NULL; + *col += 2; } - -if (intro) +else if (padded && *col < 78) { - size_t sl = Ustrlen(intro); - - str = string_catn(str, intro, sl); - *col += sl; - l -= sl; - intro = NULL; + str = string_catn(str, US" ", 1); + (*col)++; } -if (payload) - { - size_t sl = Ustrlen(payload); +/* Call recursively with intro as payload: it gets the same, special treatment +(that is, not split if < 78). */ - str = string_catn(str, payload, sl); - *col += sl; - } +if (intro) + str = pdkim_headcat(col, str, NULL, NULL, intro); +if (payload) + for (len = Ustrlen(payload); len; len -= chomp) + { + if (*col >= 78) + str = pdkim_hdr_cont(str, col); + chomp = *col+len > 78 ? 78 - *col : len; + str = string_catn(str, payload, chomp); + *col += chomp; + payload += chomp; + } + return str; } @@ -1352,7 +1327,8 @@ int excess = p->key.len - 32; if (excess > 0) { - DEBUG(D_acl) debug_printf("DKIM: unexpected pubkey len %lu\n", p->key.len); + DEBUG(D_acl) + debug_printf("DKIM: unexpected pubkey len %lu\n", (unsigned long) p->key.len); p->key.data += excess; p->key.len = 32; } } diff -urN ../exim-4.94.orig/src/priv.c ./src/priv.c --- ../exim-4.94.orig/src/priv.c 1970-01-01 03:00:00.000000000 +0300 +++ ./src/priv.c 2021-05-15 15:06:16.107284000 +0300 @@ -0,0 +1,76 @@ +#include "exim.h" +#include +#include +#include + +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! */ + +void +priv_drop_temp(const uid_t temp_uid, const gid_t temp_gid) +{ +if (priv_state != PRIV_RESTORED) + log_write(0, LOG_PANIC_DIE, "priv_drop_temp: unexpected priv_state %d != %d", priv_state, PRIV_RESTORED); + +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) + log_write(0, LOG_PANIC_DIE, "getgroups: %s", strerror(errno)); + + if (priv_ngroups > 0 && setgroups(1, &temp_gid) != 0) + log_write(0, LOG_PANIC_DIE, "setgroups: %s", strerror(errno)); + if (setegid(temp_gid) != 0) + log_write(0, LOG_PANIC_DIE, "setegid(%d): %s", temp_gid, strerror(errno)); + if (seteuid(temp_uid) != 0) + log_write(0, LOG_PANIC_DIE, "seteuid(%d): %s", temp_uid, strerror(errno)); + + if (geteuid() != temp_uid) + log_write(0, LOG_PANIC_DIE, "getdeuid() != %d", temp_uid); + if (getegid() != temp_gid) + log_write(0, LOG_PANIC_DIE, "getegid() != %d", temp_gid); + } + +priv_state = PRIV_DROPPED; +} + +/* Inspired by OpenSSH's restore_uid(). Thanks! */ + +void +priv_restore(void) +{ +if (priv_state != PRIV_DROPPED) + log_write(0, LOG_PANIC_DIE, "priv_restore: unexpected priv_state %d != %d", priv_state, PRIV_DROPPED); +priv_state = PRIV_RESTORING; + +if (priv_euid == root_uid) + { + if (seteuid(priv_euid) != 0) + log_write(0, LOG_PANIC_DIE, "seteuid(%d): %s", priv_euid, strerror(errno)); + if (setegid(priv_egid) != 0) + log_write(0, LOG_PANIC_DIE, "setegid(%d): %s", priv_egid, strerror(errno)); + if (priv_ngroups > 0 && setgroups(priv_ngroups, priv_groups) != 0) + log_write(0, LOG_PANIC_DIE, "setgroups: %s", strerror(errno)); + + if (geteuid() != priv_euid) + log_write(0, LOG_PANIC_DIE, "getdeuid() != %d", priv_euid); + if (getegid() != priv_egid) + log_write(0, LOG_PANIC_DIE, "getdegid() != %d", priv_egid); + } + +priv_state = PRIV_RESTORED; +} diff -urN ../exim-4.94.orig/src/queue.c ./src/queue.c --- ../exim-4.94.orig/src/queue.c 2020-05-30 23:35:38.000000000 +0300 +++ ./src/queue.c 2021-05-15 15:06:16.107623000 +0300 @@ -393,12 +393,18 @@ 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 += Ustrlen(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 += Ustrlen(CCS p); + } log_detail = string_copy(big_buffer); if (*queue_name) diff -urN ../exim-4.94.orig/src/rda.c ./src/rda.c --- ../exim-4.94.orig/src/rda.c 2020-05-30 23:35:38.000000000 +0300 +++ ./src/rda.c 2021-05-15 15:06:16.107876000 +0300 @@ -618,9 +618,14 @@ if ((pid = exim_fork(US"router-interpret")) == 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 -urN ../exim-4.94.orig/src/readconf.c ./src/readconf.c --- ../exim-4.94.orig/src/readconf.c 2020-05-30 23:35:38.000000000 +0300 +++ ./src/readconf.c 2021-05-15 15:06:16.108815000 +0300 @@ -416,7 +416,7 @@ void options_auths(void) { -uschar buf[64]; +uschar buf[EXIM_DRIVERNAME_MAX]; options_from_list(optionlist_auths, optionlist_auths_size, US"AUTHENTICATORS", NULL); @@ -433,7 +433,7 @@ void options_logging(void) { -uschar buf[64]; +uschar buf[EXIM_DRIVERNAME_MAX]; for (bit_table * bp = log_options; bp < log_options + log_options_count; bp++) { @@ -678,7 +678,7 @@ BOOL macro_read_assignment(uschar *s) { -uschar name[64]; +uschar name[EXIM_DRIVERNAME_MAX]; int namelen = 0; BOOL redef = FALSE; macro_item *m; @@ -1172,15 +1172,25 @@ readconf_readname(uschar *name, int len, uschar *s) { int p = 0; +BOOL broken = FALSE; if (isalpha(Uskip_whitespace(&s))) while (isalnum(*s) || *s == '_') { if (p < len-1) name[p++] = *s; + else { + broken = TRUE; + break; + } s++; } name[p] = 0; +if (broken) { + log_write(0, LOG_PANIC_DIE|LOG_CONFIG_IN, + "exim item name too long (>%d), unable to use \"%s\" (truncated)", + len, name); +} Uskip_whitespace(&s); return s; } @@ -1305,7 +1315,7 @@ */ static optionlist * -find_option(uschar *name, optionlist *ol, int last) +find_option(const uschar *name, optionlist *ol, int last) { int first = 0; while (last > first) @@ -1344,10 +1354,10 @@ */ static BOOL * -get_set_flag(uschar *name, optionlist *oltop, int last, void *data_block) +get_set_flag(const uschar *name, optionlist *oltop, int last, void *data_block) { optionlist *ol; -uschar name2[64]; +uschar name2[EXIM_DRIVERNAME_MAX]; sprintf(CS name2, "*set_%.50s", name); if (!(ol = find_option(name2, oltop, last))) log_write(0, LOG_MAIN|LOG_PANIC_DIE, @@ -1546,7 +1556,7 @@ { if (flags & opt_fn_print_label) printf("%s = ", name); printf("%s\n", smtp_receive_timeout_s - ? string_printing2(smtp_receive_timeout_s, FALSE) + ? string_printing2(smtp_receive_timeout_s, SP_TAB) : readconf_printtime(smtp_receive_timeout)); } else if (*str == '$') @@ -1620,8 +1630,8 @@ uschar *sptr; uschar *s = buffer; uschar **str_target; -uschar name[64]; -uschar name2[64]; +uschar name[EXIM_DRIVERNAME_MAX]; +uschar name2[EXIM_DRIVERNAME_MAX]; /* There may be leading spaces; thereafter, we expect an option name starting with a letter. */ @@ -2080,7 +2090,7 @@ case opt_bool_set: if (*s != 0) { - s = readconf_readname(name2, 64, s); + s = readconf_readname(name2, EXIM_DRIVERNAME_MAX, s); if (strcmpic(name2, US"true") == 0 || strcmpic(name2, US"yes") == 0) boolvalue = TRUE; else if (strcmpic(name2, US"false") == 0 || strcmpic(name2, US"no") == 0) @@ -2417,7 +2427,7 @@ */ static BOOL -print_ol(optionlist *ol, uschar *name, void *options_block, +print_ol(optionlist *ol, const uschar *name, void *options_block, optionlist *oltop, int last, BOOL no_labels) { struct passwd *pw; @@ -2427,7 +2437,7 @@ uid_t *uidlist; gid_t *gidlist; uschar *s; -uschar name2[64]; +uschar name2[EXIM_DRIVERNAME_MAX]; if (!ol) { @@ -2463,7 +2473,7 @@ case opt_rewrite: /* Show the text value */ s = *(USS value); if (!no_labels) printf("%s = ", name); - printf("%s\n", s ? string_printing2(s, FALSE) : US""); + printf("%s\n", s ? string_printing2(s, SP_TAB) : US""); break; case opt_int: @@ -2727,7 +2737,7 @@ */ BOOL -readconf_print(uschar *name, uschar *type, BOOL no_labels) +readconf_print(const uschar *name, uschar *type, BOOL no_labels) { BOOL names_only = FALSE; optionlist *ol2 = NULL; @@ -2866,7 +2876,7 @@ else return print_ol(find_option(name, - optionlist_config, nelem(optionlist_config)), + optionlist_config, nelem(optionlist_config)), name, NULL, optionlist_config, nelem(optionlist_config), no_labels); } @@ -3698,7 +3708,7 @@ while ((buffer = get_config_line())) { - uschar name[64]; + uschar name[EXIM_DRIVERNAME_MAX]; uschar *s; /* Read the first name on the line and test for the start of a new driver. A @@ -4226,7 +4236,7 @@ while(acl_line) { - uschar name[64]; + uschar name[EXIM_DRIVERNAME_MAX]; tree_node *node; uschar *error; diff -urN ../exim-4.94.orig/src/receive.c ./src/receive.c --- ../exim-4.94.orig/src/receive.c 2020-05-30 23:35:38.000000000 +0300 +++ ./src/receive.c 2021-05-15 15:06:16.109886000 +0300 @@ -489,6 +489,13 @@ { 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), FALSE); if (oldlist != NULL) @@ -1784,7 +1791,7 @@ ids, and fractions of a second are required. See the comments that precede the message id creation below. */ -(void)gettimeofday(&message_id_tv, NULL); +exim_gettime(&message_id_tv); /* For other uses of the received time we can operate with granularity of one second, and for that we use the global variable received_time. This is for @@ -2123,7 +2130,8 @@ if (newsender) { if (domain == 0 && newsender[0] != 0) - newsender = rewrite_address_qualify(newsender, FALSE); + /* deconst ok as newsender was not const */ + newsender = US rewrite_address_qualify(newsender, FALSE); if (filter_test != FTEST_NONE || receive_check_set_sender(newsender)) { @@ -2503,7 +2511,7 @@ { while (recipients_count-- > 0) { - uschar *s = rewrite_address(recipients_list[recipients_count].address, + const uschar * s = rewrite_address(recipients_list[recipients_count].address, TRUE, TRUE, global_rewrite_rules, rewrite_existflags); tree_add_nonrecipient(s); } @@ -2554,11 +2562,12 @@ &domain, FALSE); #ifdef SUPPORT_I18N - if (string_is_utf8(recipient)) - message_smtputf8 = TRUE; - else - allow_utf8_domains = b; + if (recipient) + if (string_is_utf8(recipient)) message_smtputf8 = TRUE; + else allow_utf8_domains = b; } +#else + ; #endif /* Keep a list of all the bad addresses so we can send a single @@ -2790,8 +2799,8 @@ recipient is TRUE). */ for (int i = 0; i < recipients_count; i++) - recipients_list[i].address = - rewrite_address(recipients_list[i].address, TRUE, TRUE, + recipients_list[i].address = /* deconst ok as src was not cont */ + US rewrite_address(recipients_list[i].address, TRUE, TRUE, global_rewrite_rules, rewrite_existflags); /* If there is no From: header, generate one for local (without @@ -2966,7 +2975,8 @@ if (global_rewrite_rules && !sender_address_unrewritten && *sender_address) { - sender_address = rewrite_address(sender_address, FALSE, TRUE, + /* deconst ok as src was not const */ + sender_address = US rewrite_address(sender_address, FALSE, TRUE, global_rewrite_rules, rewrite_existflags); DEBUG(D_receive|D_rewrite) debug_printf("rewritten sender = %s\n", sender_address); @@ -4004,7 +4014,7 @@ if (LOGGING(tls_peerdn) && tls_in.peerdn) g = string_append(g, 3, US" DN=\"", string_printing(tls_in.peerdn), US"\""); if (LOGGING(tls_sni) && tls_in.sni) - g = string_append(g, 3, US" SNI=\"", string_printing(tls_in.sni), US"\""); + g = string_append(g, 2, US" SNI=", string_printing2(tls_in.sni, SP_TAB|SP_SPACE)); #endif if (sender_host_authenticated) diff -urN ../exim-4.94.orig/src/rewrite.c ./src/rewrite.c --- ../exim-4.94.orig/src/rewrite.c 2020-05-30 23:35:38.000000000 +0300 +++ ./src/rewrite.c 2021-05-15 15:06:16.110551000 +0300 @@ -59,12 +59,12 @@ Returns: fully-qualified address */ -uschar * -rewrite_address_qualify(uschar *s, BOOL is_recipient) +const uschar * +rewrite_address_qualify(const uschar *s, BOOL is_recipient) { -return (parse_find_at(s) != NULL)? s : - string_sprintf("%s@%s", s, - is_recipient? qualify_domain_recipient : qualify_domain_sender); +return parse_find_at(s) + ? s : string_sprintf("%s@%s", s, + is_recipient ? qualify_domain_recipient : qualify_domain_sender); } @@ -96,12 +96,12 @@ rewritten address is returned, not just the active bit. */ -uschar * -rewrite_one(uschar *s, int flag, BOOL *whole, BOOL add_header, uschar *name, +const uschar * +rewrite_one(const uschar *s, int flag, BOOL *whole, BOOL add_header, uschar *name, rewrite_rule *rewrite_rules) { -uschar *yield = s; -uschar *subject = s; +const uschar *yield = s; +const uschar *subject = s; uschar *domain = NULL; BOOL done = FALSE; int rule_number = 1; @@ -119,7 +119,8 @@ int count = 0; uschar *save_localpart; const uschar *save_domain; - uschar *error, *new, *newparsed; + uschar *error, *new; + const uschar * newparsed; /* Ensure that the flag matches the flags in the rule. */ @@ -185,7 +186,7 @@ set up as an expansion variable */ domain[-1] = 0; - deliver_localpart = subject; + deliver_localpart = US subject; deliver_domain = domain; new = expand_string(rule->replacement); @@ -292,17 +293,12 @@ uschar *p1 = new + start - 1; uschar *p2 = new + end + 1; const uschar *pf1, *pf2; - uschar buff1[256], buff2[256]; while (p1 > new && p1[-1] == ' ') p1--; - pf1 = parse_fix_phrase(new, p1 - new, buff1, sizeof(buff1)); + pf1 = parse_fix_phrase(new, p1 - new); while (*p2 == ' ') p2++; - pf2 = parse_fix_phrase(p2, Ustrlen(p2), buff2, sizeof(buff2)); + pf2 = parse_fix_phrase(p2, Ustrlen(p2)); - /* Note that pf1 and pf2 are NOT necessarily buff1 and buff2. For - a non-RFC 2047 phrase that does not need to be RFC 2822 quoted, they - will be buff1+1 and buff2+1. */ - start = Ustrlen(pf1) + start + new - p1; end = start + Ustrlen(newparsed); new = string_sprintf("%s%.*s%s", pf1, (int)(p2 - p1), p1, pf2); @@ -395,15 +391,16 @@ Returns: possibly rewritten address */ -uschar * -rewrite_address(uschar *s, BOOL is_recipient, BOOL add_header, +const uschar * +rewrite_address(const uschar *s, BOOL is_recipient, BOOL add_header, rewrite_rule *rewrite_rules, int existflags) { -int flag = is_recipient? rewrite_envto : rewrite_envfrom; +int flag = is_recipient ? rewrite_envto : rewrite_envfrom; + s = rewrite_address_qualify(s, is_recipient); -if ((existflags & flag) != 0) +if (existflags & flag) { - uschar *new = rewrite_one(s, flag, NULL, add_header, is_recipient? + const uschar *new = rewrite_one(s, flag, NULL, add_header, is_recipient? US"original-recipient" : US"sender", rewrite_rules); if (new != s) s = new; } @@ -474,8 +471,9 @@ { uschar *sprev; uschar *ss = parse_find_address_end(s, FALSE); - uschar *recipient, *new, *errmess; + uschar *recipient, *new; rmark loop_reset_point = store_mark(); + uschar *errmess = NULL; BOOL changed = FALSE; int terminator = *ss; int start, end, domain; @@ -486,16 +484,42 @@ *ss = 0; recipient = parse_extract_address(s,&errmess,&start,&end,&domain,FALSE); + *ss = terminator; sprev = s; s = ss + (terminator? 1:0); while (isspace(*s)) s++; /* There isn't much we can do for syntactic disasters at this stage. - Pro tem (possibly for ever) ignore them. */ + Pro tem (possibly for ever) ignore them. + If we got nothing, then there was any sort of error: non-parsable address, + empty address, overlong addres. Sometimes the result matters, sometimes not. + It seems this function is called for *any* header we see. */ + if (!recipient) { +#if 0 + /* FIXME: + This was(!) an attempt tho handle empty rewrits, but seemingly it + needs more effort to decide if the returned empty address matters. + Now this will now break test 471 again. + + 471 fails now because it uses an overlong address, for wich parse_extract_address() + returns an empty address (which was not expected). + + Checking the output and exit if rewrite_rules or routed_old are present + isn't a good idea either: It's enough to have *any* rewrite rule + in the configuration plus "To: undisclosed recpients:;" to exit(), which + is not what we want. + */ + + if (rewrite_rules || routed_old) + { + log_write(0, LOG_MAIN, "rewrite: %s", errmess); + exim_exit(EXIT_FAILURE); + } +#endif loop_reset_point = store_reset(loop_reset_point); continue; } @@ -534,7 +558,8 @@ { BOOL is_recipient = (flag & (rewrite_sender | rewrite_from | rewrite_replyto)) == 0; - new = rewrite_address_qualify(recipient, is_recipient); + /* deconst ok as recipient was notconst */ + new = US rewrite_address_qualify(recipient, is_recipient); changed = (new != recipient); recipient = new; @@ -557,7 +582,8 @@ if ((existflags & flag) != 0) { BOOL whole; - new = rewrite_one(recipient, flag, &whole, FALSE, NULL, rewrite_rules); + /* deconst ok as recipient was notconst */ + new = US rewrite_one(recipient, flag, &whole, FALSE, NULL, rewrite_rules); if (new != recipient) { changed = TRUE; @@ -749,7 +775,8 @@ Returns: nothing */ -void rewrite_test(uschar *s) +void +rewrite_test(const uschar *s) { uschar *recipient, *error; int start, end, domain; @@ -766,8 +793,8 @@ if ((rewrite_existflags & rewrite_smtp) != 0) { - uschar *new = rewrite_one(s, rewrite_smtp|rewrite_smtp_sender, NULL, FALSE, - US"", global_rewrite_rules); + const uschar * new = rewrite_one(s, rewrite_smtp|rewrite_smtp_sender, NULL, + FALSE, US"", global_rewrite_rules); if (new != s) { if (*new == 0) @@ -800,7 +827,7 @@ { BOOL whole = FALSE; int flag = 1 << i; - uschar *new = rewrite_one(recipient, flag, &whole, FALSE, US"", + const uschar * new = rewrite_one(recipient, flag, &whole, FALSE, US"", global_rewrite_rules); printf("%s: ", rrname[i]); if (*new == 0) diff -urN ../exim-4.94.orig/src/routers/rf_queue_add.c ./src/routers/rf_queue_add.c --- ../exim-4.94.orig/src/routers/rf_queue_add.c 2020-05-30 23:35:38.000000000 +0300 +++ ./src/routers/rf_queue_add.c 2021-05-15 15:06:16.114356000 +0300 @@ -98,7 +98,7 @@ " errors_to=%s\n", addr->transport ? addr->transport->name : US"", addr->local_part, addr->domain, addr->prop.errors_address); - debug_printf(" domain_data=%s localpart_data=%s\n", addr->prop.domain_data, + debug_printf(" domain_data=%s local_part_data=%s\n", addr->prop.domain_data, addr->prop.localpart_data); } diff -urN ../exim-4.94.orig/src/sieve.c ./src/sieve.c --- ../exim-4.94.orig/src/sieve.c 2020-05-30 23:35:38.000000000 +0300 +++ ./src/sieve.c 2021-05-15 15:06:16.116031000 +0300 @@ -3087,11 +3087,8 @@ if ((pid = child_open_exim2(&fd, envelope_from, envelope_from, US"sieve-notify")) >= 1) { - FILE *f; - uschar *buffer; - int buffer_capacity; + FILE * f = fdopen(fd, "wb"); - f = fdopen(fd, "wb"); fprintf(f,"From: %s\n", from.length == -1 ? expand_string(US"$local_part_prefix$local_part$local_part_suffix@$domain") : from.character); @@ -3104,12 +3101,9 @@ message.character=US"Notification"; message.length=Ustrlen(message.character); } - /* Allocation is larger than necessary, but enough even for split MIME words */ - buffer_capacity = 32 + 4*message.length; - buffer=store_get(buffer_capacity, TRUE); if (message.length != -1) fprintf(f, "Subject: %s\n", parse_quote_2047(message.character, - message.length, US"utf-8", buffer, buffer_capacity, TRUE)); + message.length, US"utf-8", TRUE)); fprintf(f,"\n"); if (body.length>0) fprintf(f,"%s\n",body.character); fflush(f); @@ -3263,8 +3257,6 @@ if (exec) { address_item *addr; - uschar *buffer; - int buffer_capacity; md5 base; uschar digest[16]; uschar hexdigest[33]; @@ -3342,11 +3334,8 @@ addr->reply->from = expand_string(US"$local_part@$domain"); else addr->reply->from = from.character; - /* Allocation is larger than necessary, but enough even for split MIME words */ - buffer_capacity=32+4*subject.length; - buffer = store_get(buffer_capacity, is_tainted(subject.character)); /* deconst cast safe as we pass in a non-const item */ - addr->reply->subject = US parse_quote_2047(subject.character, subject.length, US"utf-8", buffer, buffer_capacity, TRUE); + addr->reply->subject = US parse_quote_2047(subject.character, subject.length, US"utf-8", TRUE); addr->reply->oncelog = string_from_gstring(once); addr->reply->once_repeat=days*86400; diff -urN ../exim-4.94.orig/src/smtp_in.c ./src/smtp_in.c --- ../exim-4.94.orig/src/smtp_in.c 2020-05-30 23:35:38.000000000 +0300 +++ ./src/smtp_in.c 2021-05-15 15:06:16.117453000 +0300 @@ -593,6 +593,11 @@ } +/* 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 underlying smtp_getc or tls_getc both for that and for getting the @@ -624,9 +629,7 @@ 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; @@ -730,9 +733,7 @@ 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 @@ -765,9 +766,7 @@ 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) { @@ -777,8 +776,45 @@ } +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; +} + /************************************************* * SMTP version of ungetc() * *************************************************/ @@ -795,6 +831,9 @@ 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; } @@ -1811,7 +1850,7 @@ if (LOGGING(tls_peerdn) && tls_in.peerdn) g = string_append(g, 3, US" DN=\"", string_printing(tls_in.peerdn), US"\""); if (LOGGING(tls_sni) && tls_in.sni) - g = string_append(g, 3, US" SNI=\"", string_printing(tls_in.sni), US"\""); + g = string_append(g, 2, US" SNI=", string_printing2(tls_in.sni, SP_TAB|SP_SPACE)); return g; } #endif @@ -1995,29 +2034,35 @@ 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; @@ -2243,9 +2288,11 @@ /* Apply SMTP rewrite */ - raw_sender = ((rewrite_existflags & rewrite_smtp) != 0)? - rewrite_one(smtp_cmd_data, rewrite_smtp|rewrite_smtp_sender, NULL, FALSE, - US"", global_rewrite_rules) : smtp_cmd_data; + raw_sender = rewrite_existflags & rewrite_smtp + /* deconst ok as smtp_cmd_data was not const */ + ? US rewrite_one(smtp_cmd_data, rewrite_smtp|rewrite_smtp_sender, NULL, + FALSE, US"", global_rewrite_rules) + : smtp_cmd_data; /* Extract the address; the TRUE flag allows <> as valid */ @@ -2265,7 +2312,8 @@ && sender_address[0] != 0 && sender_address[0] != '@') if (f.allow_unqualified_sender) { - sender_address = rewrite_address_qualify(sender_address, FALSE); + /* deconst ok as sender_address was not const */ + sender_address = US rewrite_address_qualify(sender_address, FALSE); DEBUG(D_receive) debug_printf("unqualified address %s accepted " "and rewritten\n", raw_sender); } @@ -2304,7 +2352,8 @@ recipient address */ recipient = rewrite_existflags & rewrite_smtp - ? rewrite_one(smtp_cmd_data, rewrite_smtp, NULL, FALSE, US"", + /* deconst ok as smtp_cmd_data was not const */ + ? US rewrite_one(smtp_cmd_data, rewrite_smtp, NULL, FALSE, US"", global_rewrite_rules) : smtp_cmd_data; @@ -2323,7 +2372,8 @@ { DEBUG(D_receive) debug_printf("unqualified address %s accepted\n", recipient); - recipient = rewrite_address_qualify(recipient, TRUE); + /* deconst ok as recipient was not const */ + recipient = US rewrite_address_qualify(recipient, TRUE); } /* The function moan_smtp_batch() does not return. */ else @@ -2564,6 +2614,9 @@ receive_feof = smtp_feof; receive_ferror = smtp_ferror; receive_smtp_buffered = smtp_buffered; +lwr_receive_getc = NULL; +lwr_receive_getbuf = NULL; +lwr_receive_ungetc = NULL; smtp_inptr = smtp_inend = smtp_inbuffer; smtp_had_eof = smtp_had_error = 0; @@ -3834,7 +3887,8 @@ DEBUG(D_receive) debug_printf("unqualified address %s accepted\n", *recipient); rd = Ustrlen(recipient) + 1; - *recipient = rewrite_address_qualify(*recipient, TRUE); + /* deconst ok as *recipient was not const */ + *recipient = US rewrite_address_qualify(*recipient, TRUE); return rd; } smtp_printf("501 %s: recipient address must contain a domain\r\n", FALSE, @@ -3945,6 +3999,14 @@ cmd_list[CMD_LIST_STARTTLS].is_mail_cmd = TRUE; #endif +if (lwr_receive_getc != NULL) + { + /* This should have already happened, but if we've gotten confused, + force a reset here. */ + DEBUG(D_receive) debug_printf("WARNING: smtp_setup_msg had to restore receive functions to lowers\n"); + bdat_pop_receive_functions(); + } + /* Set the local signal handler for SIGTERM - it tries to end off tidily */ had_command_sigterm = 0; @@ -4833,7 +4895,8 @@ TRUE flag allows "<>" as a sender address. */ raw_sender = rewrite_existflags & rewrite_smtp - ? rewrite_one(smtp_cmd_data, rewrite_smtp, NULL, FALSE, US"", + /* deconst ok as smtp_cmd_data was not const */ + ? US rewrite_one(smtp_cmd_data, rewrite_smtp, NULL, FALSE, US"", global_rewrite_rules) : smtp_cmd_data; @@ -4895,7 +4958,8 @@ if (f.allow_unqualified_sender) { sender_domain = Ustrlen(sender_address) + 1; - sender_address = rewrite_address_qualify(sender_address, FALSE); + /* deconst ok as sender_address was not const */ + sender_address = US rewrite_address_qualify(sender_address, FALSE); DEBUG(D_receive) debug_printf("unqualified address %s accepted\n", raw_sender); } @@ -4965,6 +5029,10 @@ case RCPT_CMD: HAD(SCH_RCPT); + /* We got really to many recipients. A check against configured + limits is done later */ + 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; @@ -5087,7 +5155,8 @@ as a recipient address */ recipient = rewrite_existflags & rewrite_smtp - ? rewrite_one(smtp_cmd_data, rewrite_smtp, NULL, FALSE, US"", + /* deconst ok as smtp_cmd_data was not const */ + ? US rewrite_one(smtp_cmd_data, rewrite_smtp, NULL, FALSE, US"", global_rewrite_rules) : smtp_cmd_data; @@ -5120,7 +5189,7 @@ /* Check maximum allowed */ - if (rcpt_count > recipients_max && recipients_max > 0) + if (rcpt_count+1 < 0 || rcpt_count > recipients_max && recipients_max > 0) { if (recipients_max_reject) { @@ -5265,16 +5334,7 @@ DEBUG(D_receive) debug_printf("chunking state %d, %d bytes\n", (int)chunking_state, chunking_data_left); - /* 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. */ - lwr_receive_getc = receive_getc; - lwr_receive_getbuf = receive_getbuf; - lwr_receive_ungetc = receive_ungetc; - - receive_getc = bdat_getc; - receive_ungetc = bdat_ungetc; - + f.bdat_readers_wanted = TRUE; /* FIXME: redundant vs chunking_state? */ f.dot_ends = FALSE; goto DATA_BDAT; @@ -5283,6 +5343,7 @@ case DATA_CMD: HAD(SCH_DATA); f.dot_ends = TRUE; + f.bdat_readers_wanted = FALSE; DATA_BDAT: /* Common code for DATA and BDAT */ #ifndef DISABLE_PIPE_CONNECT @@ -5303,15 +5364,18 @@ } 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"); if (chunking_state > CHUNKING_OFFERED) + { + bdat_push_receive_functions(); bdat_flush_data(); + } break; } @@ -5320,6 +5384,12 @@ sender_address = NULL; /* This will allow a new MAIL without RSET */ sender_address_unrewritten = NULL; smtp_printf("554 Too many recipients\r\n", FALSE); + + if (chunking_state > CHUNKING_OFFERED) + { + bdat_push_receive_functions(); + bdat_flush_data(); + } break; } @@ -5357,6 +5427,9 @@ "354 Enter message, ending with \".\" on a line by itself\r\n", FALSE); } + if (f.bdat_readers_wanted) + bdat_push_receive_functions(); + #ifdef TCP_QUICKACK if (smtp_in) /* all ACKs needed to ramp window up for bulk data */ (void) setsockopt(fileno(smtp_in), IPPROTO_TCP, TCP_QUICKACK, @@ -5935,12 +6008,14 @@ g = string_append(g, 2, US";\n\tauth=pass (", sender_host_auth_pubname); -if (Ustrcmp(sender_host_auth_pubname, "tls") != 0) - g = string_append(g, 2, US") smtp.auth=", authenticated_id); -else if (authenticated_id) - g = string_append(g, 2, US") x509.auth=", authenticated_id); +if (Ustrcmp(sender_host_auth_pubname, "tls") == 0) + g = authenticated_id + ? string_append(g, 2, US") x509.auth=", authenticated_id) + : string_cat(g, US") reason=x509.auth"); else - g = string_catn(g, US") reason=x509.auth", 17); + g = authenticated_id + ? string_append(g, 2, US") smtp.auth=", authenticated_id) + : string_cat(g, US", no id saved)"); if (authenticated_sender) g = string_append(g, 2, US" smtp.mailfrom=", authenticated_sender); diff -urN ../exim-4.94.orig/src/smtp_out.c ./src/smtp_out.c --- ../exim-4.94.orig/src/smtp_out.c 2020-05-30 23:35:38.000000000 +0300 +++ ./src/smtp_out.c 2021-05-15 15:06:16.117684000 +0300 @@ -375,7 +375,7 @@ { if (host->port != PORT_NONE) { - HDEBUG(D_transport|D_acl|D_v) + HDEBUG(D_transport|D_acl|D_v) if (port != host->port) debug_printf_indent("Transport port=%d replaced by host-specific port=%d\n", port, host->port); port = host->port; @@ -643,7 +643,7 @@ timelimit deadline for reading the lime, seconds past epoch 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 @@ -684,6 +684,7 @@ { *p = 0; /* Leave malformed line for error message */ errno = ERRNO_SMTPFORMAT; + inblock->ptr = ptr; return -1; } } @@ -709,6 +710,7 @@ /* 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 -urN ../exim-4.94.orig/src/spam.c ./src/spam.c --- ../exim-4.94.orig/src/spam.c 2020-05-30 23:35:38.000000000 +0300 +++ ./src/spam.c 2021-05-15 15:06:16.117945000 +0300 @@ -18,7 +18,7 @@ uschar spam_bar_buffer[128]; uschar spam_action_buffer[32]; uschar spam_report_buffer[32600]; -uschar prev_user_name[128] = ""; +uschar * prev_user_name = NULL; int spam_ok = 0; int spam_rc = 0; uschar *prev_spamd_address_work = NULL; @@ -174,7 +174,7 @@ { sd = spamds[i]; if (!sd->is_failed && sd->priority == pri) - if ((rnd -= sd->weight) <= 0) + if ((rnd -= sd->weight) < 0) return i; } @@ -190,7 +190,6 @@ int sep = 0; const uschar *list = *listptr; uschar *user_name; -uschar user_name_buffer[128]; unsigned long mbox_size; FILE *mbox_file; client_conn_ctx spamd_cctx = {.sock = -1}; @@ -218,9 +217,7 @@ result = 0; /* find the username from the option list */ -if ((user_name = string_nextinlist(&list, &sep, - user_name_buffer, - sizeof(user_name_buffer))) == NULL) +if (!(user_name = string_nextinlist(&list, &sep, NULL, 0))) { /* no username given, this means no scanning should be done */ return FAIL; @@ -396,13 +393,12 @@ } else { /* spamassassin variant */ - (void)string_format(spamd_buffer, - sizeof(spamd_buffer), - "REPORT SPAMC/1.2\r\nUser: %s\r\nContent-length: %ld\r\n\r\n", - user_name, - mbox_size); + int n; + uschar * s = string_sprintf( + "REPORT SPAMC/1.2\r\nUser: %s\r\nContent-length: %ld\r\n\r\n%n", + user_name, mbox_size, &n); /* send our request */ - wrote = send(spamd_cctx.sock, spamd_buffer, Ustrlen(spamd_buffer), 0); + wrote = send(spamd_cctx.sock, s, n, 0); } if (wrote == -1) @@ -633,7 +629,7 @@ prev_spamd_address_work = string_copy(spamd_address_work); /* remember user name and "been here" for it */ -Ustrcpy(prev_user_name, user_name); +prev_user_name = user_name; spam_ok = 1; return override diff -urN ../exim-4.94.orig/src/spool_in.c ./src/spool_in.c --- ../exim-4.94.orig/src/spool_in.c 2020-05-30 23:35:38.000000000 +0300 +++ ./src/spool_in.c 2021-05-15 15:06:16.118520000 +0300 @@ -55,7 +55,7 @@ set_subdir_str(message_subdir, id, i); fname = spool_fname(US"input", message_subdir, id, US"-D"); - DEBUG(D_deliver) debug_printf("Trying spool file %s\n", fname); + DEBUG(D_deliver) debug_printf_indent("Trying spool file %s\n", fname); /* We protect against symlink attacks both in not propagating the * file-descriptor to other processes as we exec, and also ensuring that we @@ -253,7 +253,7 @@ sender_host_address = NULL; sender_host_name = NULL; sender_host_port = 0; -sender_host_authenticated = NULL; +sender_host_authenticated = sender_host_auth_pubname = NULL; sender_ident = NULL; f.sender_local = FALSE; f.sender_set_untrusted = FALSE; @@ -304,7 +304,36 @@ 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, FALSE); + 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 * *************************************************/ @@ -367,7 +396,7 @@ errno = 0; #ifndef COMPILE_UTILITY -DEBUG(D_deliver) debug_printf("reading spool file %s\n", name); +DEBUG(D_deliver) debug_printf_indent("reading spool file %s\n", name); #endif /* COMPILE_UTILITY */ /* The first line of a spool file contains the message id followed by -H (i.e. @@ -430,7 +459,7 @@ #endif #ifndef COMPILE_UTILITY -DEBUG(D_deliver) debug_printf("user=%s uid=%ld gid=%ld sender=%s\n", +DEBUG(D_deliver) debug_printf_indent("user=%s uid=%ld gid=%ld sender=%s\n", originator_login, (long int)originator_uid, (long int)originator_gid, sender_address); #endif @@ -452,26 +481,13 @@ for (;;) { - int len; BOOL tainted; uschar * var; const uschar * p; - 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, FALSE); - 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; tainted = big_buffer[1] == '-'; var = big_buffer + (tainted ? 2 : 1); @@ -580,6 +596,8 @@ host_lookup_deferred = TRUE; else if (Ustrcmp(p, "ost_lookup_failed") == 0) host_lookup_failed = TRUE; + else if (Ustrncmp(p, "ost_auth_pubname", 16) == 0) + sender_host_auth_pubname = string_copy_taint(var + 18, tainted); else if (Ustrncmp(p, "ost_auth", 8) == 0) sender_host_authenticated = string_copy_taint(var + 10, tainted); else if (Ustrncmp(p, "ost_name", 8) == 0) @@ -715,7 +733,7 @@ #ifndef COMPILE_UTILITY DEBUG(D_deliver) - debug_printf("sender_local=%d ident=%s\n", f.sender_local, + debug_printf_indent("sender_local=%d ident=%s\n", f.sender_local, sender_ident ? sender_ident : US"unset"); #endif /* COMPILE_UTILITY */ @@ -743,7 +761,7 @@ goto SPOOL_FORMAT_ERROR; #ifndef COMPILE_UTILITY -DEBUG(D_deliver) debug_printf("recipients_count=%d\n", rcount); +DEBUG(D_deliver) debug_printf_indent("recipients_count=%d\n", rcount); #endif /* COMPILE_UTILITY */ recipients_list_max = rcount; @@ -762,7 +780,7 @@ uschar *errors_to = NULL; uschar *p; - if (Ufgets(big_buffer, big_buffer_size, fp) == NULL) goto SPOOL_READ_ERROR; + if (fgets_big_buffer(fp) == NULL) goto SPOOL_READ_ERROR; nn = Ustrlen(big_buffer); if (nn < 2) goto SPOOL_FORMAT_ERROR; @@ -814,7 +832,7 @@ { int dummy; #if !defined (COMPILE_UTILITY) - DEBUG(D_deliver) debug_printf("**** SPOOL_IN - Exim 3 spool file\n"); + DEBUG(D_deliver) debug_printf_indent("**** SPOOL_IN - Exim 3 spool file\n"); #endif while (isdigit(*(--p)) || *p == ','); if (*p == ' ') @@ -829,7 +847,7 @@ else if (*p == ' ') { #if !defined (COMPILE_UTILITY) - DEBUG(D_deliver) debug_printf("**** SPOOL_IN - early Exim 4 spool file\n"); + DEBUG(D_deliver) debug_printf_indent("**** SPOOL_IN - early Exim 4 spool file\n"); #endif *p++ = 0; (void)sscanf(CS p, "%d", &pno); @@ -842,7 +860,7 @@ int flags; #if !defined (COMPILE_UTILITY) - DEBUG(D_deliver) debug_printf("**** SPOOL_IN - Exim standard format spoolfile\n"); + DEBUG(D_deliver) debug_printf_indent("**** SPOOL_IN - Exim standard format spoolfile\n"); #endif (void)sscanf(CS p+1, "%d", &flags); @@ -878,13 +896,13 @@ } #if !defined(COMPILE_UTILITY) else - { DEBUG(D_deliver) debug_printf("**** SPOOL_IN - No additional fields\n"); } + { DEBUG(D_deliver) debug_printf_indent("**** SPOOL_IN - No additional fields\n"); } if (orcpt || dsn_flags) - DEBUG(D_deliver) debug_printf("**** SPOOL_IN - address: <%s> orcpt: <%s> dsn_flags: 0x%x\n", + DEBUG(D_deliver) debug_printf_indent("**** SPOOL_IN - address: <%s> orcpt: <%s> dsn_flags: 0x%x\n", big_buffer, orcpt, dsn_flags); if (errors_to) - DEBUG(D_deliver) debug_printf("**** SPOOL_IN - address: <%s> errorsto: <%s>\n", + DEBUG(D_deliver) debug_printf_indent("**** SPOOL_IN - address: <%s> errorsto: <%s>\n", big_buffer, errors_to); #endif @@ -956,7 +974,7 @@ and give a positive response. */ #ifndef COMPILE_UTILITY -DEBUG(D_deliver) debug_printf("body_linecount=%d message_linecount=%d\n", +DEBUG(D_deliver) debug_printf_indent("body_linecount=%d message_linecount=%d\n", body_linecount, message_linecount); #endif /* COMPILE_UTILITY */ diff -urN ../exim-4.94.orig/src/spool_out.c ./src/spool_out.c --- ../exim-4.94.orig/src/spool_out.c 2020-05-30 23:35:38.000000000 +0300 +++ ./src/spool_out.c 2021-05-15 15:06:16.118855000 +0300 @@ -105,6 +105,18 @@ +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; +} + static void spool_var_write(FILE * fp, const uschar * name, const uschar * val) { @@ -174,9 +186,11 @@ fprintf(fp, "-host_address %s.%d\n", sender_host_address, sender_host_port); if (sender_host_name) spool_var_write(fp, US"host_name", sender_host_name); - if (sender_host_authenticated) - spool_var_write(fp, US"host_auth", sender_host_authenticated); } +if (sender_host_authenticated) + spool_var_write(fp, US"host_auth", sender_host_authenticated); +if (sender_host_auth_pubname) + spool_var_write(fp, US"host_auth_pubname", sender_host_auth_pubname); /* Also about the interface a message came in on */ @@ -219,7 +233,7 @@ if (authenticated_id) spool_var_write(fp, US"auth_id", authenticated_id); if (authenticated_sender) - spool_var_write(fp, US"auth_sender", authenticated_sender); + spool_var_write(fp, US"auth_sender", 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"); @@ -292,19 +306,20 @@ for (int 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: 0x%x\n", r->dsn_flags); */ if (r->pno < 0 && !r->errors_to && 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) : CUS""; /* 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""; + const uschar *orcpt = r->orcpt ? zap_newlines(r->orcpt) : CUS""; - 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 -urN ../exim-4.94.orig/src/std-crypto.c ./src/std-crypto.c --- ../exim-4.94.orig/src/std-crypto.c 2020-05-30 23:35:38.000000000 +0300 +++ ./src/std-crypto.c 2021-05-15 15:06:16.119344000 +0300 @@ -350,12 +350,12 @@ */ static const char dh_ike_22_pem[] = "-----BEGIN DH PARAMETERS-----\n" -"MIIBCAKBgQCxC4+WoIDgHd6S3l6uXVTsUsmfvPsGo8aaap3KUtI7YWBz4oZ1oj0Y\n" +"MIIBDAKBgQCxC4+WoIDgHd6S3l6uXVTsUsmfvPsGo8aaap3KUtI7YWBz4oZ1oj0Y\n" "mDjvHi7mUsAT7LSuqQYRIySXXDzUm4O/rMvdfZDEvXCYSI6cIZpzck7/1vrlZEc4\n" "+qMaT/VbzMChUa9fDci0vUW/N982XBpl5oz9p21NpwjfH7K8LkpDcQKBgQCk0cvV\n" "w/00EmdlpELvuZkF+BBN0lisUH/WQGz/FCZtMSZv6h5cQVZLd35pD1UE8hMWAhe0\n" "sBuIal6RVH+eJ0n01/vX07mpLuGQnQ0iY/gKdqaiTAh6CR9THb8KAWm2oorWYqTR\n" -"jnOvoy13nVkY0IvIhY9Nzvl8KiSFXm7rIrOy5Q==\n" +"jnOvoy13nVkY0IvIhY9Nzvl8KiSFXm7rIrOy5QICAKA=\n" "-----END DH PARAMETERS-----\n"; /* RFC 5114 IKE_id=23 @@ -396,7 +396,7 @@ */ static const char dh_ike_23_pem[] = "-----BEGIN DH PARAMETERS-----\n" -"MIICCgKCAQEArRB+HpEjqdDWYPqnlVnFH6INZOVoO5/RtUsVl7YdCnXm+hQd+VpW\n" +"MIICDgKCAQEArRB+HpEjqdDWYPqnlVnFH6INZOVoO5/RtUsVl7YdCnXm+hQd+VpW\n" "26+aPEB7od8V6z1oijCcGA4d5rhaEnSgpm0/gVKtasISkDfJ7e/aTfjZHo/vVbc5\n" "S3rVt9C2wSIHyfmNEe002/bGugssi7wnvmoA4KC5xJcIs7+KMXCRiDaBKGEwvImF\n" "2xYC5xRBXZMwJ4Jzx94x79xzEPcSH9WgdBWYfZrcCkhtzfk6zEQyg4cxXXXhmMZB\n" @@ -406,7 +406,8 @@ "kAGo1mrXwXZpEBmZAkr00CcnWsE0i7inYtBSG8mK4kcVBCLqHtQJk51U2nRgzbX2\n" "xrJQcXy+8YDrNBGOmNEZUppF1vg0Vm4wJeMWozDvu3eobwwasVsFGuPUKMj4rLcK\n" "gTcVC47rEOGD7dGZY93Z4mPkdwWJ72qiHn9fL/OBtTnM40CdE81Wavu0jWwBkYHh\n" -"vP6UswJp7f5y/ptqpL17Wg8ccc//TBnEGOH27AF5gbwIfypwZbOEuJDTGR8r+g==\n" +"vP6UswJp7f5y/ptqpL17Wg8ccc//TBnEGOH27AF5gbwIfypwZbOEuJDTGR8r+gIC\n" +"AOA=\n" "-----END DH PARAMETERS-----\n"; /* RFC 5114 IKE_id=24 @@ -447,7 +448,7 @@ */ static const char dh_ike_24_pem[] = "-----BEGIN DH PARAMETERS-----\n" -"MIICCQKCAQEAh6jmHbS2Zjz/u9GcZRlZmYzu9ghmDdDyXSzu1ENeOwDgDfjx1hlX\n" +"MIICDQKCAQEAh6jmHbS2Zjz/u9GcZRlZmYzu9ghmDdDyXSzu1ENeOwDgDfjx1hlX\n" "1Pr330VhsqowFsPZETQJb6o79Cltgw6afCCeDGSXUXq9WoqdMGvPZ+2R+eZyW0dY\n" "wCLgse9Cdb97bFv8EdRfkIi5QfVOseWbuLw5oL8SMH9cT9twxYGyP3a2Osrhyqa3\n" "kC1SUmc1SIoO8TxtmlG/pKs62DR3llJNjvahZ7WkGCXZZ+FE5RQFZCUcysuD5rSG\n" @@ -457,7 +458,8 @@ "+MKMuxilWuMTQQAKZQGW+THHelfy3fRj5ensFEt3feYqqrioYorDdtKC1u04ZOZ5\n" "gkKOvIMdFDSPby+Rk7UEWvJ2cWTh38lnwfs/LlWkvRv/6DucgNBSuYXRguoK2yo7\n" "cxPT/hTISEseBSWIubfSu9LfAWGZ7NBuFVfNCRWzNTu7ZODsN3/QKDcN+StSx4kU\n" -"KM3GfrYYS1I9HbJGwy9jB4SQ8A741kfRSNR5VFFeIyfP75jFgmZLTA9sxBZZ\n" +"KM3GfrYYS1I9HbJGwy9jB4SQ8A741kfRSNR5VFFeIyfP75jFgmZLTA9sxBZZAgIB\n" +"AA==\n" "-----END DH PARAMETERS-----\n"; /* ------------------------------------------------------------------------- */ @@ -509,12 +511,12 @@ */ static const char dh_ffdhe2048_pem[] = "-----BEGIN DH PARAMETERS-----\n" -"MIH+AoH4DfhUWKK7Spqv3FYgJz088di5xYPOLTaVqeE2QRRkM/vMk53OJJs++X0v\n" -"42NjDHXY9oGyAq7EYXrT3x7V1f1lYSQz9R9fBm7QhWNlVT3tGvO1VxNef1fJNZhP\n" -"DHDg5ot34qaJ2vPv6HId8VihNq3nNTCsyk9IOnl6vAqxgrMk+2HRCKlLssjj+7lq\n" -"2rdg1/RoHU9Co945TfSuVu3nY3K7GQsHp8juCm1wngL84c334uzANATNKDQvYZFy\n" -"/pzphYP/jk8SMu7ygYPD/jsbTG+tczu1/LwuwiAFxY7xg30Wg7LG80omwbLv+ohr\n" -"QjhhKFyX//////////8CAQI=\n" +"MIIBDAKCAQEA//////////+t+FRYortKmq/cViAnPTzx2LnFg84tNpWp4TZBFGQz\n" +"+8yTnc4kmz75fS/jY2MMddj2gbICrsRhetPfHtXV/WVhJDP1H18GbtCFY2VVPe0a\n" +"87VXE15/V8k1mE8McODmi3fipona8+/och3xWKE2rec1MKzKT0g6eXq8CrGCsyT7\n" +"YdEIqUuyyOP7uWrat2DX9GgdT0Kj3jlN9K5W7edjcrsZCwenyO4KbXCeAvzhzffi\n" +"7MA0BM0oNC9hkXL+nOmFg/+OTxIy7vKBg8P+OxtMb61zO7X8vC7CIAXFjvGDfRaD\n" +"ssbzSibBsu/6iGtCOGEoXJf//////////wIBAgICB/8=\n" "-----END DH PARAMETERS-----\n"; /* @@ -574,7 +576,7 @@ */ static const char dh_ffdhe3072_pem[] = "-----BEGIN DH PARAMETERS-----\n" -"MIIBiAKCAYEA//////////+t+FRYortKmq/cViAnPTzx2LnFg84tNpWp4TZBFGQz\n" +"MIIBjAKCAYEA//////////+t+FRYortKmq/cViAnPTzx2LnFg84tNpWp4TZBFGQz\n" "+8yTnc4kmz75fS/jY2MMddj2gbICrsRhetPfHtXV/WVhJDP1H18GbtCFY2VVPe0a\n" "87VXE15/V8k1mE8McODmi3fipona8+/och3xWKE2rec1MKzKT0g6eXq8CrGCsyT7\n" "YdEIqUuyyOP7uWrat2DX9GgdT0Kj3jlN9K5W7edjcrsZCwenyO4KbXCeAvzhzffi\n" @@ -582,7 +584,7 @@ "ssbzSibBsu/6iGtCOGEfz9zeNVs7ZRkDW7w09N75nAI4YbRvydbmyQd62R0mkff3\n" "7lmMsPrBhtkcrv4TCYUTknC0EwyTvEN5RPT9RFLi103TZPLiHnH1S/9croKrnJ32\n" "nuhtK8UiNjoNq8Uhl5sN6todv5pC1cRITgq80Gv6U93vPBsg7j/VnXwl5B0rZsYu\n" -"N///////////AgEC\n" +"N///////////AgECAgIL/w==\n" "-----END DH PARAMETERS-----\n"; /* @@ -654,7 +656,7 @@ */ static const char dh_ffdhe4096_pem[] = "-----BEGIN DH PARAMETERS-----\n" -"MIICCAKCAgEA//////////+t+FRYortKmq/cViAnPTzx2LnFg84tNpWp4TZBFGQz\n" +"MIICDAKCAgEA//////////+t+FRYortKmq/cViAnPTzx2LnFg84tNpWp4TZBFGQz\n" "+8yTnc4kmz75fS/jY2MMddj2gbICrsRhetPfHtXV/WVhJDP1H18GbtCFY2VVPe0a\n" "87VXE15/V8k1mE8McODmi3fipona8+/och3xWKE2rec1MKzKT0g6eXq8CrGCsyT7\n" "YdEIqUuyyOP7uWrat2DX9GgdT0Kj3jlN9K5W7edjcrsZCwenyO4KbXCeAvzhzffi\n" @@ -664,7 +666,7 @@ "nuhtK8UiNjoNq8Uhl5sN6todv5pC1cRITgq80Gv6U93vPBsg7j/VnXwl5B0rZp4e\n" "8W5vUsMWTfT7eTDp5OWIV7asfV9C1p9tGHdjzx1VA0AEh/VbpX4xzHpxNciG77Qx\n" "iu1qHgEtnmgyqQdgCpGBMMRtx3j5ca0AOAkpmaMzy4t6Gh25PXFAADwqTs6p+Y0K\n" -"zAqCkc3OyX3Pjsm1Wn+IpGtNtahR9EGC4caKAH5eZV9q//////////8CAQI=\n" +"zAqCkc3OyX3Pjsm1Wn+IpGtNtahR9EGC4caKAH5eZV9q//////////8CAQICAg//\n" "-----END DH PARAMETERS-----\n"; /* @@ -756,7 +758,7 @@ */ static const char dh_ffdhe6144_pem[] = "-----BEGIN DH PARAMETERS-----\n" -"MIIDCAKCAwEA//////////+t+FRYortKmq/cViAnPTzx2LnFg84tNpWp4TZBFGQz\n" +"MIIDDAKCAwEA//////////+t+FRYortKmq/cViAnPTzx2LnFg84tNpWp4TZBFGQz\n" "+8yTnc4kmz75fS/jY2MMddj2gbICrsRhetPfHtXV/WVhJDP1H18GbtCFY2VVPe0a\n" "87VXE15/V8k1mE8McODmi3fipona8+/och3xWKE2rec1MKzKT0g6eXq8CrGCsyT7\n" "YdEIqUuyyOP7uWrat2DX9GgdT0Kj3jlN9K5W7edjcrsZCwenyO4KbXCeAvzhzffi\n" @@ -772,7 +774,7 @@ "w1h+ONoAd9m0dj5OS5Syu8GUxmUed8r5ku6qwCMqKBv2s6c5wSJhFoIK6NtYR6Z8\n" "vvnJCRtGLVOM1ysDdGrnf15iKSwxFWKoRlBdyC24VDOK5J9SNclbkReMzy3Vys70\n" "A+ydGBDGJysEWztx+dxrgNY/3UqOmtseaWKmlSbUMWHBpB1XDXk42tSkDjKc0OQO\n" -"Zf//////////AgEC\n" +"Zf//////////AgECAgIX/w==\n" "-----END DH PARAMETERS-----\n"; /* @@ -886,7 +888,7 @@ */ static const char dh_ffdhe8192_pem[] = "-----BEGIN DH PARAMETERS-----\n" -"MIIECAKCBAEA//////////+t+FRYortKmq/cViAnPTzx2LnFg84tNpWp4TZBFGQz\n" +"MIIEDAKCBAEA//////////+t+FRYortKmq/cViAnPTzx2LnFg84tNpWp4TZBFGQz\n" "+8yTnc4kmz75fS/jY2MMddj2gbICrsRhetPfHtXV/WVhJDP1H18GbtCFY2VVPe0a\n" "87VXE15/V8k1mE8McODmi3fipona8+/och3xWKE2rec1MKzKT0g6eXq8CrGCsyT7\n" "YdEIqUuyyOP7uWrat2DX9GgdT0Kj3jlN9K5W7edjcrsZCwenyO4KbXCeAvzhzffi\n" @@ -907,7 +909,7 @@ "UVQfxoychrAiu3CZh2pGDnRRqKkxCXA/7hwhfmw4JuUsUappHg5CPPyZ6eMWUMEh\n" "e2JIFs2tmpX51bgBlIjZwKCh/jB1pXfiMYP4HUo/L6RXHvyM4LqKT+i2hV3+crCm\n" "bt7S+6v75Yow+vq+HF1xqH4vdB74wf6G/qa7/eUwZ38Nl9EdSfeoRD0IIuUGqfRh\n" -"TgEeKpSDj/iM1oyLt8XGQkz//////////wIBAg==\n" +"TgEeKpSDj/iM1oyLt8XGQkz//////////wIBAgICH/8=\n" "-----END DH PARAMETERS-----\n"; /* ========================================================================= */ diff -urN ../exim-4.94.orig/src/store.c ./src/store.c --- ../exim-4.94.orig/src/store.c 2020-05-30 23:35:38.000000000 +0300 +++ ./src/store.c 2021-05-15 15:06:16.119595000 +0300 @@ -188,14 +188,14 @@ if ((b = current_block[pool])) { uschar * bc = US b + ALIGNED_SIZEOF_STOREBLOCK; - if (US p >= bc && US p <= bc + b->length) return TRUE; + if (US p >= bc && US p < bc + b->length) return TRUE; } for (int pool = POOL_TAINT_BASE; pool < nelem(chainbase); pool++) for (b = chainbase[pool]; b; b = b->next) { uschar * bc = US b + ALIGNED_SIZEOF_STOREBLOCK; - if (US p >= bc && US p <= bc + b->length) return TRUE; + if (US p >= bc && US p < bc + b->length) return TRUE; } return FALSE; } @@ -233,6 +233,17 @@ { int pool = tainted ? store_pool + POOL_TAINT_BASE : store_pool; +/* Ensure we've been asked to allocate memory. +A negative size is a sign of a security problem. +A zero size might be also suspect, but our internal usage deliberately +does this to return a current watermark value for a later release of +allocated store. */ + +if (size < 0 || size >= INT_MAX/2) + log_write(0, LOG_MAIN|LOG_PANIC_DIE, + "bad memory allocation requested (%d bytes) at %s %d", + size, func, linenumber); + /* 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 @@ -379,6 +390,11 @@ 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) at %s %d", + oldsize, newsize, func, linenumber); + /* Check that the block being extended was already of the required taint status; refuse to extend if not. */ @@ -739,6 +755,11 @@ die_tainted(US"store_newblock", CUS func, linenumber); #endif +if (len < 0 || len > newsize) + log_write(0, LOG_MAIN|LOG_PANIC_DIE, + "bad memory extension requested (%d -> %d bytes) at %s %d", + len, newsize, func, linenumber); + newtext = store_get(newsize, tainted); memcpy(newtext, block, len); if (release_ok) store_release_3(block, pool, func, linenumber); @@ -768,6 +789,11 @@ internal_store_malloc(int size, const char *func, int line) { void * yield; + +if (size < 0 || size >= INT_MAX/2) + log_write(0, LOG_MAIN|LOG_PANIC_DIE, + "bad memory allocation requested (%d bytes) at %s %d", + size, func, line); if (size < 16) size = 16; diff -urN ../exim-4.94.orig/src/string.c ./src/string.c --- ../exim-4.94.orig/src/string.c 2020-05-30 23:35:38.000000000 +0300 +++ ./src/string.c 2021-05-15 15:06:16.120002000 +0300 @@ -281,17 +281,17 @@ /* This function is called for critical strings. It checks for any non-printing characters, and if any are found, it makes a new copy of the string with suitable escape sequences. It is most often called by the -macro string_printing(), which sets allow_tab TRUE. +macro string_printing(), which sets flags to 0. Arguments: s the input string - allow_tab TRUE to allow tab as a printing character + flags Bit 0: convert tabs. Bit 1: convert spaces. Returns: string with non-printers encoded as printing sequences */ const uschar * -string_printing2(const uschar *s, BOOL allow_tab) +string_printing2(const uschar *s, int flags) { int nonprintcount = 0; int length = 0; @@ -301,7 +301,10 @@ while (*t != 0) { int c = *t++; - if (!mac_isprint(c) || (!allow_tab && c == '\t')) nonprintcount++; + if ( !mac_isprint(c) + || flags & SP_TAB && c == '\t' + || flags & SP_SPACE && c == ' ' + ) nonprintcount++; length++; } @@ -310,17 +313,19 @@ /* Get a new block of store guaranteed big enough to hold the expanded string. */ -ss = store_get(length + nonprintcount * 3 + 1, is_tainted(s)); +tt = ss = store_get(length + nonprintcount * 3 + 1, is_tainted(s)); /* Copy everything, escaping non printers. */ -t = s; -tt = ss; - -while (*t != 0) +for (t = s; *t; ) { int c = *t; - if (mac_isprint(c) && (allow_tab || c != '\t')) *tt++ = *t++; else + if ( mac_isprint(c) + && (!(flags & SP_TAB) || c != '\t') + && (!(flags & SP_SPACE) || c != ' ') + ) + *tt++ = *t++; + else { *tt++ = '\\'; switch (*t) @@ -947,7 +952,10 @@ s = ss; if (!*s || *++s != sep || sep_is_special) break; } - while (g->ptr > 0 && isspace(g->s[g->ptr-1])) g->ptr--; + /* while (g->ptr > 0 && isspace(g->s[g->ptr-1])) g->ptr--; */ + while ( g->ptr > 0 && isspace(g->s[g->ptr-1]) + && (g->ptr == 1 || g->s[g->ptr-2] != '\\') ) + g->ptr--; buffer = string_from_gstring(g); gstring_release_unused(g); } @@ -1083,7 +1091,16 @@ 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 + 1) & ~inc; /* one for a NUL */ /* Try to extend an existing allocation. If the result of calling @@ -1132,6 +1149,10 @@ int p; BOOL srctaint = is_tainted(s); +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; @@ -1141,8 +1162,12 @@ else if (srctaint && !is_tainted(g->s)) gstring_rebuffer(g); +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, count); /* Because we always specify the exact number of characters to copy, we can diff -urN ../exim-4.94.orig/src/structs.h ./src/structs.h --- ../exim-4.94.orig/src/structs.h 2020-05-30 23:35:38.000000000 +0300 +++ ./src/structs.h 2021-05-15 15:06:16.120357000 +0300 @@ -80,14 +80,17 @@ typedef struct host_item { struct host_item *next; - const uschar *name; /* Host name */ - const uschar *address; /* IP address in text form */ - int port; /* port value in host order (if SRV lookup) */ - int mx; /* MX value if found via MX records */ - int sort_key; /* MX*1000 plus random "fraction" */ - int status; /* Usable, unusable, or unknown */ - int why; /* Why host is unusable */ - int last_try; /* Time of last try if known */ + const uschar *name; /* Host name */ +#ifndef DISABLE_TLS + const uschar *certname; /* Name used for certificate checks */ +#endif + const uschar *address; /* IP address in text form */ + int port; /* port value in host order (if SRV lookup) */ + int mx; /* MX value if found via MX records */ + int sort_key; /* MX*1000 plus random "fraction" */ + int status; /* Usable, unusable, or unknown */ + int why; /* Why host is unusable */ + int last_try; /* Time of last try if known */ dnssec_status_t dnssec; } host_item; diff -urN ../exim-4.94.orig/src/tls-gnu.c ./src/tls-gnu.c --- ../exim-4.94.orig/src/tls-gnu.c 2020-05-30 23:35:38.000000000 +0300 +++ ./src/tls-gnu.c 2021-05-15 15:06:16.121494000 +0300 @@ -545,8 +545,11 @@ /* peercert is set in peer_status() */ tlsp->peerdn = state->peerdn; -tlsp->sni = state->received_sni; +/* do not corrupt sni sent by client; record sni rxd by server */ +if (!state->host) + tlsp->sni = state->received_sni; + /* record our certificate */ { const gnutls_datum_t * cert = gnutls_certificate_get_ours(state->session); @@ -2601,9 +2604,9 @@ { state->exp_tls_verify_cert_hostnames = #ifdef SUPPORT_I18N - string_domain_utf8_to_alabel(host->name, NULL); + string_domain_utf8_to_alabel(host->certname, NULL); #else - host->name; + host->certname; #endif DEBUG(D_tls) debug_printf("TLS: server cert verification includes hostname: \"%s\".\n", @@ -2863,7 +2866,7 @@ /* If dane is flagged, have either request or require dane for this host, and a TLSA record found. Therefore, dane verify required. Which implies cert must be requested and supplied, dane verify must pass, and cert verify irrelevant -(incl. hostnames), and (caller handled) require_tls */ +(incl. hostnames), and (caller handled) require_tls and sni=$domain */ if (conn_args->dane && ob->dane_require_tls_ciphers) { @@ -2890,6 +2893,7 @@ cipher_list, &state, tlsp, errstr) != OK) return FALSE; + #ifdef MEASURE_TIMING report_time_since(&t0, US"client tls_init (delta)"); #endif @@ -3158,6 +3162,7 @@ sigalrm_seen = FALSE; if (smtp_receive_timeout > 0) ALARM(smtp_receive_timeout); +errno = 0; do inbytes = gnutls_record_recv(state->session, state->xfer_buffer, MIN(ssl_xfer_buffer_size, lim)); @@ -3318,6 +3323,7 @@ debug_printf("Calling gnutls_record_recv(session=%p, buffer=%p, len=" SIZE_T_FMT ")\n", state->session, buff, len); +errno = 0; do inbytes = gnutls_record_recv(state->session, buff, len); while (inbytes == GNUTLS_E_AGAIN); @@ -3381,6 +3387,7 @@ DEBUG(D_tls) debug_printf("gnutls_record_send(session=%p, buffer=%p, left=" SIZE_T_FMT ")\n", state->session, buff, left); + errno = 0; do outbytes = gnutls_record_send(state->session, buff, left); while (outbytes == GNUTLS_E_AGAIN); diff -urN ../exim-4.94.orig/src/tls-openssl.c ./src/tls-openssl.c --- ../exim-4.94.orig/src/tls-openssl.c 2020-05-30 23:35:38.000000000 +0300 +++ ./src/tls-openssl.c 2021-05-15 15:06:16.122255000 +0300 @@ -372,10 +372,10 @@ } ocsp_resplist; typedef struct tls_ext_ctx_cb { - tls_support * tlsp; - uschar *certificate; - uschar *privatekey; - BOOL is_server; + tls_support * tlsp; + uschar * certificate; + uschar * privatekey; + BOOL is_server; #ifndef DISABLE_OCSP STACK_OF(X509) *verify_stack; /* chain for verifying the proof */ union { @@ -390,14 +390,14 @@ } client; } u_ocsp; #endif - uschar *dhparam; + uschar * dhparam; /* these are cached from first expand */ - uschar *server_cipher_list; + uschar * server_cipher_list; /* only passed down to tls_error: */ - host_item *host; + host_item * host; const uschar * verify_cert_hostnames; #ifndef DISABLE_EVENT - uschar * event_action; + uschar * event_action; #endif } tls_ext_ctx_cb; @@ -2915,9 +2915,9 @@ { cbinfo->verify_cert_hostnames = #ifdef SUPPORT_I18N - string_domain_utf8_to_alabel(host->name, NULL); + string_domain_utf8_to_alabel(host->certname, NULL); #else - host->name; + host->certname; #endif DEBUG(D_tls) debug_printf("Cert hostname to check: \"%s\"\n", cbinfo->verify_cert_hostnames); @@ -3197,6 +3197,7 @@ #ifndef DISABLE_OCSP { # ifdef SUPPORT_DANE + /*XXX this should be moved to caller, to be common across gnutls/openssl */ if ( conn_args->dane && ob->hosts_request_ocsp[0] == '*' && ob->hosts_request_ocsp[1] == '\0' @@ -3674,16 +3675,12 @@ { if (!len) buff = US &error; /* dummy just so that string_catn is ok */ -#ifndef DISABLE_PIPE_CONNECT int save_pool = store_pool; store_pool = POOL_PERM; -#endif corked = string_catn(corked, buff, len); -#ifndef DISABLE_PIPE_CONNECT store_pool = save_pool; -#endif if (more) { diff -urN ../exim-4.94.orig/src/tls.c ./src/tls.c --- ../exim-4.94.orig/src/tls.c 2020-05-30 23:35:38.000000000 +0300 +++ ./src/tls.c 2021-05-15 15:06:16.122391000 +0300 @@ -157,6 +157,9 @@ 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 -urN ../exim-4.94.orig/src/transport.c ./src/transport.c --- ../exim-4.94.orig/src/transport.c 2020-05-30 23:35:38.000000000 +0300 +++ ./src/transport.c 2021-05-15 15:06:16.123407000 +0300 @@ -958,10 +958,10 @@ if (tctx->options & topt_add_return_path) { - uschar buffer[ADDRESS_MAXLENGTH + 20]; - int n = sprintf(CS buffer, "Return-path: <%.*s>\n", ADDRESS_MAXLENGTH, - return_path); - if (!write_chunk(tctx, buffer, n)) goto bad; + int n; + uschar * s = string_sprintf("Return-path: <%.*s>\n%n", + EXIM_EMAILADDR_MAX, return_path, &n); + if (!write_chunk(tctx, s, n)) goto bad; } /* Add envelope-to: if requested */ @@ -1661,6 +1661,7 @@ debug_printf("transport_check_waiting entered\n"); debug_printf(" sequence=%d local_max=%d global_max=%d\n", continue_sequence, local_message_max, connection_max_messages); + acl_level++; } /* Do nothing if we have hit the maximum number that can be send down one @@ -1670,23 +1671,23 @@ if (local_message_max > 0 && continue_sequence >= local_message_max) { DEBUG(D_transport) - debug_printf("max messages for one connection reached: returning\n"); - return FALSE; + debug_printf_indent("max messages for one connection reached: returning\n"); + goto retfalse; } /* Open the waiting information database. */ if (!(dbm_file = dbfn_open(string_sprintf("wait-%.200s", transport_name), O_RDWR, &dbblock, TRUE, TRUE))) - return FALSE; + goto retfalse; /* See if there is a record for this host; if not, there's nothing to do. */ if (!(host_record = dbfn_read(dbm_file, hostname))) { dbfn_close(dbm_file); - DEBUG(D_transport) debug_printf("no messages waiting for %s\n", hostname); - return FALSE; + DEBUG(D_transport) debug_printf_indent("no messages waiting for %s\n", hostname); + goto retfalse; } /* If the data in the record looks corrupt, just log something and @@ -1697,7 +1698,7 @@ dbfn_close(dbm_file); log_write(0, LOG_MAIN|LOG_PANIC, "smtp-wait database entry for %s has bad " "count=%d (max=%d)", hostname, host_record->count, WAIT_NAME_MAX); - return FALSE; + goto retfalse; } /* Scan the message ids in the record from the end towards the beginning, @@ -1729,7 +1730,7 @@ { msgq[i].bKeep = TRUE; - Ustrncpy_nt(msgq[i].message_id, host_record->text + (i * MESSAGE_ID_LENGTH), + Ustrncpy_nt(msgq[i].message_id, host_record->text + (i * MESSAGE_ID_LENGTH), MESSAGE_ID_LENGTH); msgq[i].message_id[MESSAGE_ID_LENGTH] = 0; } @@ -1835,8 +1836,8 @@ if (host_length <= 0) { dbfn_close(dbm_file); - DEBUG(D_transport) debug_printf("waiting messages already delivered\n"); - return FALSE; + DEBUG(D_transport) debug_printf_indent("waiting messages already delivered\n"); + goto retfalse; } /* we were not able to find an acceptable message, nor was there a @@ -1847,7 +1848,7 @@ { Ustrcpy(new_message_id, message_id); dbfn_close(dbm_file); - return FALSE; + goto retfalse; } } /* we need to process a continuation record */ @@ -1865,7 +1866,12 @@ } dbfn_close(dbm_file); +DEBUG(D_transport) {acl_level--; debug_printf("transport_check_waiting: TRUE\n"); } return TRUE; + +retfalse: +DEBUG(D_transport) {acl_level--; debug_printf("transport_check_waiting: FALSE\n"); } +return FALSE; } /************************************************* @@ -1877,7 +1883,7 @@ transport_do_pass_socket(const uschar *transport_name, const uschar *hostname, const uschar *hostaddress, uschar *id, int socket_fd) { -int i = 20; +int i = 22; const uschar **argv; /* Set up the calling arguments; use the standard function for the basics, @@ -1898,6 +1904,16 @@ argv[i++] = sending_ip_address; argv[i++] = string_sprintf("%d", sending_port); argv[i++] = tls_out.active.sock >= 0 ? tls_out.cipher : continue_proxy_cipher; + + if (tls_out.sni) + { + argv[i++] = +#ifdef SUPPORT_DANE + tls_out.dane_verified ? US"-MCr" : +#endif + US"-MCs"; + argv[i++] = tls_out.sni; + } } else argv[i++] = US"-MCT"; diff -urN ../exim-4.94.orig/src/transports/appendfile.c ./src/transports/appendfile.c --- ../exim-4.94.orig/src/transports/appendfile.c 2020-05-30 23:35:38.000000000 +0300 +++ ./src/transports/appendfile.c 2021-05-15 15:06:16.124618000 +0300 @@ -1783,8 +1783,8 @@ if (statbuf.st_nlink != 1) { addr->basic_errno = ERRNO_NOTREGULAR; - addr->message = string_sprintf("mailbox %s%s has too many links (%d)", - filename, islink ? " (symlink)" : "", statbuf.st_nlink); + addr->message = string_sprintf("mailbox %s%s has too many links (%lu)", + filename, islink ? " (symlink)" : "", (unsigned long)statbuf.st_nlink); goto RETURN; } diff -urN ../exim-4.94.orig/src/transports/autoreply.c ./src/transports/autoreply.c --- ../exim-4.94.orig/src/transports/autoreply.c 2020-05-30 23:35:38.000000000 +0300 +++ ./src/transports/autoreply.c 2021-05-15 15:06:16.124949000 +0300 @@ -474,10 +474,10 @@ else { EXIM_DATUM key_datum, result_datum; - uschar * dirname = string_copy(oncelog); - uschar * s; + uschar * dirname, * s; - if ((s = Ustrrchr(dirname, '/'))) *s = '\0'; + dirname = (s = Ustrrchr(oncelog, '/')) + ? string_copyn(oncelog, s - oncelog) : NULL; EXIM_DBOPEN(oncelog, dirname, O_RDWR|O_CREAT, ob->mode, &dbm_file); if (!dbm_file) { diff -urN ../exim-4.94.orig/src/transports/queuefile.c ./src/transports/queuefile.c --- ../exim-4.94.orig/src/transports/queuefile.c 2020-05-30 23:35:38.000000000 +0300 +++ ./src/transports/queuefile.c 2021-05-15 15:06:16.125923000 +0300 @@ -8,7 +8,10 @@ /* See the file NOTICE for conditions of use and distribution. */ + #include "../exim.h" + +#ifdef EXPERIMENTAL_QUEUEFILE /* whole file */ #include "queuefile.h" /* Options specific to the appendfile transport. They must be in alphabetic @@ -276,3 +279,4 @@ } #endif /*!MACRO_PREDEF*/ +#endif /*EXPERIMENTAL_QUEUEFILE*/ diff -urN ../exim-4.94.orig/src/transports/smtp.c ./src/transports/smtp.c --- ../exim-4.94.orig/src/transports/smtp.c 2020-05-30 23:35:38.000000000 +0300 +++ ./src/transports/smtp.c 2021-05-15 15:06:16.127874000 +0300 @@ -1020,7 +1020,7 @@ (void) smtp_discard_responses(sx, sx->conn_args.ob, *countp); return rc; } -#endif +#endif /*!DISABLE_PIPE_CONNECT*/ /************************************************* @@ -1087,7 +1087,7 @@ { DEBUG(D_transport) debug_printf("%s expect mail\n", __FUNCTION__); count--; - sx->pending_MAIL = FALSE; + sx->pending_MAIL = sx->RCPT_452 = FALSE; if (!smtp_read_response(sx, sx->buffer, sizeof(sx->buffer), '2', ob->command_timeout)) { @@ -1227,7 +1227,7 @@ if (addr->more_errno >> 8 == 52 && yield & 3) { - if (!sx->RCPT_452) + if (!sx->RCPT_452) /* initialised at MAIL-ack above */ { DEBUG(D_transport) debug_printf("%s: seen first 452 too-many-rcpts\n", __FUNCTION__); @@ -1274,6 +1274,8 @@ } } } + if (count && !(addr = addr->next)) + return -2; } /* Loop for next RCPT response */ /* Update where to start at for the next block of responses, unless we @@ -1549,7 +1551,9 @@ if (require_auth == OK && !f.smtp_authenticated) { +#ifndef DISABLE_PIPE_CONNECT invalidate_ehlo_cache_entry(sx); +#endif set_errno_nohost(sx->addrlist, ERRNO_AUTHFAIL, string_sprintf("authentication required but %s", fail_reason), DEFER, FALSE, &sx->delivery_start); @@ -1620,8 +1624,8 @@ typedef struct smtp_compare_s { - uschar *current_sender_address; - struct transport_instance *tblock; + uschar * current_sender_address; + struct transport_instance * tblock; } smtp_compare_t; @@ -1989,8 +1993,81 @@ DEFER, FALSE, &sx->delivery_start); return ERROR; } -#endif +#else +/* If we have a proxied TLS connection, check usability for this message */ + +if (continue_hostname && continue_proxy_cipher) + { + int rc; + const uschar * sni = US""; + +# ifdef SUPPORT_DANE + /* Check if the message will be DANE-verified; if so force its SNI */ + + tls_out.dane_verified = FALSE; + smtp_port_for_connect(sx->conn_args.host, sx->port); + if ( sx->conn_args.host->dnssec == DS_YES + && ( sx->dane_required + || verify_check_given_host(CUSS &ob->hosts_try_dane, sx->conn_args.host) == OK + ) ) + switch (rc = tlsa_lookup(sx->conn_args.host, &sx->conn_args.tlsa_dnsa, sx->dane_required)) + { + case OK: sx->conn_args.dane = TRUE; + ob->tls_tempfail_tryclear = FALSE; /* force TLS */ + ob->tls_sni = sx->first_addr->domain; /* force SNI */ + break; + case FAIL_FORCED: break; + default: set_errno_nohost(sx->addrlist, ERRNO_DNSDEFER, + string_sprintf("DANE error: tlsa lookup %s", + rc_to_string(rc)), + rc, FALSE, &sx->delivery_start); +# ifndef DISABLE_EVENT + (void) event_raise(sx->conn_args.tblock->event_action, + US"dane:fail", sx->dane_required + ? US"dane-required" : US"dnssec-invalid"); +# endif + return rc; + } +# endif + + /* If the SNI or the DANE status required for the new message differs from the + existing conn drop the connection to force a new one. */ + + if (ob->tls_sni && !(sni = expand_cstring(ob->tls_sni))) + log_write(0, LOG_MAIN|LOG_PANIC, + "<%s>: failed to expand transport's tls_sni value: %s", + sx->addrlist->address, expand_string_message); + +# ifdef SUPPORT_DANE + if ( (continue_proxy_sni ? (Ustrcmp(continue_proxy_sni, sni) == 0) : !*sni) + && continue_proxy_dane == sx->conn_args.dane) + { + tls_out.sni = US sni; + if ((tls_out.dane_verified = continue_proxy_dane)) + sx->conn_args.host->dnssec = DS_YES; + } +# else + if ((continue_proxy_sni ? (Ustrcmp(continue_proxy_sni, sni) == 0) : !*sni)) + tls_out.sni = US sni; +# endif + else + { + DEBUG(D_transport) + debug_printf("Closing proxied-TLS connection due to SNI mismatch\n"); + + HDEBUG(D_transport|D_acl|D_v) debug_printf_indent(" SMTP>> QUIT\n"); + write(0, "QUIT\r\n", 6); + close(0); + continue_hostname = continue_proxy_cipher = NULL; + f.continue_more = FALSE; + continue_sequence = 1; /* Unfortunately, this process cannot affect success log + which is done by delivery proc. Would have to pass this + back through reporting pipe. */ + } + } +#endif /*!DISABLE_TLS*/ + /* Make a connection to the host if this isn't a continued delivery, and handle the initial interaction and HELO/EHLO/LHLO. Connect timeout errors are handled specially so they can be identified for retries. */ @@ -2019,7 +2096,8 @@ switch (rc = tlsa_lookup(sx->conn_args.host, &sx->conn_args.tlsa_dnsa, sx->dane_required)) { case OK: sx->conn_args.dane = TRUE; - ob->tls_tempfail_tryclear = FALSE; + ob->tls_tempfail_tryclear = FALSE; /* force TLS */ + ob->tls_sni = sx->first_addr->domain; /* force SNI */ break; case FAIL_FORCED: break; default: set_errno_nohost(sx->addrlist, ERRNO_DNSDEFER, @@ -2281,8 +2359,8 @@ 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; @@ -3429,6 +3507,9 @@ uschar *message = NULL; uschar new_message_id[MESSAGE_ID_LENGTH + 1]; smtp_context * sx = store_get(sizeof(*sx), TRUE); /* tainted, for the data buffers */ +#ifdef SUPPORT_DANE +BOOL dane_held; +#endif suppress_tls = suppress_tls; /* stop compiler warning when no TLS support */ *message_defer = FALSE; @@ -3445,15 +3526,41 @@ gettimeofday(&sx->delivery_start, NULL); sx->sync_addr = sx->first_addr = addrlist; -/* Get the channel set up ready for a message (MAIL FROM being the next -SMTP command to send */ +#ifdef SUPPORT_DANE +DANE_DOMAINS: +dane_held = FALSE; +#endif +/* Get the channel set up ready for a message, MAIL FROM being the next +SMTP command to send. */ + if ((rc = smtp_setup_conn(sx, suppress_tls)) != OK) { timesince(&addrlist->delivery_time, &sx->delivery_start); - return rc; + yield = rc; + goto TIDYUP; } +#ifdef SUPPORT_DANE +/* If the connection used DANE, ignore for now any addresses with incompatible +domains. The SNI has to be the domain. Arrange a whole new TCP conn later, +just in case only TLS isn't enough. */ + +if (sx->conn_args.dane) + { + const uschar * dane_domain = sx->first_addr->domain; + + for (address_item * a = sx->first_addr->next; a; a = a->next) + if ( a->transport_return == PENDING_DEFER + && Ustrcmp(dane_domain, a->domain) != 0) + { + DEBUG(D_transport) debug_printf("DANE: holding %s for later\n", a->domain); + dane_held = TRUE; + a->transport_return = DANE; + } + } +#endif + /* If there is a filter command specified for this transport, we can now set it up. This cannot be done until the identity of the host is known. */ @@ -3819,9 +3926,10 @@ } /* Process all transported addresses - for LMTP or PRDR, read a status for - each one. */ + each one. We used to drop out at first_addr, until someone returned a 452 + followed by a 250... and we screwed up the accepted addresses. */ - for (address_item * addr = addrlist; addr != sx->first_addr; addr = addr->next) + for (address_item * addr = addrlist; addr; addr = addr->next) { if (addr->transport_return != PENDING_OK) continue; @@ -4154,8 +4262,8 @@ t_compare.tblock = tblock; t_compare.current_sender_address = sender_address; - if ( sx->first_addr != NULL - || f.continue_more + if ( sx->first_addr != NULL /* more addrs for this message */ + || f.continue_more /* more addrs for coninued-host */ || ( #ifndef DISABLE_TLS ( tls_out.active.sock < 0 && !continue_proxy_cipher @@ -4202,7 +4310,7 @@ if (sx->first_addr != NULL) /* More addresses still to be sent */ - { /* in this run of the transport */ + { /* for this message */ continue_sequence++; /* Causes * in logging */ pipelining_active = sx->pipelining_used; /* was cleared at DATA */ goto SEND_MESSAGE; @@ -4226,6 +4334,7 @@ tls_close(sx->cctx.tls_ctx, TLS_SHUTDOWN_WAIT); sx->cctx.tls_ctx = NULL; + tls_out.active.sock = -1; smtp_peer_options = smtp_peer_options_wrap; sx->ok = !sx->smtps && smtp_write_command(sx, SCMD_FLUSH, "EHLO %s\r\n", sx->helo_data) @@ -4234,7 +4343,7 @@ '2', ob->command_timeout); if (sx->ok && f.continue_more) - return yield; /* More addresses for another run */ + goto TIDYUP; /* More addresses for another run */ } else { @@ -4254,7 +4363,7 @@ else #endif if (f.continue_more) - return yield; /* More addresses for another run */ + goto TIDYUP; /* More addresses for another run */ /* If the socket is successfully passed, we mustn't send QUIT (or indeed anything!) from here. */ @@ -4294,7 +4403,7 @@ sx->cctx.sock = -1; continue_transport = NULL; continue_hostname = NULL; - return yield; + goto TIDYUP; } log_write(0, LOG_PANIC_DIE, "fork failed"); } @@ -4369,8 +4478,37 @@ (void) event_raise(tblock->event_action, US"tcp:close", NULL); #endif +#ifdef SUPPORT_DANE +if (dane_held) + { + sx->first_addr = NULL; + for (address_item * a = sx->addrlist->next; a; a = a->next) + if (a->transport_return == DANE) + { + a->transport_return = PENDING_DEFER; + if (!sx->first_addr) + { + /* Remember the new start-point in the addrlist, for smtp_setup_conn() + to get the domain string for SNI */ + + sx->first_addr = a; + DEBUG(D_transport) debug_printf("DANE: go-around for %s\n", a->domain); + } + } + goto DANE_DOMAINS; + } +#endif + continue_transport = NULL; continue_hostname = NULL; +return yield; + +TIDYUP: +#ifdef SUPPORT_DANE +if (dane_held) for (address_item * a = sx->addrlist->next; a; a = a->next) + if (a->transport_return == DANE) + a->transport_return = PENDING_DEFER; +#endif return yield; } diff -urN ../exim-4.94.orig/src/transports/smtp.h ./src/transports/smtp.h --- ../exim-4.94.orig/src/transports/smtp.h 2020-05-30 23:35:38.000000000 +0300 +++ ./src/transports/smtp.h 2021-05-15 15:06:16.127994000 +0300 @@ -87,7 +87,7 @@ # ifdef EXPERIMENTAL_TLS_RESUME uschar *tls_resumption_hosts; # endif - uschar *tls_sni; + const uschar *tls_sni; uschar *tls_verify_certificates; int tls_dh_min_bits; BOOL tls_tempfail_tryclear; diff -urN ../exim-4.94.orig/src/transports/smtp_socks.c ./src/transports/smtp_socks.c --- ../exim-4.94.orig/src/transports/smtp_socks.c 2020-05-30 23:35:38.000000000 +0300 +++ ./src/transports/smtp_socks.c 2021-05-15 15:06:16.128145000 +0300 @@ -190,7 +190,7 @@ { sd = &proxies[i]; if (!sd->is_failed && sd->priority == pri) - if ((rnd -= sd->weight) <= 0) + if ((rnd -= sd->weight) < 0) return i; } diff -urN ../exim-4.94.orig/src/tree.c ./src/tree.c --- ../exim-4.94.orig/src/tree.c 2020-05-30 23:35:38.000000000 +0300 +++ ./src/tree.c 2021-05-15 15:06:16.128503000 +0300 @@ -27,7 +27,7 @@ */ void -tree_add_nonrecipient(uschar *s) +tree_add_nonrecipient(const uschar *s) { rmark rpoint = store_mark(); tree_node *node = store_get(sizeof(tree_node) + Ustrlen(s), is_tainted(s)); @@ -52,7 +52,7 @@ */ void -tree_add_duplicate(uschar *s, address_item *addr) +tree_add_duplicate(const uschar *s, address_item *addr) { rmark rpoint = store_mark(); tree_node *node = store_get(sizeof(tree_node) + Ustrlen(s), is_tainted(s)); @@ -74,7 +74,7 @@ */ void -tree_add_unusable(host_item *h) +tree_add_unusable(const host_item *h) { rmark rpoint = store_mark(); tree_node *node; diff -urN ../exim-4.94.orig/src/verify.c ./src/verify.c --- ../exim-4.94.orig/src/verify.c 2020-05-30 23:35:38.000000000 +0300 +++ ./src/verify.c 2021-05-15 15:06:16.131003000 +0300 @@ -674,7 +674,7 @@ if (!sx) sx = store_get(sizeof(*sx), TRUE); /* tainted buffers */ memset(sx, 0, sizeof(*sx)); - sx->addrlist = addr; + sx->addrlist = sx->first_addr = addr; sx->conn_args.host = host; sx->conn_args.host_af = host_af, sx->port = port; @@ -875,12 +875,12 @@ case PENDING_OK: done = TRUE; new_address_record.result = ccache_accept; break; - case FAIL: done = TRUE; + case FAIL: done = TRUE; yield = FAIL; *failure_ptr = US"recipient"; new_address_record.result = ccache_reject; break; - default: break; + default: break; } break; @@ -1705,7 +1705,8 @@ *failure_ptr = US"qualify"; return FAIL; } - address = rewrite_address_qualify(address, options & vopt_is_recipient); + /* deconst ok as address was not const */ + address = US rewrite_address_qualify(address, options & vopt_is_recipient); } DEBUG(D_verify) @@ -1720,7 +1721,8 @@ if (global_rewrite_rules) { uschar *old = address; - address = rewrite_address(address, options & vopt_is_recipient, FALSE, + /* deconst ok as address was not const */ + address = US rewrite_address(address, options & vopt_is_recipient, FALSE, global_rewrite_rules, rewrite_existflags); if (address != old) { diff -urN ../exim-4.94.orig/src/version.h ./src/version.h --- ../exim-4.94.orig/src/version.h 2020-06-01 17:32:25.000000000 +0300 +++ ./src/version.h 2021-05-15 15:06:16.918949000 +0300 @@ -1,5 +1,5 @@ /* automatically generated file - see ../scripts/reversion */ -#define EXIM_RELEASE_VERSION "4.94" +#define EXIM_RELEASE_VERSION "4.94.1-3-c1faf04b8" #ifdef EXIM_VARIANT_VERSION #define EXIM_VERSION_STR EXIM_RELEASE_VERSION "-" EXIM_VARIANT_VERSION #else diff -urN ../exim-4.94.orig/src/version.sh ./src/version.sh --- ../exim-4.94.orig/src/version.sh 2020-06-01 17:32:25.000000000 +0300 +++ ./src/version.sh 2021-05-15 15:06:16.918629000 +0300 @@ -1,3 +1,3 @@ # automatically generated file - see ../scripts/reversion -EXIM_RELEASE_VERSION="4.94" +EXIM_RELEASE_VERSION="4.94.1-3-c1faf04b8" EXIM_COMPILE_NUMBER="1" diff -urN ../exim-4.94.orig/util/gen_pkcs3.c ./util/gen_pkcs3.c --- ../exim-4.94.orig/util/gen_pkcs3.c 2020-05-30 23:35:38.000000000 +0300 +++ ./util/gen_pkcs3.c 2021-05-15 15:06:16.131579000 +0300 @@ -54,7 +54,6 @@ die_openssl_err(const char *msg) { char err_string[250]; - unsigned long e; ERR_error_string_n(ERR_get_error(), err_string, sizeof(err_string)); die("%s: %s", msg, err_string); @@ -71,9 +70,9 @@ int rc; len = strlen(text); - spaceless = malloc(len); + spaceless = malloc(len + 1); if (!spaceless) - die("malloc(%zu) failed: %s", len, strerror(errno)); + die("malloc(%zu) failed: %s", len + 1, strerror(errno)); for (p = spaceless, q = text, end = text + len; q < end; @@ -81,13 +80,15 @@ if (!isspace(*q)) *p++ = *q; } + len = p - spaceless; + *p++ = '\0'; b = NULL; rc = BN_hex2bn(&b, spaceless); - if (rc != p - spaceless) + if (rc != (int)len) die("BN_hex2bn did not convert entire input; took %d of %zu bytes", - rc, p - spaceless); + rc, len); return b; }