* Exim - an Internet mail transport agent *
*************************************************/
-/* Copyright (c) University of Cambridge 1995 - 2016 */
+/* Copyright (c) University of Cambridge 1995 - 2018 */
/* See the file NOTICE for conditions of use and distribution. */
/* Functions for reading spool files. When compiling for a utility (eximon),
overwriting some other file descriptor with the value of this one), open it
with append.
+As called by deliver_message() (at least) we are operating as root.
+
Argument: the id of the message
Returns: fd if file successfully opened and locked, else -1
uschar * fname;
int save_errno;
- message_subdir[0] = split_spool_directory == i == 0 ? id[5] : 0;
+ message_subdir[0] = split_spool_directory == i ? '\0' : id[5];
fname = spool_fname(US"input", message_subdir, id, US"-D");
DEBUG(D_deliver) debug_printf("Trying spool file %s\n", fname);
- if ((fd = Uopen(fname, O_RDWR | O_APPEND, 0)) >= 0)
+ /* We protect against symlink attacks both in not propagating the
+ * file-descriptor to other processes as we exec, and also ensuring that we
+ * don't even open symlinks.
+ * No -D file inside the spool area should be a symlink.
+ */
+ if ((fd = Uopen(fname,
+#ifdef O_CLOEXEC
+ O_CLOEXEC |
+#endif
+#ifdef O_NOFOLLOW
+ O_NOFOLLOW |
+#endif
+ O_RDWR | O_APPEND, 0)) >= 0)
break;
save_errno = errno;
if (errno == ENOENT)
if (i == 0) continue;
if (!queue_running)
log_write(0, LOG_MAIN, "Spool%s%s file %s-D not found",
- *queue_name ? " Q=" : "",
- *queue_name ? queue_name : "",
+ *queue_name ? US" Q=" : US"",
+ *queue_name ? queue_name : US"",
id);
}
else
Unix systems it doesn't make any difference as long as Exim is consistent in
what it locks. */
-(void)fcntl(fd, F_SETFD, fcntl(fd, F_GETFD) |
- FD_CLOEXEC);
+#ifndef O_CLOEXEC
+(void)fcntl(fd, F_SETFD, fcntl(fd, F_GETFD) | FD_CLOEXEC);
+#endif
lock_data.l_type = F_WRLCK;
lock_data.l_whence = SEEK_SET;
-/*************************************************
-* Read spool header file *
-*************************************************/
-
-/* This function reads a spool header file and places the data into the
-appropriate global variables. The header portion is always read, but header
-structures are built only if read_headers is set true. It isn't, for example,
-while generating -bp output.
-
-It may be possible for blocks of nulls (binary zeroes) to get written on the
-end of a file if there is a system crash during writing. It was observed on an
-earlier version of Exim that omitted to fsync() the files - this is thought to
-have been the cause of that incident, but in any case, this code must be robust
-against such an event, and if such a file is encountered, it must be treated as
-malformed.
-
-Arguments:
- name name of the header file, including the -H
- read_headers TRUE if in-store header structures are to be built
- subdir_set TRUE is message_subdir is already set
-
-Returns: spool_read_OK success
- spool_read_notopen open failed
- spool_read_enverror error in the envelope portion
- spool_read_hdrdrror error in the header portion
-*/
-
-int
-spool_read_header(uschar *name, BOOL read_headers, BOOL subdir_set)
-{
-FILE *f = NULL;
-int n;
-int rcount = 0;
-long int uid, gid;
-BOOL inheader = FALSE;
-uschar *p;
-
/* Reset all the global variables to their default values. However, there is
one exception. DO NOT change the default value of dont_deliver, because it may
be forced by an external setting. */
+void
+spool_clear_header_globals(void)
+{
acl_var_c = acl_var_m = NULL;
authenticated_id = NULL;
authenticated_sender = NULL;
interface_address = NULL;
interface_port = 0;
local_error_message = FALSE;
+#ifdef HAVE_LOCAL_SCAN
local_scan_data = NULL;
+#endif
max_received_linelength = 0;
message_linecount = 0;
received_protocol = NULL;
sender_local = FALSE;
sender_set_untrusted = FALSE;
smtp_active_hostname = primary_hostname;
+#ifndef COMPILE_UTILITY
+spool_file_wireformat = FALSE;
+#endif
tree_nonrecipients = NULL;
#ifdef EXPERIMENTAL_BRIGHTMAIL
#ifndef DISABLE_DKIM
dkim_signers = NULL;
dkim_disable_verify = FALSE;
-dkim_collect_input = FALSE;
+dkim_collect_input = 0;
#endif
#ifdef SUPPORT_TLS
tls_in.certificate_verified = FALSE;
-# ifdef EXPERIMENTAL_DANE
+# ifdef SUPPORT_DANE
tls_in.dane_verified = FALSE;
# endif
tls_in.cipher = NULL;
tls_in.peerdn = NULL;
tls_in.sni = NULL;
tls_in.ocsp = OCSP_NOT_REQ;
+# if defined(EXPERIMENTAL_REQUIRETLS) && !defined(COMPILE_UTILITY)
+tls_requiretls = 0;
+# endif
#endif
#ifdef WITH_CONTENT_SCAN
dsn_ret = 0;
dsn_envid = NULL;
+}
+
+
+/*************************************************
+* Read spool header file *
+*************************************************/
+
+/* This function reads a spool header file and places the data into the
+appropriate global variables. The header portion is always read, but header
+structures are built only if read_headers is set true. It isn't, for example,
+while generating -bp output.
+
+It may be possible for blocks of nulls (binary zeroes) to get written on the
+end of a file if there is a system crash during writing. It was observed on an
+earlier version of Exim that omitted to fsync() the files - this is thought to
+have been the cause of that incident, but in any case, this code must be robust
+against such an event, and if such a file is encountered, it must be treated as
+malformed.
+
+As called from deliver_message() (at least) we are running as root.
+
+Arguments:
+ name name of the header file, including the -H
+ read_headers TRUE if in-store header structures are to be built
+ subdir_set TRUE is message_subdir is already set
+
+Returns: spool_read_OK success
+ spool_read_notopen open failed
+ spool_read_enverror error in the envelope portion
+ spool_read_hdrerror error in the header portion
+*/
+
+int
+spool_read_header(uschar *name, BOOL read_headers, BOOL subdir_set)
+{
+FILE *f = NULL;
+int n;
+int rcount = 0;
+long int uid, gid;
+BOOL inheader = FALSE;
+uschar *p;
+
+/* Reset all the global variables to their default values. However, there is
+one exception. DO NOT change the default value of dont_deliver, because it may
+be forced by an external setting. */
+
+spool_clear_header_globals();
/* Generate the full name and open the file. If message_subdir is already
set, just look in the given directory. Otherwise, look in both the split
/* time */
if (Ufgets(big_buffer, big_buffer_size, f) == NULL) goto SPOOL_READ_ERROR;
-if (sscanf(CS big_buffer, "%d %d", &received_time, &warning_count) != 2)
+if (sscanf(CS big_buffer, TIME_T_FMT " %d", &received_time.tv_sec, &warning_count) != 2)
goto SPOOL_FORMAT_ERROR;
+received_time.tv_usec = 0;
-message_age = time(NULL) - received_time;
+message_age = time(NULL) - received_time.tv_sec;
#ifndef COMPILE_UTILITY
DEBUG(D_deliver) debug_printf("user=%s uid=%ld gid=%ld sender=%s\n",
tree_node *node;
endptr = Ustrchr(big_buffer + 6, ' ');
if (endptr == NULL) goto SPOOL_FORMAT_ERROR;
- name = string_sprintf("%c%.*s", big_buffer[4], endptr - big_buffer - 6,
- big_buffer + 6);
+ name = string_sprintf("%c%.*s", big_buffer[4],
+ (int)(endptr - big_buffer - 6), big_buffer + 6);
if (sscanf(CS endptr, " %d", &count) != 1) goto SPOOL_FORMAT_ERROR;
node = acl_var_create(name);
node->data.ptr = store_get(count + 1);
else if (Ustrncmp(p, "cl ", 3) == 0)
{
- int index, count;
- uschar name[20]; /* Need plenty of space for %d format */
- tree_node *node;
- if ( sscanf(CS big_buffer + 5, "%d %d", &index, &count) != 2
+ unsigned index, count;
+ uschar name[20]; /* Need plenty of space for %u format */
+ tree_node * node;
+ if ( sscanf(CS big_buffer + 5, "%u %u", &index, &count) != 2
|| index >= 20
+ || count > 16384 /* arbitrary limit on variable size */
)
goto SPOOL_FORMAT_ERROR;
if (index < 10)
- (void) string_format(name, sizeof(name), "%c%d", 'c', index);
+ (void) string_format(name, sizeof(name), "%c%u", 'c', index);
else
- (void) string_format(name, sizeof(name), "%c%d", 'm', index - 10);
+ (void) string_format(name, sizeof(name), "%c%u", 'm', index - 10);
node = acl_var_create(name);
node->data.ptr = store_get(count + 1);
+ /* We sanity-checked the count, so disable the Coverity error */
+ /* coverity[tainted_data] */
if (fread(node->data.ptr, 1, count+1, f) < count) goto SPOOL_READ_ERROR;
- ((uschar*)node->data.ptr)[count] = 0;
+ (US node->data.ptr)[count] = '\0';
}
break;
break;
case 'l':
- if (Ustrcmp(p, "ocal") == 0) sender_local = TRUE;
+ if (Ustrcmp(p, "ocal") == 0)
+ sender_local = TRUE;
else if (Ustrcmp(big_buffer, "-localerror") == 0)
local_error_message = TRUE;
+#ifdef HAVE_LOCAL_SCAN
else if (Ustrncmp(p, "ocal_scan ", 10) == 0)
local_scan_data = string_copy(big_buffer + 12);
+#endif
break;
case 'm':
case 'r':
if (Ustrncmp(p, "eceived_protocol", 16) == 0)
received_protocol = string_copy(big_buffer + 19);
+ else if (Ustrncmp(p, "eceived_time_usec", 17) == 0)
+ {
+ unsigned usec;
+ if (sscanf(CS big_buffer + 21, "%u", &usec) == 1)
+ received_time.tv_usec = usec;
+ }
break;
case 's':
else if (Ustrncmp(p, "pam_score_int ", 14) == 0)
spam_score_int = string_copy(big_buffer + 16);
#endif
+#ifndef COMPILE_UTILITY
+ else if (Ustrncmp(p, "pool_file_wireformat", 20) == 0)
+ spool_file_wireformat = TRUE;
+#endif
#if defined(SUPPORT_I18N) && !defined(COMPILE_UTILITY)
else if (Ustrncmp(p, "mtputf8", 7) == 0)
message_smtputf8 = TRUE;
#ifdef SUPPORT_TLS
case 't':
- if (Ustrncmp(p, "ls_certificate_verified", 23) == 0)
- tls_in.certificate_verified = TRUE;
- else if (Ustrncmp(p, "ls_cipher", 9) == 0)
- tls_in.cipher = string_copy(big_buffer + 12);
+ if (Ustrncmp(p, "ls_", 3) == 0)
+ {
+ uschar * q = p + 3;
+ if (Ustrncmp(q, "certificate_verified", 20) == 0)
+ tls_in.certificate_verified = TRUE;
+ else if (Ustrncmp(q, "cipher", 6) == 0)
+ tls_in.cipher = string_copy(big_buffer + 12);
# ifndef COMPILE_UTILITY /* tls support fns not built in */
- else if (Ustrncmp(p, "ls_ourcert", 10) == 0)
- (void) tls_import_cert(big_buffer + 13, &tls_in.ourcert);
- else if (Ustrncmp(p, "ls_peercert", 11) == 0)
- (void) tls_import_cert(big_buffer + 14, &tls_in.peercert);
+ else if (Ustrncmp(q, "ourcert", 7) == 0)
+ (void) tls_import_cert(big_buffer + 13, &tls_in.ourcert);
+ else if (Ustrncmp(q, "peercert", 8) == 0)
+ (void) tls_import_cert(big_buffer + 14, &tls_in.peercert);
+# endif
+ else if (Ustrncmp(q, "peerdn", 6) == 0)
+ tls_in.peerdn = string_unprinting(string_copy(big_buffer + 12));
+ else if (Ustrncmp(q, "sni", 3) == 0)
+ tls_in.sni = string_unprinting(string_copy(big_buffer + 9));
+ else if (Ustrncmp(q, "ocsp", 4) == 0)
+ tls_in.ocsp = big_buffer[10] - '0';
+# if defined(EXPERIMENTAL_REQUIRETLS) && !defined(COMPILE_UTILITY)
+ else if (Ustrncmp(q, "requiretls", 10) == 0)
+ tls_requiretls = strtol(CS big_buffer+16, NULL, 0);
# endif
- else if (Ustrncmp(p, "ls_peerdn", 9) == 0)
- tls_in.peerdn = string_unprinting(string_copy(big_buffer + 12));
- else if (Ustrncmp(p, "ls_sni", 6) == 0)
- tls_in.sni = string_unprinting(string_copy(big_buffer + 9));
- else if (Ustrncmp(p, "ls_ocsp", 7) == 0)
- tls_in.ocsp = big_buffer[10] - '0';
+ }
break;
#endif
#endif /* COMPILE_UTILITY */
/* After reading the tree, the next line has not yet been read into the
-buffer. It contains the count of recipients which follow on separate lines. */
+buffer. It contains the count of recipients which follow on separate lines.
+Apply an arbitrary sanity check.*/
if (Ufgets(big_buffer, big_buffer_size, f) == NULL) goto SPOOL_READ_ERROR;
-if (sscanf(CS big_buffer, "%d", &rcount) != 1) goto SPOOL_FORMAT_ERROR;
+if (sscanf(CS big_buffer, "%d", &rcount) != 1 || rcount > 16384)
+ goto SPOOL_FORMAT_ERROR;
#ifndef COMPILE_UTILITY
DEBUG(D_deliver) debug_printf("recipients_count=%d\n", rcount);
recipients_list_max = rcount;
recipients_list = store_get(rcount * sizeof(recipient_item));
+/* We sanitised the count and know we have enough memory, so disable
+the Coverity error on recipients_count */
+/* coverity[tainted_data] */
+
for (recipients_count = 0; recipients_count < rcount; recipients_count++)
{
int nn;