diff options
Diffstat (limited to 'src')
43 files changed, 1166 insertions, 607 deletions
diff --git a/src/OS/Makefile-Base b/src/OS/Makefile-Base index 77fb34742..bc5fbc42c 100644 --- a/src/OS/Makefile-Base +++ b/src/OS/Makefile-Base @@ -486,7 +486,7 @@ OBJ_EXIM = acl.o base64.o child.o crypt16.o daemon.o dbfn.o debug.o deliver.o \ directory.o dns.o drtables.o enq.o exim.o expand.o filter.o \ filtertest.o globals.o dkim.o dkim_transport.o dnsbl.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 \ @@ -793,6 +793,7 @@ md5.o: $(HDRS) md5.c 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 --git a/src/README.UPDATING b/src/README.UPDATING index 708027f2c..72bc97021 100644 --- a/src/README.UPDATING +++ b/src/README.UPDATING @@ -26,6 +26,24 @@ The rest of this document contains information about changes in 4.xx releases 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 ----------------- diff --git a/src/exim_monitor/em_version.c b/src/exim_monitor/em_version.c index c5931fc8d..336f2ae97 100644 --- a/src/exim_monitor/em_version.c +++ b/src/exim_monitor/em_version.c @@ -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 --git a/src/scripts/MakeLinks b/src/scripts/MakeLinks index 54ca7fb61..3e16ae13a 100755 --- a/src/scripts/MakeLinks +++ b/src/scripts/MakeLinks @@ -103,7 +103,7 @@ for f in blob.h dbfunctions.h dbstuff.h exim.h functions.h globals.h \ deliver.c directory.c dns.c dnsbl.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 --git a/src/src/acl.c b/src/src/acl.c index a6c3d4cbe..f358516a1 100644 --- a/src/src/acl.c +++ b/src/src/acl.c @@ -486,7 +486,7 @@ static control_def controls_list[] = { { 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 @@ -744,7 +744,7 @@ while ((s = (*func)())) 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. */ diff --git a/src/src/configure.default b/src/src/configure.default index 6127a9bf0..87f255aa9 100644 --- a/src/src/configure.default +++ b/src/src/configure.default @@ -458,6 +458,20 @@ acl_check_rcpt: 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 --git a/src/src/daemon.c b/src/src/daemon.c index 52e6dcab1..f40a58c75 100644 --- a/src/src/daemon.c +++ b/src/src/daemon.c @@ -471,6 +471,7 @@ if (pid == 0) 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 @@ -697,6 +698,7 @@ if (pid == 0) signal(SIGHUP, SIG_DFL); signal(SIGCHLD, SIG_DFL); signal(SIGTERM, SIG_DFL); + signal(SIGINT, SIG_DFL); if (geteuid() != root_uid && !deliver_drop_privilege) { @@ -930,7 +932,6 @@ while ((pid = waitpid(-1, &status, WNOHANG)) > 0) } - static void set_pid_file_path(void) { @@ -939,37 +940,150 @@ if (override_pid_file_path) if (!*pid_file_path) pid_file_path = string_sprintf("%s/exim-daemon.pid", spool_directory); + +if (pid_file_path[0] != '/') + log_write(0, LOG_PANIC_DIE, "pid file path %s must be absolute\n", pid_file_path); } -/* 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("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); } @@ -982,7 +1096,7 @@ daemon_die(void) { int pid; -DEBUG(D_any) debug_printf("SIGTERM seen\n"); +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 @@ -1058,7 +1172,7 @@ len = offsetof(struct sockaddr_un, sun_path) + 1 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 @@ -1836,22 +1950,14 @@ The variable daemon_write_pid is used to control this. */ 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("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); @@ -1888,6 +1994,7 @@ os_non_restarting_signal(SIGCHLD, main_sigchld_handler); 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. */ @@ -2175,6 +2282,7 @@ for (;;) 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(). */ @@ -2415,7 +2523,7 @@ for (;;) accept_retry_errno = errno; accept_retry_select_failed = select_failed; } - else if ( errno != accept_retry_errno + else if ( errno != accept_retry_errno || select_failed != accept_retry_select_failed || accept_retry_count >= 50) { diff --git a/src/src/dbfn.c b/src/src/dbfn.c index 0f56ad5a6..5cbe10c1f 100644 --- a/src/src/dbfn.c +++ b/src/src/dbfn.c @@ -63,8 +63,6 @@ log_write(0, LOG_MAIN, "Berkeley DB error: %s", msg); #endif - - /************************************************* * Open and lock a database file * *************************************************/ @@ -96,7 +94,6 @@ dbfn_open(uschar *name, int flags, open_db *dbblock, BOOL lof, BOOL panic) { int rc, save_errno; BOOL read_only = flags == O_RDONLY; -BOOL created = FALSE; flock_t lock_data; uschar dirname[PATHLEN], filename[PATHLEN]; @@ -118,12 +115,13 @@ exists, there is no error. */ snprintf(CS dirname, sizeof(dirname), "%s/db", spool_directory); snprintf(CS filename, sizeof(filename), "%s/%s.lockfile", dirname, name); +priv_drop_temp(exim_uid, exim_gid); if ((dbblock->lockfd = Uopen(filename, O_RDWR, EXIMDB_LOCKFILE_MODE)) < 0) { - created = TRUE; (void)directory_make(spool_directory, US"db", EXIMDB_DIRECTORY_MODE, panic); dbblock->lockfd = Uopen(filename, O_RDWR|O_CREAT, EXIMDB_LOCKFILE_MODE); } +priv_restore(); if (dbblock->lockfd < 0) { @@ -172,63 +170,17 @@ it easy to pin this down, there are now debug statements on either side of the open call. */ snprintf(CS filename, sizeof(filename), "%s/%s", dirname, name); -EXIM_DBOPEN(filename, dirname, flags, EXIMDB_MODE, &(dbblock->dbptr)); +priv_drop_temp(exim_uid, exim_gid); +EXIM_DBOPEN(filename, dirname, flags, EXIMDB_MODE, &(dbblock->dbptr)); if (!dbblock->dbptr && errno == ENOENT && flags == O_RDWR) { DEBUG(D_hints_lookup) debug_printf_indent("%s appears not to exist: trying to create\n", filename); - created = TRUE; EXIM_DBOPEN(filename, dirname, flags|O_CREAT, EXIMDB_MODE, &(dbblock->dbptr)); } - save_errno = errno; - -/* If we are running as root and this is the first access to the database, its -files will be owned by root. We want them to be owned by exim. We detect this -situation by noting above when we had to create the lock file or the database -itself. Because the different dbm libraries use different extensions for their -files, I don't know of any easier way of arranging this than scanning the -directory for files with the appropriate base name. At least this deals with -the lock file at the same time. Also, the directory will typically have only -half a dozen files, so the scan will be quick. - -This code is placed here, before the test for successful opening, because there -was a case when a file was created, but the DBM library still returned NULL -because of some problem. It also sorts out the lock file if that was created -but creation of the database file failed. */ - -if (created && geteuid() == root_uid) - { - DIR * dd; - uschar path[PATHLEN]; - uschar *lastname; - int namelen = Ustrlen(name); - - Ustrcpy(path, filename); - lastname = Ustrrchr(path, '/') + 1; - *lastname = 0; - - if ((dd = exim_opendir(path))) - 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(path, &statbuf) >= 0 && statbuf.st_uid != exim_uid) - { - DEBUG(D_hints_lookup) - debug_printf_indent("ensuring %s is owned by exim\n", path); - if (exim_chown(path, exim_uid, exim_gid)) - DEBUG(D_hints_lookup) - debug_printf_indent("failed setting %s to owned by exim\n", path); - } - } - - closedir(dd); - } +priv_restore(); /* If the open has failed, return NULL, leaving errno set. If lof is TRUE, log the event - also for debugging - but debug only if the file just doesn't diff --git a/src/src/deliver.c b/src/src/deliver.c index ba2948dfd..4e472ebe6 100644 --- a/src/src/deliver.c +++ b/src/src/deliver.c @@ -334,7 +334,7 @@ static int 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--) @@ -2097,9 +2097,9 @@ return FALSE; /* 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. diff --git a/src/src/dmarc.c b/src/src/dmarc.c index 599ecc454..750f9197f 100644 --- a/src/src/dmarc.c +++ b/src/src/dmarc.c @@ -53,9 +53,9 @@ static dmarc_exim_p dmarc_policy_description[] = { }; -void +void dmarc_version_report(FILE *f) -{ +{ const char *implementation, *version; fprintf(f, "Library version: dmarc: Compile: %d.%d.%d.%d\n", @@ -254,7 +254,7 @@ 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); +history_file_fd = log_create_as_exim(dmarc_history_file); if (history_file_fd < 0) { diff --git a/src/src/exim.c b/src/src/exim.c index fd1d87362..6a9a1477f 100644 --- a/src/src/exim.c +++ b/src/src/exim.c @@ -233,18 +233,8 @@ int fd; os_restarting_signal(sig, usr1_handler); -if ((fd = Uopen(process_log_path, O_APPEND|O_WRONLY, LOG_MODE)) < 0) - { - /* If we are already running as the Exim user, try to create it in the - current process (assuming spool_directory exists). Otherwise, if we are - root, do the creation in an exim:exim subprocess. */ - - int euid = geteuid(); - if (euid == exim_uid) - fd = Uopen(process_log_path, O_CREAT|O_APPEND|O_WRONLY, LOG_MODE); - else if (euid == root_uid) - fd = log_create_as_exim(process_log_path); - } +if (!process_log_path) return; +fd = log_open_as_exim(process_log_path); /* If we are neither exim nor root, or if we failed to create the log file, give up. There is not much useful we can do with errors, since we don't want @@ -760,6 +750,25 @@ vfprintf(stderr, fmt, ap); 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 @@ -1540,10 +1549,11 @@ Arguments: */ 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'; @@ -1557,7 +1567,7 @@ if (isupper(big_buffer[0])) 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); } @@ -1641,10 +1651,10 @@ uschar *cmdline_syslog_name = NULL; 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; @@ -1739,6 +1749,9 @@ f.running_in_test_harness = 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 @@ -2140,10 +2153,10 @@ on the second character (the one after '-'), to save some effort. */ { 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; @@ -2153,7 +2166,7 @@ on the second character (the one after '-'), to save some effort. */ 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; @@ -2611,7 +2624,7 @@ on the second character (the one after '-'), to save some effort. */ 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; @@ -2637,6 +2650,7 @@ on the second character (the one after '-'), to save some effort. */ 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 @@ -2733,9 +2747,9 @@ on the second character (the one after '-'), to save some effort. */ 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; @@ -2780,7 +2794,7 @@ on the second character (the one after '-'), to save some effort. */ /* -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; @@ -2788,7 +2802,7 @@ on the second character (the one after '-'), to save some effort. */ 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], FALSE); + 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; @@ -2861,7 +2875,7 @@ on the second character (the one after '-'), to save some effort. */ case 'r': case 's': if (++i < argc) { - continue_proxy_sni = string_copy_taint(argv[i], TRUE); + 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; @@ -2873,13 +2887,13 @@ on the second character (the one after '-'), to save some effort. */ 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*/ @@ -2906,6 +2920,7 @@ on the second character (the one after '-'), to save some effort. */ 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 @@ -2941,7 +2956,7 @@ on the second character (the one after '-'), to save some effort. */ 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) { @@ -3154,27 +3169,27 @@ on the second character (the one after '-'), to save some effort. */ /* -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 */ @@ -3194,19 +3209,19 @@ on the second character (the one after '-'), to save some effort. */ 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 */ @@ -3231,6 +3246,10 @@ on the second character (the one after '-'), to save some effort. */ -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; @@ -3256,10 +3275,11 @@ on the second character (the one after '-'), to save some effort. */ break; /* -oX <list>: 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; /* -oY: Override creation of daemon notifier socket */ @@ -3307,9 +3327,10 @@ on the second character (the one after '-'), to save some effort. */ 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<protocol>"), TRUE); else { + (void) exim_str_fail_toolong(argrest, (EXIM_DRIVERNAME_MAX+1+EXIM_HOSTNAME_MAX), "-p<protocol>:<host>"); received_protocol = string_copyn_taint(argrest, hn - argrest, TRUE); sender_host_name = string_copy_taint(hn + 1, TRUE); } @@ -3365,6 +3386,7 @@ on the second character (the one after '-'), to save some effort. */ { int i; for (argrest++, i = 0; argrest[i] && argrest[i] != '/'; ) i++; + exim_len_fail_toolong(i, EXIM_DRIVERNAME_MAX, "-q*G<name>"); queue_name = string_copyn(argrest, i); argrest += i; if (*argrest == '/') argrest++; @@ -3394,7 +3416,10 @@ on the second character (the one after '-'), to save some effort. */ 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, @@ -3405,35 +3430,41 @@ on the second character (the one after '-'), to save some effort. */ 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, @@ -3444,25 +3475,27 @@ on the second character (the one after '-'), to save some effort. */ 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. @@ -3544,10 +3577,12 @@ on the second character (the one after '-'), to save some effort. */ 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; @@ -3837,6 +3872,11 @@ during readconf_main() some expansion takes place already. */ /* Store the initial cwd before we change directories. Can be NULL if the dir has already been unlinked. */ initial_cwd = os_getcwd(NULL, 0); +if (!initial_cwd && 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 - @@ -4124,11 +4164,9 @@ if ( (debug_selector & D_any || LOGGING(arguments)) p += 13; else { - Ustrncpy(p + 4, initial_cwd, big_buffer_size-5); - p += 4 + Ustrlen(initial_cwd); - /* in case p is near the end and we don't provide enough space for - * string_format to be willing to write. */ - *p = '\0'; + p += 4; + snprintf(CS p, big_buffer_size - (p - big_buffer), "%s", CCS initial_cwd); + p += Ustrlen(CCS p); } (void)string_format(p, big_buffer_size - (p - big_buffer), " %d args:", argc); @@ -4515,14 +4553,14 @@ if (test_retry_arg >= 0) 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 @@ -4538,13 +4576,13 @@ if (test_retry_arg >= 0) /* 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) @@ -4646,11 +4684,11 @@ if (list_options) 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); } @@ -4695,6 +4733,7 @@ if (msg_action_arg > 0 && msg_action != MSG_LOAD) 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) @@ -4900,7 +4939,7 @@ if (test_rewrite_arg >= 0) 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); } @@ -4993,7 +5032,7 @@ if (verify_address_mode || f.address_test_mode) 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; @@ -5010,7 +5049,7 @@ if (verify_address_mode || f.address_test_mode) { 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(); @@ -5027,11 +5066,15 @@ if (expansion_test) dns_init(FALSE, FALSE, FALSE); if (msg_action_arg > 0 && msg_action == MSG_LOAD) { - uschar spoolname[256]; /* Not big_buffer; used in spool_read_header() */ + 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) @@ -5070,7 +5113,7 @@ if (expansion_test) 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 */ @@ -5513,7 +5556,9 @@ while (more) { 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 */ @@ -5548,11 +5593,12 @@ while (more) 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) { @@ -5650,10 +5696,10 @@ while (more) { 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 --git a/src/src/exim.h b/src/src/exim.h index bb8e4f6e2..8bbeecb4d 100644 --- a/src/src/exim.h +++ b/src/src/exim.h @@ -135,6 +135,51 @@ making unique names. */ # 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 <sys/types.h> #include <sys/file.h> #include <dirent.h> diff --git a/src/src/filter.c b/src/src/filter.c index 3897ae0f9..3f9f750b6 100644 --- a/src/src/filter.c +++ b/src/src/filter.c @@ -51,7 +51,7 @@ typedef struct condition_block { /* 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 @@ Returns: FF_DELIVERED success, a significant action was taken 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 @@ while (commands) { 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 @@ while (commands) 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 @@ while (commands) 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 @@ while (commands) 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 @@ while (commands) 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; @@ -2016,7 +2015,7 @@ while (commands) /* 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; @@ -2040,7 +2039,7 @@ while (commands) 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; @@ -2123,7 +2122,7 @@ while (commands) for (i = 0; i < MAILARGS_STRING_COUNT; i++) { uschar *p; - uschar *s = expargs[i]; + const uschar *s = expargs[i]; if (s == NULL) continue; @@ -2177,7 +2176,7 @@ while (commands) /* 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 */ @@ -2365,8 +2364,9 @@ Returns: TRUE if the message is deemed to be personal 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 --git a/src/src/functions.h b/src/src/functions.h index 7d6e3380e..84eb87397 100644 --- a/src/src/functions.h +++ b/src/src/functions.h @@ -316,8 +316,7 @@ extern int ip_streamsocket(const uschar *, uschar **, int, host_item *); 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); @@ -371,16 +370,18 @@ extern FILE *modefopen(const uschar *, const char *, mode_t); 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_find_at(const uschar *); extern const uschar *parse_fix_phrase(const uschar *, int); -extern uschar *parse_message_id(uschar *, uschar **, uschar **); +extern const uschar *parse_message_id(const uschar *, uschar **, uschar **); extern const uschar *parse_quote_2047(const uschar *, int, uschar *, BOOL); -extern uschar *parse_date_time(uschar *str, time_t *t); +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); @@ -408,7 +409,7 @@ extern void readconf_driver_init(uschar *, driver_instance **, 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); @@ -434,14 +435,14 @@ extern retry_config *retry_find_config(const uschar *, const uschar *, int, int) 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 **, @@ -609,9 +610,9 @@ extern BOOL transport_headers_send(transport_ctx *, 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 *); diff --git a/src/src/globals.c b/src/src/globals.c index c45e8a930..e1837b67d 100644 --- a/src/src/globals.c +++ b/src/src/globals.c @@ -227,6 +227,7 @@ struct global_flags f = .authentication_local = FALSE, .background_daemon = TRUE, + .bdat_readers_wanted = FALSE, .chunking_offered = FALSE, .config_changed = FALSE, @@ -1295,7 +1296,7 @@ uschar *recipient_verify_failure = NULL; 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 --git a/src/src/globals.h b/src/src/globals.h index ed7cffb76..4beb9d07e 100644 --- a/src/src/globals.h +++ b/src/src/globals.h @@ -185,6 +185,7 @@ extern struct global_flags { BOOL authentication_local :1; /* TRUE if non-smtp (implicit authentication) */ BOOL background_daemon :1; /* Set FALSE to keep in foreground */ + BOOL bdat_readers_wanted :1; /* BDAT-handling to be pushed on readfunc stack */ BOOL chunking_offered :1; BOOL config_changed :1; /* True if -C used */ diff --git a/src/src/log.c b/src/src/log.c index 2cf578069..bb6902ea0 100644 --- a/src/src/log.c +++ b/src/src/log.c @@ -265,14 +265,19 @@ overwrite it temporarily if it is necessary to create the directory. Returns: a file descriptor, or < 0 on failure (errno set) */ -int -log_create(uschar *name) +static int +log_open_already_exim(uschar * const name) { -int fd = Uopen(name, -#ifdef O_CLOEXEC - O_CLOEXEC | -#endif - O_CREAT|O_APPEND|O_WRONLY, LOG_MODE); +int fd = -1; +const int flags = O_WRONLY | O_APPEND | O_CREAT | O_NONBLOCK; + +if (geteuid() != exim_uid) + { + errno = EACCES; + return -1; + } + +fd = Uopen(name, flags, LOG_MODE); /* If creation failed, attempt to build a log directory in case that is the problem. */ @@ -286,11 +291,7 @@ if (fd < 0 && errno == ENOENT) DEBUG(D_any) debug_printf("%s log directory %s\n", created ? "created" : "failed to create", name); *lastslash = '/'; - if (created) fd = Uopen(name, -#ifdef O_CLOEXEC - O_CLOEXEC | -#endif - O_CREAT|O_APPEND|O_WRONLY, LOG_MODE); + if (created) fd = Uopen(name, flags, LOG_MODE); } return fd; @@ -298,6 +299,81 @@ return fd; +/* Inspired by OpenSSH's mm_send_fd(). Thanks! */ + +static int +log_send_fd(const int sock, const int fd) +{ +struct msghdr msg; +union { + struct cmsghdr hdr; + char buf[CMSG_SPACE(sizeof(int))]; +} cmsgbuf; +struct cmsghdr *cmsg; +struct iovec vec; +char ch = 'A'; +ssize_t n; + +memset(&msg, 0, sizeof(msg)); +memset(&cmsgbuf, 0, sizeof(cmsgbuf)); +msg.msg_control = &cmsgbuf.buf; +msg.msg_controllen = sizeof(cmsgbuf.buf); + +cmsg = CMSG_FIRSTHDR(&msg); +cmsg->cmsg_len = CMSG_LEN(sizeof(int)); +cmsg->cmsg_level = SOL_SOCKET; +cmsg->cmsg_type = SCM_RIGHTS; +*(int *)CMSG_DATA(cmsg) = fd; + +vec.iov_base = &ch; +vec.iov_len = 1; +msg.msg_iov = &vec; +msg.msg_iovlen = 1; + +while ((n = sendmsg(sock, &msg, 0)) == -1 && errno == EINTR); +if (n != 1) return -1; +return 0; +} + +/* Inspired by OpenSSH's mm_receive_fd(). Thanks! */ + +static int +log_recv_fd(const int sock) +{ +struct msghdr msg; +union { + struct cmsghdr hdr; + char buf[CMSG_SPACE(sizeof(int))]; +} cmsgbuf; +struct cmsghdr *cmsg; +struct iovec vec; +ssize_t n; +char ch = '\0'; +int fd = -1; + +memset(&msg, 0, sizeof(msg)); +vec.iov_base = &ch; +vec.iov_len = 1; +msg.msg_iov = &vec; +msg.msg_iovlen = 1; + +memset(&cmsgbuf, 0, sizeof(cmsgbuf)); +msg.msg_control = &cmsgbuf.buf; +msg.msg_controllen = sizeof(cmsgbuf.buf); + +while ((n = recvmsg(sock, &msg, 0)) == -1 && errno == EINTR); +if (n != 1 || ch != 'A') return -1; + +cmsg = CMSG_FIRSTHDR(&msg); +if (cmsg == NULL) return -1; +if (cmsg->cmsg_type != SCM_RIGHTS) return -1; +fd = *(const int *)CMSG_DATA(cmsg); +if (fd < 0) return -1; +return fd; +} + + + /************************************************* * Create a log file as the exim user * *************************************************/ @@ -313,41 +389,60 @@ Returns: a file descriptor, or < 0 on failure (errno set) */ int -log_create_as_exim(uschar *name) +log_open_as_exim(uschar * const name) { -pid_t pid = 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 (getuid() != exim_uid || geteuid() != exim_uid) _exit(EXIT_FAILURE); + if (getgid() != exim_gid || getegid() != exim_gid) _exit(EXIT_FAILURE); + + fd = log_open_already_exim(name); + if (fd < 0) _exit(EXIT_FAILURE); + if (log_send_fd(sock[1], fd) != 0) _exit(EXIT_FAILURE); + (void)close(sock[1]); + _exit(EXIT_SUCCESS); + } -/* If we created a subprocess, wait for it. If it succeeded, try the open. */ - -while (pid > 0 && waitpid(pid, &status, 0) != pid); -if (status == 0) fd = Uopen(name, -#ifdef O_CLOEXEC - O_CLOEXEC | -#endif - O_APPEND|O_WRONLY, LOG_MODE); + (void)close(sock[1]); + if (pid > 0) + { + fd = log_recv_fd(sock[0]); + while (waitpid(pid, NULL, 0) == -1 && errno == EINTR); + } + (void)close(sock[0]); + } + } -/* If we failed to create a subprocess, we are in a bad way. We return -with fd still < 0, and errno set, letting the caller handle the error. */ +if (fd >= 0) + { + int flags; + flags = fcntl(fd, F_GETFD); + if (flags != -1) (void)fcntl(fd, F_SETFD, flags | FD_CLOEXEC); + flags = fcntl(fd, F_GETFL); + if (flags != -1) (void)fcntl(fd, F_SETFL, flags & ~O_NONBLOCK); + } +else + { + errno = EACCES; + } return fd; } @@ -462,52 +557,17 @@ if (!ok) die(US"exim: log file path too long: aborting", US"Logging failure; please try later"); -/* We now have the file name. Try to open an existing file. After a successful -open, arrange for automatic closure on exec(), and then return. */ +/* We now have the file name. After a successful open, return. */ -*fd = Uopen(buffer, -#ifdef O_CLOEXEC - O_CLOEXEC | -#endif - O_APPEND|O_WRONLY, LOG_MODE); +*fd = log_open_as_exim(buffer); if (*fd >= 0) { -#ifndef O_CLOEXEC - (void)fcntl(*fd, F_SETFD, fcntl(*fd, F_GETFD) | FD_CLOEXEC); -#endif return; } -/* Open was not successful: try creating the file. If this is a root process, -we must do the creating in a subprocess set to exim:exim in order to ensure -that the file is created with the right ownership. Otherwise, there can be a -race if another Exim process is trying to write to the log at the same time. -The use of SIGUSR1 by the exiwhat utility can provoke a lot of simultaneous -writing. */ - euid = geteuid(); -/* If we are already running as the Exim user (even if that user is root), -we can go ahead and create in the current process. */ - -if (euid == exim_uid) *fd = log_create(buffer); - -/* Otherwise, if we are root, do the creation in an exim:exim subprocess. If we -are neither exim nor root, creation is not attempted. */ - -else if (euid == root_uid) *fd = log_create_as_exim(buffer); - -/* If we now have an open file, set the close-on-exec flag and return. */ - -if (*fd >= 0) - { -#ifndef O_CLOEXEC - (void)fcntl(*fd, F_SETFD, fcntl(*fd, F_GETFD) | FD_CLOEXEC); -#endif - return; - } - /* Creation failed. There are some circumstances in which we get here when the effective uid is not root or exim, which is the problem. (For example, a non-setuid binary with log_arguments set, called in certain ways.) Rather than @@ -903,6 +963,7 @@ if (!(flags & (LOG_MAIN|LOG_PANIC|LOG_REJECT))) if (f.disable_logging) { DEBUG(D_any) debug_printf("log writing disabled\n"); + if ((flags & LOG_PANIC_DIE) == LOG_PANIC_DIE) exim_exit(EXIT_FAILURE); return; } diff --git a/src/src/macro_predef.c b/src/src/macro_predef.c index b0f57be19..5340c5af8 100644 --- a/src/src/macro_predef.c +++ b/src/src/macro_predef.c @@ -72,7 +72,7 @@ options_from_list(optionlist * opts, unsigned nopt, 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 --git a/src/src/macros.h b/src/src/macros.h index 43217807f..f8987d604 100644 --- a/src/src/macros.h +++ b/src/src/macros.h @@ -154,7 +154,9 @@ enough to hold all the headers from a normal kind of message. */ /* 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 */ @@ -163,10 +165,12 @@ as long as the maximum path length. */ # 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 @@ -180,12 +184,6 @@ written on the spool, it gets read into big_buffer. */ #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. */ diff --git a/src/src/malware.c b/src/src/malware.c index 831e9af1f..a6e354bc4 100644 --- a/src/src/malware.c +++ b/src/src/malware.c @@ -109,7 +109,7 @@ features_malware(void) { const uschar * s; uschar * t; -uschar buf[64]; +uschar buf[EXIM_DRIVERNAME_MAX]; spf(buf, sizeof(buf), US"_HAVE_MALWARE_"); diff --git a/src/src/moan.c b/src/src/moan.c index bf5483ce3..1d9c5b9a5 100644 --- a/src/src/moan.c +++ b/src/src/moan.c @@ -86,7 +86,8 @@ if (h || message_id) fprintf(fp, "References:"); if (h) { - uschar * s, * id, * error; + const uschar * s; + uschar * id, * error; uschar * referenced_ids[12]; int reference_count = 0; diff --git a/src/src/parse.c b/src/src/parse.c index 885a01c0d..fa3395206 100644 --- a/src/src/parse.c +++ b/src/src/parse.c @@ -12,7 +12,7 @@ #include "exim.h" -static uschar *last_comment_position; +static const uschar *last_comment_position; @@ -143,21 +143,21 @@ Argument: pointer to an address, possibly unqualified 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 @@ Argument: current character pointer 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 @@ Arguments: 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 @@ Arguments: 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 @@ Arguments: 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 @@ Arguments: 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 @@ Returns: points to the extracted address, or NULL on error #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 @@ while (isspace(endptr[-1])) endptr--; *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; @@ -984,7 +984,12 @@ if (i < len) /* No non-printers; use the RFC 822 quoting rules */ -buffer = store_get(len*4, is_tainted(phrase)); +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; @@ -1129,9 +1134,12 @@ while (s < end) { if (ss >= end) ss--; *t++ = '('; - Ustrncpy(t, s, ss-s); - t += ss-s; - s = ss; + if (ss > s) + { + Ustrncpy(t, s, ss-s); + t += ss-s; + s = ss; + } } } @@ -1582,7 +1590,7 @@ for (;;) else { int start, end, domain; - uschar *recipient = NULL; + const uschar *recipient = NULL; int save = s[len]; s[len] = 0; @@ -1678,8 +1686,8 @@ for (;;) 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 @@ -1713,8 +1721,8 @@ Arguments: 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; @@ -1754,8 +1762,7 @@ while (*id) id++; *id++ = 0; store_release_above(id); -str = skip_comment(str); -return str; +return skip_comment(str); } @@ -1773,16 +1780,16 @@ Arguments: 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; } @@ -1799,8 +1806,8 @@ Arguments: 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 @@ -1815,17 +1822,16 @@ static const uschar *day_name[7]={ US"mon", US"tue", US"wed", US"thu", US"fri", 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); } @@ -1845,8 +1851,8 @@ Arguments: 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 @@ -1868,36 +1874,39 @@ day = ([FWS] 1*2DIGIT) / obs-day 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; @@ -1922,8 +1931,8 @@ Arguments: 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 @@ -1958,61 +1967,61 @@ obs-zone = "UT" / "GMT" / ; Universal Time %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; @@ -2032,8 +2041,8 @@ Arguments: 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] @@ -2045,27 +2054,26 @@ extern char **environ; 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); } diff --git a/src/src/pdkim/pdkim.c b/src/src/pdkim/pdkim.c index ca16e2b74..45fa2a104 100644 --- a/src/src/pdkim/pdkim.c +++ b/src/src/pdkim/pdkim.c @@ -107,7 +107,7 @@ pdkim_combined_canon_entry pdkim_combined_canons[] = { }; -static blob lineending = {.data = US"\r\n", .len = 2}; +static const blob lineending = {.data = US"\r\n", .len = 2}; /* -------------------------------------------------------------------------- */ uschar * @@ -720,9 +720,11 @@ return NULL; If we have to relax the data for this sig, return our copy of it. */ static blob * -pdkim_update_ctx_bodyhash(pdkim_bodyhash * b, blob * orig_data, blob * relaxed_data) +pdkim_update_ctx_bodyhash(pdkim_bodyhash * b, const blob * orig_data, blob * relaxed_data) { -blob * canon_data = orig_data; +const blob * canon_data = orig_data; +size_t left; + /* Defaults to simple canon (no further treatment necessary) */ if (b->canon_method == PDKIM_CANON_RELAXED) @@ -767,16 +769,17 @@ if (b->canon_method == PDKIM_CANON_RELAXED) } /* Make sure we don't exceed the to-be-signed body length */ +left = canon_data->len; if ( b->bodylength >= 0 - && b->signed_body_bytes + (unsigned long)canon_data->len > b->bodylength + && left > (unsigned long)b->bodylength - b->signed_body_bytes ) - canon_data->len = b->bodylength - b->signed_body_bytes; + left = (unsigned long)b->bodylength - b->signed_body_bytes; -if (canon_data->len > 0) +if (left > 0) { - exim_sha_update(&b->body_hash_ctx, CUS canon_data->data, canon_data->len); - b->signed_body_bytes += canon_data->len; - DEBUG(D_acl) pdkim_quoteprint(canon_data->data, canon_data->len); + exim_sha_update(&b->body_hash_ctx, CUS canon_data->data, left); + b->signed_body_bytes += left; + DEBUG(D_acl) pdkim_quoteprint(canon_data->data, left); } return relaxed_data; @@ -822,7 +825,7 @@ for (pdkim_signature * sig = ctx->sig; sig; sig = sig->next) /* VERIFICATION --------------------------------------------------------- */ /* Be careful that the header sig included a bodyash */ - if ( sig->bodyhash.data + if (sig->bodyhash.data && sig->bodyhash.len == b->bh.len && memcmp(b->bh.data, sig->bodyhash.data, b->bh.len) == 0) { DEBUG(D_acl) debug_printf("DKIM [%s] Body hash compared OK\n", sig->domain); @@ -1003,7 +1006,7 @@ else last_sig->next = sig; } - if (--dkim_collect_input == 0) + if (dkim_collect_input && --dkim_collect_input == 0) { ctx->headers = pdkim_prepend_stringlist(ctx->headers, ctx->cur_header->s); ctx->cur_header->s[ctx->cur_header->ptr = 0] = '\0'; diff --git a/src/src/priv.c b/src/src/priv.c new file mode 100644 index 000000000..94d425401 --- /dev/null +++ b/src/src/priv.c @@ -0,0 +1,76 @@ +#include "exim.h" +#include <sys/types.h> +#include <unistd.h> +#include <string.h> + +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 --git a/src/src/queue.c b/src/src/queue.c index 4c93c1d7f..567784575 100644 --- a/src/src/queue.c +++ b/src/src/queue.c @@ -396,12 +396,18 @@ if (!recurse) p += sprintf(CS p, " -q%s", extras); if (deliver_selectstring) - p += sprintf(CS p, " -R%s %s", f.deliver_selectstring_regex? "r" : "", - deliver_selectstring); + { + snprintf(CS p, big_buffer_size - (p - big_buffer), " -R%s %s", + f.deliver_selectstring_regex? "r" : "", deliver_selectstring); + p += 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 --git a/src/src/rda.c b/src/src/rda.c index 5615b64d5..fb3714ea2 100644 --- a/src/src/rda.c +++ b/src/src/rda.c @@ -618,9 +618,14 @@ search_tidyup(); 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 --git a/src/src/readconf.c b/src/src/readconf.c index e8e310beb..816133329 100644 --- a/src/src/readconf.c +++ b/src/src/readconf.c @@ -422,7 +422,7 @@ options_from_list(optionlist_config, nelem(optionlist_config), US"MAIN", NULL); void options_auths(void) { -uschar buf[64]; +uschar buf[EXIM_DRIVERNAME_MAX]; options_from_list(optionlist_auths, optionlist_auths_size, US"AUTHENTICATORS", NULL); @@ -439,7 +439,7 @@ for (struct auth_info * ai = auths_available; ai->driver_name[0]; ai++) 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++) { @@ -684,7 +684,7 @@ Returns: FALSE iff fatal error BOOL macro_read_assignment(uschar *s) { -uschar name[64]; +uschar name[EXIM_DRIVERNAME_MAX]; int namelen = 0; BOOL redef = FALSE; macro_item *m; @@ -1178,15 +1178,25 @@ uschar * 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; } @@ -1311,7 +1321,7 @@ Returns: pointer to an option entry, or NULL if not found */ 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) @@ -1350,10 +1360,10 @@ Returns: a pointer to the boolean flag. */ 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, @@ -1626,8 +1636,8 @@ uschar *inttype = US""; 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. */ @@ -2089,7 +2099,7 @@ switch (type) 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) @@ -2426,7 +2436,7 @@ Returns: boolean success */ 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; @@ -2436,7 +2446,7 @@ void *value; uid_t *uidlist; gid_t *gidlist; uschar *s; -uschar name2[64]; +uschar name2[EXIM_DRIVERNAME_MAX]; if (!ol) { @@ -2736,7 +2746,7 @@ Returns: Boolean success */ 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; @@ -2875,7 +2885,7 @@ if (!type) 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); } @@ -3706,7 +3716,7 @@ uschar *buffer; 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 @@ -4234,7 +4244,7 @@ acl_line = get_config_line(); while(acl_line) { - uschar name[64]; + uschar name[EXIM_DRIVERNAME_MAX]; tree_node *node; uschar *error; diff --git a/src/src/receive.c b/src/src/receive.c index b0dacbd68..2223d4645 100644 --- a/src/src/receive.c +++ b/src/src/receive.c @@ -490,6 +490,13 @@ if (recipients_count >= recipients_list_max) { recipient_item *oldlist = recipients_list; int oldmax = recipients_list_max; + + const int safe_recipients_limit = INT_MAX / 2 / sizeof(recipient_item); + if (recipients_list_max < 0 || recipients_list_max >= safe_recipients_limit) + { + log_write(0, LOG_MAIN|LOG_PANIC_DIE, "Too many recipients: %d", recipients_list_max); + } + recipients_list_max = recipients_list_max ? 2*recipients_list_max : 50; recipients_list = store_get(recipients_list_max * sizeof(recipient_item), FALSE); if (oldlist) @@ -2122,7 +2129,8 @@ OVERSIZE: 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)) { @@ -2502,7 +2510,7 @@ if (extract_recip) { 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); } @@ -2553,11 +2561,12 @@ if (extract_recip) &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 @@ -2789,8 +2798,8 @@ recipients will get here only if the conditions were right (allow_unqualified_ 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 @@ -2965,7 +2974,8 @@ it has already been rewritten as part of verification for SMTP input. */ 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); diff --git a/src/src/rewrite.c b/src/src/rewrite.c index d003c6ce0..6a6da47df 100644 --- a/src/src/rewrite.c +++ b/src/src/rewrite.c @@ -59,12 +59,12 @@ Arguments: 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 @@ Returns: new address if rewritten; the input address if no change; 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 @@ for (rewrite_rule * rule = rewrite_rules; int count = 0; uschar *save_localpart; const uschar *save_domain; - uschar *error, *new, *newparsed; + uschar *error, *new; + const uschar * newparsed; /* Come back here for a repeat after a successful rewrite. We do this only so many times. */ @@ -181,7 +182,7 @@ for (rewrite_rule * rule = rewrite_rules; set up as an expansion variable */ domain[-1] = 0; - deliver_localpart = subject; + deliver_localpart = US subject; deliver_domain = domain; new = expand_string(rule->replacement); @@ -386,15 +387,16 @@ Arguments: 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; } @@ -466,8 +468,9 @@ while (*s) { 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; @@ -484,10 +487,35 @@ while (*s) 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; } @@ -526,7 +554,8 @@ while (*s) { 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; @@ -549,7 +578,8 @@ while (*s) if (existflags & flag) { 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; @@ -741,7 +771,8 @@ Argument: the address to test Returns: nothing */ -void rewrite_test(uschar *s) +void +rewrite_test(const uschar *s) { uschar *recipient, *error; int start, end, domain; @@ -758,8 +789,8 @@ pretending it is a sender. */ 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) @@ -792,7 +823,7 @@ for (int i = 0; i < 8; i++) { 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 --git a/src/src/smtp_in.c b/src/src/smtp_in.c index d60e7d5c5..8e9b93ab3 100644 --- a/src/src/smtp_in.c +++ b/src/src/smtp_in.c @@ -593,6 +593,11 @@ if (n > 0) } +/* 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 @@ for(;;) if (chunking_data_left > 0) return lwr_receive_getc(chunking_data_left--); - receive_getc = lwr_receive_getc; - receive_getbuf = lwr_receive_getbuf; - receive_ungetc = lwr_receive_ungetc; + bdat_pop_receive_functions(); #ifndef DISABLE_DKIM dkim_save = dkim_collect_input; dkim_collect_input = 0; @@ -730,9 +733,7 @@ next_cmd: goto repeat_until_rset; } - receive_getc = bdat_getc; - receive_getbuf = bdat_getbuf; /* r~getbuf is never actually used */ - receive_ungetc = bdat_ungetc; + bdat_push_receive_functions(); #ifndef DISABLE_DKIM dkim_collect_input = dkim_save; #endif @@ -765,9 +766,7 @@ while (chunking_data_left) if (!bdat_getbuf(&n)) break; } -receive_getc = lwr_receive_getc; -receive_getbuf = lwr_receive_getbuf; -receive_ungetc = lwr_receive_ungetc; +bdat_pop_receive_functions(); if (chunking_state != CHUNKING_LAST) { @@ -777,7 +776,44 @@ if (chunking_state != CHUNKING_LAST) } +static inline void +bdat_push_receive_functions(void) +{ +/* push the current receive_* function on the "stack", and +replace them by bdat_getc(), which in turn will use the lwr_receive_* +functions to do the dirty work. */ +if (lwr_receive_getc == NULL) + { + lwr_receive_getc = receive_getc; + lwr_receive_getbuf = receive_getbuf; + lwr_receive_ungetc = receive_ungetc; + } +else + { + DEBUG(D_receive) debug_printf("chunking double-push receive functions\n"); + } + +receive_getc = bdat_getc; +receive_getbuf = bdat_getbuf; +receive_ungetc = bdat_ungetc; +} + +static inline void +bdat_pop_receive_functions(void) +{ +if (lwr_receive_getc == NULL) + { + DEBUG(D_receive) debug_printf("chunking double-pop receive functions\n"); + return; + } +receive_getc = lwr_receive_getc; +receive_getbuf = lwr_receive_getbuf; +receive_ungetc = lwr_receive_ungetc; +lwr_receive_getc = NULL; +lwr_receive_getbuf = NULL; +lwr_receive_ungetc = NULL; +} /************************************************* * SMTP version of ungetc() * @@ -795,6 +831,9 @@ Returns: the character int smtp_ungetc(int ch) { +if (smtp_inptr <= smtp_inbuffer) + log_write(0, LOG_MAIN|LOG_PANIC_DIE, "buffer underflow in smtp_ungetc"); + *--smtp_inptr = ch; return ch; } @@ -804,6 +843,7 @@ int bdat_ungetc(int ch) { chunking_data_left++; +bdat_push_receive_functions(); /* we're not done yet, calling push is safe, because it checks the state before pushing anything */ return lwr_receive_ungetc(ch); } @@ -1967,29 +2007,35 @@ static BOOL extract_option(uschar **name, uschar **value) { uschar *n; -uschar *v = smtp_cmd_data + Ustrlen(smtp_cmd_data) - 1; -while (isspace(*v)) v--; +uschar *v; +if (Ustrlen(smtp_cmd_data) <= 0) return FALSE; +v = smtp_cmd_data + Ustrlen(smtp_cmd_data) - 1; +while (v > smtp_cmd_data && isspace(*v)) v--; v[1] = 0; + while (v > smtp_cmd_data && *v != '=' && !isspace(*v)) { /* Take care to not stop at a space embedded in a quoted local-part */ - - if (*v == '"') do v--; while (*v != '"' && v > smtp_cmd_data+1); + if (*v == '"') + { + do v--; while (v > smtp_cmd_data && *v != '"'); + if (v <= smtp_cmd_data) return FALSE; + } v--; } +if (v <= smtp_cmd_data) return FALSE; n = v; if (*v == '=') { - while(isalpha(n[-1])) n--; + while (n > smtp_cmd_data && isalpha(n[-1])) n--; /* RFC says SP, but TAB seen in wild and other major MTAs accept it */ - if (!isspace(n[-1])) return FALSE; + if (n <= smtp_cmd_data || !isspace(n[-1])) return FALSE; n[-1] = 0; } else { n++; - if (v == smtp_cmd_data) return FALSE; } *v++ = 0; *name = n; @@ -2205,9 +2251,11 @@ while (done <= 0) /* 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 */ @@ -2227,7 +2275,8 @@ while (done <= 0) && 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); } @@ -2266,7 +2315,8 @@ while (done <= 0) 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; @@ -2285,7 +2335,8 @@ while (done <= 0) { 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 @@ -2527,6 +2578,9 @@ receive_ungetc = smtp_ungetc; 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; @@ -3812,7 +3866,8 @@ if (f.allow_unqualified_recipient || strcmpic(*recipient, US"postmaster") == 0) 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, @@ -3953,6 +4008,14 @@ cmd_list[CMD_LIST_EHLO].is_mail_cmd = TRUE; 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; @@ -4855,7 +4918,8 @@ while (done <= 0) 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; @@ -4917,7 +4981,8 @@ while (done <= 0) 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); } @@ -4987,6 +5052,10 @@ while (done <= 0) 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; @@ -5109,7 +5178,8 @@ while (done <= 0) 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; @@ -5142,7 +5212,7 @@ while (done <= 0) /* 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) { @@ -5287,16 +5357,7 @@ while (done <= 0) 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; @@ -5305,6 +5366,7 @@ while (done <= 0) 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 @@ -5333,7 +5395,10 @@ while (done <= 0) : US"valid RCPT command must precede BDAT"); if (chunking_state > CHUNKING_OFFERED) + { + bdat_push_receive_functions(); bdat_flush_data(); + } break; } @@ -5342,6 +5407,12 @@ while (done <= 0) 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; } @@ -5379,6 +5450,9 @@ while (done <= 0) "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, diff --git a/src/src/smtp_out.c b/src/src/smtp_out.c index 2a4497488..f103c2752 100644 --- a/src/src/smtp_out.c +++ b/src/src/smtp_out.c @@ -692,7 +692,7 @@ Arguments: 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 @@ -733,6 +733,7 @@ for (;;) { *p = 0; /* Leave malformed line for error message */ errno = ERRNO_SMTPFORMAT; + inblock->ptr = ptr; return -1; } } @@ -758,6 +759,7 @@ for (;;) /* Get here if there has been some kind of recv() error; errno is set, but we ensure that the result buffer is empty before returning. */ +inblock->ptr = inblock->ptrend = inblock->buffer; *buffer = 0; return -1; } diff --git a/src/src/spool_in.c b/src/src/spool_in.c index f64c52c5a..0bebcea97 100644 --- a/src/src/spool_in.c +++ b/src/src/spool_in.c @@ -304,6 +304,35 @@ dsn_ret = 0; dsn_envid = NULL; } +static void * +fgets_big_buffer(FILE *fp) +{ +int len = 0; + +big_buffer[0] = 0; +if (Ufgets(big_buffer, big_buffer_size, fp) == NULL) return NULL; + +while ((len = Ustrlen(big_buffer)) == big_buffer_size-1 + && big_buffer[len-1] != '\n') + { + uschar *newbuffer; + int newsize; + + if (big_buffer_size >= BIG_BUFFER_SIZE * 4) return NULL; + newsize = big_buffer_size * 2; + newbuffer = store_get_perm(newsize, 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 * @@ -454,26 +483,13 @@ If the line starts with "--" the content of the variable is tainted. */ 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); @@ -774,7 +790,7 @@ for (recipients_count = 0; recipients_count < rcount; recipients_count++) 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; diff --git a/src/src/spool_out.c b/src/src/spool_out.c index bbc798fb4..8531112c0 100644 --- a/src/src/spool_out.c +++ b/src/src/spool_out.c @@ -105,6 +105,18 @@ return fd; +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) { @@ -223,7 +235,7 @@ if (body_zerocount > 0) fprintf(fp, "-body_zerocount %d\n", body_zerocount); 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"); @@ -296,19 +308,20 @@ fprintf(fp, "%d\n", recipients_count); 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 --git a/src/src/std-crypto.c b/src/src/std-crypto.c index e4df56006..b462908ea 100644 --- a/src/src/std-crypto.c +++ b/src/src/std-crypto.c @@ -350,12 +350,12 @@ static const char dh_ike_18_pem[] = */ 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_22_pem[] = */ 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 @@ static const char dh_ike_23_pem[] = "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_23_pem[] = */ 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 @@ static const char dh_ike_24_pem[] = "+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 @@ A.1. ffdhe2048 */ 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 @@ A.2. ffdhe3072 */ 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 @@ static const char dh_ffdhe3072_pem[] = "ssbzSibBsu/6iGtCOGEfz9zeNVs7ZRkDW7w09N75nAI4YbRvydbmyQd62R0mkff3\n" "7lmMsPrBhtkcrv4TCYUTknC0EwyTvEN5RPT9RFLi103TZPLiHnH1S/9croKrnJ32\n" "nuhtK8UiNjoNq8Uhl5sN6todv5pC1cRITgq80Gv6U93vPBsg7j/VnXwl5B0rZsYu\n" -"N///////////AgEC\n" +"N///////////AgECAgIL/w==\n" "-----END DH PARAMETERS-----\n"; /* @@ -654,7 +656,7 @@ A.3. ffdhe4096 */ 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 @@ static const char dh_ffdhe4096_pem[] = "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 @@ A.4. ffdhe6144 */ 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 @@ static const char dh_ffdhe6144_pem[] = "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 @@ A.5. ffdhe8192 */ 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 @@ static const char dh_ffdhe8192_pem[] = "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 --git a/src/src/store.c b/src/src/store.c index 22615ea08..2a32e9b5c 100644 --- a/src/src/store.c +++ b/src/src/store.c @@ -268,6 +268,17 @@ store_get_3(int size, BOOL tainted, const char *func, int linenumber) { 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 @@ -417,6 +428,11 @@ int pool = tainted ? store_pool + POOL_TAINT_BASE : store_pool; 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. */ @@ -784,6 +800,11 @@ if (is_tainted(block) != tainted) 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); @@ -814,6 +835,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); + size += sizeof(int); /* space to store the size, used under debug */ if (size < 16) size = 16; diff --git a/src/src/string.c b/src/src/string.c index cb9132420..64d14439e 100644 --- a/src/src/string.c +++ b/src/src/string.c @@ -1094,7 +1094,16 @@ existing length of the string. */ unsigned inc = oldsize < 4096 ? 127 : 1023; +if (g->ptr < 0 || g->ptr > g->size || g->size >= INT_MAX/2) + log_write(0, LOG_MAIN|LOG_PANIC_DIE, + "internal error in gstring_grow (ptr %d size %d)", g->ptr, g->size); + if (count <= 0) return; + +if (count >= INT_MAX/2 - g->ptr) + log_write(0, LOG_MAIN|LOG_PANIC_DIE, + "internal error in gstring_grow (ptr %d count %d)", g->ptr, count); + g->size = (p + count + inc + 1) & ~inc; /* one for a NUL */ /* Try to extend an existing allocation. If the result of calling @@ -1143,6 +1152,10 @@ string_catn(gstring * g, const uschar *s, int count) 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; @@ -1152,8 +1165,12 @@ if (!g) 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 --git a/src/src/tls-openssl.c b/src/src/tls-openssl.c index 13b0c232f..eb18d64d3 100644 --- a/src/src/tls-openssl.c +++ b/src/src/tls-openssl.c @@ -4053,16 +4053,12 @@ if (more || corked) { 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 --git a/src/src/tls.c b/src/src/tls.c index ddee95de2..e073eadbe 100644 --- a/src/src/tls.c +++ b/src/src/tls.c @@ -457,6 +457,9 @@ Returns: the character int tls_ungetc(int ch) { +if (ssl_xfer_buffer_lwm <= 0) + log_write(0, LOG_MAIN|LOG_PANIC_DIE, "buffer underflow in tls_ungetc"); + ssl_xfer_buffer[--ssl_xfer_buffer_lwm] = ch; return ch; } diff --git a/src/src/transport.c b/src/src/transport.c index d8fd85855..3d86919de 100644 --- a/src/src/transport.c +++ b/src/src/transport.c @@ -959,10 +959,10 @@ if (!(tctx->options & topt_no_headers)) 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 */ @@ -1729,7 +1729,7 @@ while (1) { 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; } diff --git a/src/src/transports/smtp.c b/src/src/transports/smtp.c index 02a55f198..264ebc094 100644 --- a/src/src/transports/smtp.c +++ b/src/src/transports/smtp.c @@ -2475,8 +2475,8 @@ goto SEND_QUIT; int n = sizeof(sx->buffer); uschar * rsp = sx->buffer; - if (sx->esmtp_sent && (n = Ustrlen(sx->buffer)) < sizeof(sx->buffer)/2) - { rsp = sx->buffer + n + 1; n = sizeof(sx->buffer) - n; } + if (sx->esmtp_sent && (n = Ustrlen(sx->buffer) + 1) < sizeof(sx->buffer)/2) + { rsp = sx->buffer + n; n = sizeof(sx->buffer) - n; } if (smtp_write_command(sx, SCMD_FLUSH, "HELO %s\r\n", sx->helo_data) < 0) goto SEND_FAILED; diff --git a/src/src/tree.c b/src/src/tree.c index d5a409651..e16a8643c 100644 --- a/src/src/tree.c +++ b/src/src/tree.c @@ -27,7 +27,7 @@ Returns: nothing */ 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 @@ Returns: nothing */ 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 @@ Returns: nothing */ void -tree_add_unusable(host_item *h) +tree_add_unusable(const host_item *h) { rmark rpoint = store_mark(); tree_node *node; diff --git a/src/src/verify.c b/src/src/verify.c index 6ddff47a5..6e07566a7 100644 --- a/src/src/verify.c +++ b/src/src/verify.c @@ -1685,7 +1685,8 @@ if (parse_find_at(address) == NULL) *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) @@ -1700,7 +1701,8 @@ may have been set by domains and local part tests during an ACL. */ 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 --git a/src/util/gen_pkcs3.c b/src/util/gen_pkcs3.c index 6a467e07a..5c4e42993 100644 --- a/src/util/gen_pkcs3.c +++ b/src/util/gen_pkcs3.c @@ -54,7 +54,6 @@ void __attribute__((__noreturn__)) 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 @@ bn_from_text(const char *text) 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 @@ bn_from_text(const char *text) 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; } |