summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJeremy Harris <jgh@wizmail.org>2017-04-23 12:20:43 +0100
committerJeremy Harris <jgh@wizmail.org>2017-04-23 12:20:43 +0100
commit42055a338593d66f0abb6eeb6b03f0eaf4439f57 (patch)
treed2cf90a67da1d137a8b16c5b888735409788a7bb
parent4e910c01eea401e36044816744691789ef4656fa (diff)
DKIM: avoid use of temporary file for signing
-rw-r--r--doc/doc-txt/ChangeLog5
-rw-r--r--src/OS/Makefile-Base3
-rwxr-xr-xsrc/scripts/MakeLinks3
-rw-r--r--src/src/deliver.c9
-rw-r--r--src/src/dkim.c20
-rw-r--r--src/src/dkim.h2
-rw-r--r--src/src/dkim_transport.c346
-rw-r--r--src/src/functions.h13
-rw-r--r--src/src/macros.h2
-rw-r--r--src/src/queue.c12
-rw-r--r--src/src/smtp_out.c2
-rw-r--r--src/src/string.c2
-rw-r--r--src/src/structs.h8
-rw-r--r--src/src/transport.c378
-rw-r--r--src/src/transports/appendfile.c10
-rw-r--r--src/src/transports/autoreply.c3
-rw-r--r--src/src/transports/lmtp.c3
-rw-r--r--src/src/transports/pipe.c8
-rw-r--r--src/src/transports/smtp.c6
-rw-r--r--src/src/verify.c7
-rw-r--r--test/log/45212
21 files changed, 539 insertions, 305 deletions
diff --git a/doc/doc-txt/ChangeLog b/doc/doc-txt/ChangeLog
index bebc9e70f..1d6ad343b 100644
--- a/doc/doc-txt/ChangeLog
+++ b/doc/doc-txt/ChangeLog
@@ -56,6 +56,11 @@ JH/07 Fix smtp transport use of limited max_rcpt under mua_wrapper. Previously
JH/08 Pipeline CHUNKING command and data together, on kernels that support
MSG_MORE. Only in-clear (not on TLS connections).
+JH/09 Avoid using a temporary file during transport using dkim. Unless a
+ transport-filter is involved we can buffer the headers in memory for
+ creating the signature, and read the spool data file once for the
+ signature and again for transmission.
+
Exim version 4.89
-----------------
diff --git a/src/OS/Makefile-Base b/src/OS/Makefile-Base
index f6b42f353..f3903180b 100644
--- a/src/OS/Makefile-Base
+++ b/src/OS/Makefile-Base
@@ -331,7 +331,7 @@ OBJ_LOOKUPS = lookups/lf_quote.o lookups/lf_check_file.o lookups/lf_sqlperform.o
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 hash.o \
+ filtertest.o globals.o dkim.o dkim_transport.o hash.o \
header.o host.o ip.o log.o lss.o match.o moan.o \
os.o parse.o queue.o \
rda.o readconf.o receive.o retry.o rewrite.o rfc2047.o \
@@ -647,6 +647,7 @@ transport.o: $(HDRS) transport.c
tree.o: $(HDRS) tree.c
verify.o: $(HDRS) transports/smtp.h verify.c
dkim.o: $(HDRS) pdkim/pdkim.h dkim.c
+dkim_transport.o: $(HDRS) dkim_transport.c
# Dependencies for WITH_CONTENT_SCAN modules
diff --git a/src/scripts/MakeLinks b/src/scripts/MakeLinks
index b710c2fd8..d361487cf 100755
--- a/src/scripts/MakeLinks
+++ b/src/scripts/MakeLinks
@@ -107,7 +107,8 @@ for f in blob.h dbfunctions.h dbstuff.h exim.h functions.h globals.h \
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-gnu.c tls-openssl.c \
- tod.c transport.c tree.c verify.c version.c dkim.c dkim.h dmarc.c dmarc.h \
+ tod.c transport.c tree.c verify.c version.c \
+ dkim.c dkim.h dkim_transport.c dmarc.c dmarc.h \
valgrind.h memcheck.h
do
ln -s ../src/$f $f
diff --git a/src/src/deliver.c b/src/src/deliver.c
index ca3b8abfb..2787d0040 100644
--- a/src/src/deliver.c
+++ b/src/src/deliver.c
@@ -7272,8 +7272,9 @@ if (addr_senddsn)
/* Write the original email out */
+ tctx.u.fd = fileno(f);
tctx.options = topt_add_return_path | topt_no_body;
- transport_write_message(fileno(f), &tctx, 0);
+ transport_write_message(&tctx, 0);
fflush(f);
fprintf(f,"\n--%s--\n", bound);
@@ -7732,11 +7733,12 @@ wording. */
transport_ctx tctx = {0};
transport_instance tb = {0};
+ tctx.u.fd = fileno(f);
tctx.tblock = &tb;
tctx.options = topt;
tb.add_headers = dsnnotifyhdr;
- transport_write_message(fileno(f), &tctx, 0);
+ transport_write_message(&tctx, 0);
}
fflush(f);
@@ -8197,12 +8199,13 @@ else if (addr_defer != (address_item *)(+1))
fflush(f);
/* header only as required by RFC. only failure DSN needs to honor RET=FULL */
+ tctx.u.fd = fileno(f);
tctx.options = topt_add_return_path | topt_no_body;
transport_filter_argv = NULL; /* Just in case */
return_path = sender_address; /* In case not previously set */
/* Write the original email out */
- transport_write_message(fileno(f), &tctx, 0);
+ transport_write_message(&tctx, 0);
fflush(f);
fprintf(f,"\n--%s--\n", bound);
diff --git a/src/src/dkim.c b/src/src/dkim.c
index f51021443..f0dfb8af3 100644
--- a/src/src/dkim.c
+++ b/src/src/dkim.c
@@ -448,15 +448,19 @@ switch (what)
}
+/* Generate signatures for the given file, returning a string.
+If a prefix is given, prepend it to the file for the calculations.
+*/
+
uschar *
-dkim_exim_sign(int dkim_fd, struct ob_dkim * dkim, const uschar ** errstr)
+dkim_exim_sign(int fd, off_t off, uschar * prefix,
+ struct ob_dkim * dkim, const uschar ** errstr)
{
const uschar * dkim_domain;
int sep = 0;
uschar *seen_items = NULL;
int seen_items_size = 0;
int seen_items_offset = 0;
-uschar itembuf[256];
uschar *dkim_canon_expanded;
uschar *dkim_sign_headers_expanded;
uschar *dkim_private_key_expanded;
@@ -485,10 +489,9 @@ if (!(dkim_domain = expand_cstring(dkim->dkim_domain)))
/* Set $dkim_domain expansion variable to each unique domain in list. */
-while ((dkim_signing_domain = string_nextinlist(&dkim_domain, &sep,
- itembuf, sizeof(itembuf))))
+while ((dkim_signing_domain = string_nextinlist(&dkim_domain, &sep, NULL, 0)))
{
- if (!dkim_signing_domain || dkim_signing_domain[0] == '\0')
+ if (dkim_signing_domain[0] == '\0')
continue;
/* Only sign once for each domain, no matter how often it
@@ -619,9 +622,12 @@ while ((dkim_signing_domain = string_nextinlist(&dkim_domain, &sep,
pdkim_canon,
pdkim_canon, -1, 0, 0);
- lseek(dkim_fd, 0, SEEK_SET);
+ if (prefix)
+ pdkim_feed(ctx, prefix, Ustrlen(prefix));
+
+ lseek(fd, off, SEEK_SET);
- while ((sread = read(dkim_fd, &buf, sizeof(buf))) > 0)
+ while ((sread = read(fd, &buf, sizeof(buf))) > 0)
if ((pdkim_rc = pdkim_feed(ctx, buf, sread)) != PDKIM_OK)
goto pk_bad;
diff --git a/src/src/dkim.h b/src/src/dkim.h
index bfdc7d42b..83c68a76c 100644
--- a/src/src/dkim.h
+++ b/src/src/dkim.h
@@ -6,7 +6,7 @@
/* See the file NOTICE for conditions of use and distribution. */
void dkim_exim_init(void);
-uschar *dkim_exim_sign(int, struct ob_dkim *, const uschar **);
+uschar *dkim_exim_sign(int, off_t, uschar *, struct ob_dkim *, const uschar **);
void dkim_exim_verify_init(BOOL);
void dkim_exim_verify_feed(uschar *, int);
void dkim_exim_verify_finish(void);
diff --git a/src/src/dkim_transport.c b/src/src/dkim_transport.c
new file mode 100644
index 000000000..c8ac92e16
--- /dev/null
+++ b/src/src/dkim_transport.c
@@ -0,0 +1,346 @@
+/*************************************************
+* Exim - an Internet mail transport agent *
+*************************************************/
+
+/* Copyright (c) University of Cambridge 1995 - 2016 */
+/* See the file NOTICE for conditions of use and distribution. */
+
+/* Transport shim for dkim signing */
+
+#ifndef DISABLE_DKIM
+
+
+#include "exim.h"
+
+#ifdef HAVE_LINUX_SENDFILE
+#include <sys/sendfile.h>
+#endif
+
+
+static BOOL
+dkt_sign_fail(struct ob_dkim * dkim, int * errp)
+{
+if (dkim->dkim_strict)
+ {
+ uschar * dkim_strict_result = expand_string(dkim->dkim_strict);
+
+ if (dkim_strict_result)
+ if ( (strcmpic(dkim->dkim_strict, US"1") == 0) ||
+ (strcmpic(dkim->dkim_strict, US"true") == 0) )
+ {
+ /* Set errno to something halfway meaningful */
+ *errp = EACCES;
+ log_write(0, LOG_MAIN, "DKIM: message could not be signed,"
+ " and dkim_strict is set. Deferring message delivery.");
+ return FALSE;
+ }
+ }
+return TRUE;
+}
+
+static BOOL
+dkt_send_file(int out_fd, int in_fd, off_t off, size_t size)
+{
+DEBUG(D_transport) debug_printf("send file fd=%d size=%d\n", out_fd, size - off);
+
+/*XXX should implement timeout, like transport_write_block_fd() ? */
+
+/* Rewind file */
+lseek(in_fd, off, SEEK_SET);
+
+#ifdef HAVE_LINUX_SENDFILE
+/* We can use sendfile() to shove the file contents
+ to the socket. However only if we don't use TLS,
+ as then there's another layer of indirection
+ before the data finally hits the socket. */
+if (tls_out.active != out_fd)
+ {
+ ssize_t copied = 0;
+
+ while(copied >= 0 && off < size)
+ copied = sendfile(tctx->u.fd, dkim_fd, &off, size - off);
+ if (copied < 0)
+ return FALSE;
+ }
+else
+
+#endif
+
+ {
+ int sread, wwritten;
+
+ /* Send file down the original fd */
+ while((sread = read(in_fd, deliver_out_buffer, DELIVER_OUT_BUFFER_SIZE)) >0)
+ {
+ uschar * p = deliver_out_buffer;
+ /* write the chunk */
+
+ while (sread)
+ {
+#ifdef SUPPORT_TLS
+ wwritten = tls_out.active == out_fd
+ ? tls_write(FALSE, p, sread)
+ : write(out_fd, CS p, sread);
+#else
+ wwritten = write(out_fd, CS p, sread);
+#endif
+ if (wwritten == -1)
+ return FALSE;
+ p += wwritten;
+ sread -= wwritten;
+ }
+ }
+
+ if (sread == -1)
+ return FALSE;
+ }
+
+return TRUE;
+}
+
+
+
+
+/* This function is a wrapper around transport_write_message().
+ It is only called from the smtp transport if DKIM or Domainkeys support
+ is active and no transport filter is to be used.
+
+Arguments:
+ As for transport_write_message() in transort.c, with additional arguments
+ for DKIM.
+
+Returns: TRUE on success; FALSE (with errno) for any failure
+*/
+
+static BOOL
+dkt_direct(transport_ctx * tctx, struct ob_dkim * dkim,
+ const uschar ** err)
+{
+int save_fd = tctx->u.fd;
+int save_options = tctx->options;
+uschar * hdrs, * dkim_signature;
+int siglen, hsize;
+const uschar * errstr;
+BOOL rc;
+
+DEBUG(D_transport) debug_printf("dkim signing direct-mode\n");
+
+/* Get headers in string for signing and transmission */
+
+tctx->u.msg = NULL;
+tctx->options = tctx->options & ~(topt_end_dot | topt_use_bdat)
+ | topt_output_string | topt_no_body;
+
+rc = transport_write_message(tctx, 0);
+hdrs = tctx->u.msg;
+hdrs[hsize = tctx->msg_ptr] = '\0';
+
+tctx->u.fd = save_fd;
+tctx->options = save_options;
+if (!rc) return FALSE;
+
+/* Get signatures for headers plus spool data file */
+
+dkim->dot_stuffed = !!(save_options & topt_end_dot);
+
+if ((dkim_signature = dkim_exim_sign(deliver_datafile, SPOOL_DATA_START_OFFSET,
+ hdrs, dkim, &errstr)))
+ siglen = Ustrlen(dkim_signature);
+else if (!(rc = dkt_sign_fail(dkim, &errno)))
+ {
+ *err = errstr;
+ return FALSE;
+ }
+
+/* Write the signature and headers into the deliver-out-buffer. This should
+mean they go out in the same packet as the MAIL, RCPT and (first) BDAT commands
+(transport_write_message() sizes the BDAT for the buffered amount) - for short
+messages, the BDAT LAST command. We want no CRLF or dotstuffing expansion */
+
+tctx->options &= ~topt_use_crlf;
+transport_write_reset(0);
+if ( !write_chunk(tctx, dkim_signature, siglen)
+ || !write_chunk(tctx, hdrs, hsize))
+ return FALSE;
+
+tctx->options = save_options | topt_no_headers | topt_continuation;
+
+if (!(transport_write_message(tctx, 0)))
+ return FALSE;
+
+tctx->options = save_options;
+return TRUE;
+}
+
+
+/* This function is a wrapper around transport_write_message().
+ It is only called from the smtp transport if DKIM or Domainkeys support
+ is active and a transport filter is to be used. The function sets up a
+ replacement fd into a -K file, then calls the normal function. This way, the
+ exact bits that exim would have put "on the wire" will end up in the file
+ (except for TLS encapsulation, which is the very very last thing). When we
+ are done signing the file, send the signed message down the original fd (or
+ TLS fd).
+
+Arguments:
+ As for transport_write_message() in transort.c, with additional arguments
+ for DKIM.
+
+Returns: TRUE on success; FALSE (with errno) for any failure
+*/
+
+static BOOL
+dkt_via_kfile(transport_ctx * tctx, struct ob_dkim * dkim, const uschar ** err)
+{
+int dkim_fd;
+int save_errno = 0;
+BOOL rc;
+uschar * dkim_spool_name, * dkim_signature;
+int sread = 0, wwritten = 0, siglen, options;
+off_t k_file_size;
+const uschar * errstr;
+
+dkim_spool_name = spool_fname(US"input", message_subdir, message_id,
+ string_sprintf("-%d-K", (int)getpid()));
+
+DEBUG(D_transport) debug_printf("dkim signing via file %s\n", dkim_spool_name);
+
+if ((dkim_fd = Uopen(dkim_spool_name, O_RDWR|O_CREAT|O_TRUNC, SPOOL_MODE)) < 0)
+ {
+ /* Can't create spool file. Ugh. */
+ rc = FALSE;
+ save_errno = errno;
+ *err = string_sprintf("dkim spoolfile create: %s", strerror(errno));
+ goto CLEANUP;
+ }
+
+/* Call transport utility function to write the -K file; does the CRLF expansion
+(but, in the CHUNKING case, neither dot-stuffing nor dot-termination). */
+
+ {
+ int save_fd = tctx->u.fd;
+ tctx->u.fd = dkim_fd;
+ options = tctx->options;
+ tctx->options &= ~topt_use_bdat;
+
+ rc = transport_write_message(tctx, 0);
+
+ tctx->u.fd = save_fd;
+ tctx->options = options;
+ }
+
+/* Save error state. We must clean up before returning. */
+if (!rc)
+ {
+ save_errno = errno;
+ goto CLEANUP;
+ }
+
+/* Feed the file to the goats^W DKIM lib */
+
+dkim->dot_stuffed = !!(options & topt_end_dot);
+if ((dkim_signature = dkim_exim_sign(dkim_fd, 0, NULL, dkim, &errstr)))
+ siglen = Ustrlen(dkim_signature);
+else if (!(rc = dkt_sign_fail(dkim, &save_errno)))
+ {
+ *err = errstr;
+ goto CLEANUP;
+ }
+
+#ifndef HAVE_LINUX_SENDFILE
+if (options & topt_use_bdat)
+#endif
+ k_file_size = lseek(dkim_fd, 0, SEEK_END); /* Fetch file size */
+
+if (options & topt_use_bdat)
+ {
+ /* On big messages output a precursor chunk to get any pipelined
+ MAIL & RCPT commands flushed, then reap the responses so we can
+ error out on RCPT rejects before sending megabytes. */
+
+ if (siglen + k_file_size > DELIVER_OUT_BUFFER_SIZE && siglen > 0)
+ {
+ if ( tctx->chunk_cb(tctx, siglen, 0) != OK
+ || !transport_write_block(tctx, dkim_signature, siglen, FALSE)
+ || tctx->chunk_cb(tctx, 0, tc_reap_prev) != OK
+ )
+ goto err;
+ siglen = 0;
+ }
+
+ /* Send the BDAT command for the entire message, as a single LAST-marked
+ chunk. */
+
+ if (tctx->chunk_cb(tctx, siglen + k_file_size, tc_chunk_last) != OK)
+ goto err;
+ }
+
+if(siglen > 0 && !transport_write_block(tctx, dkim_signature, siglen, TRUE))
+ goto err;
+
+if (!dkt_send_file(tctx->u.fd, dkim_fd, 0, k_file_size))
+ {
+ save_errno = errno;
+ rc = FALSE;
+ }
+
+CLEANUP:
+ /* unlink -K file */
+ (void)close(dkim_fd);
+ Uunlink(dkim_spool_name);
+ errno = save_errno;
+ return rc;
+
+err:
+ save_errno = errno;
+ rc = FALSE;
+ goto CLEANUP;
+}
+
+
+
+/***************************************************************************************************
+* External interface to write the message, while signing it with DKIM and/or Domainkeys *
+***************************************************************************************************/
+
+/* This function is a wrapper around transport_write_message().
+ It is only called from the smtp transport if DKIM or Domainkeys support
+ is compiled in.
+
+Arguments:
+ As for transport_write_message() in transort.c, with additional arguments
+ for DKIM.
+
+Returns: TRUE on success; FALSE (with errno) for any failure
+*/
+
+BOOL
+dkim_transport_write_message(transport_ctx * tctx,
+ struct ob_dkim * dkim, const uschar ** err)
+{
+/* If we can't sign, just call the original function. */
+
+if (!(dkim->dkim_private_key && dkim->dkim_domain && dkim->dkim_selector))
+ return transport_write_message(tctx, 0);
+
+/* If there is no filter command set up, construct the message and calculate
+a dkim signature of it, send the signature and a reconstructed message. This
+avoids using a temprary file. */
+
+if ( !transport_filter_argv
+ || !*transport_filter_argv
+ || !**transport_filter_argv
+ )
+ return dkt_direct(tctx, dkim, err);
+
+/* Use the transport path to write a file, calculate a dkim signature,
+send the signature and then send the file. */
+
+return dkt_via_kfile(tctx, dkim, err);
+}
+
+#endif /* whole file */
+
+/* vi: aw ai sw=2
+*/
+/* End of dkim_transport.c */
diff --git a/src/src/functions.h b/src/src/functions.h
index c3c96b69c..ee17e9c27 100644
--- a/src/src/functions.h
+++ b/src/src/functions.h
@@ -154,7 +154,7 @@ extern void delivery_re_exec(int);
extern BOOL directory_make(const uschar *, const uschar *, int, BOOL);
#ifndef DISABLE_DKIM
-extern BOOL dkim_transport_write_message(int, transport_ctx *,
+extern BOOL dkim_transport_write_message(transport_ctx *,
struct ob_dkim *, const uschar ** errstr);
#endif
extern dns_address *dns_address_from_rr(dns_answer *, dns_record *);
@@ -231,6 +231,7 @@ extern uschar *imap_utf7_encode(uschar *, const uschar *,
uschar, uschar *, uschar **);
extern void invert_address(uschar *, uschar *);
+extern BOOL internal_transport_write_message(transport_ctx *, int);
extern int ip_addr(void *, int, const uschar *, int);
extern int ip_bind(int, int, uschar *, int);
extern int ip_connect(int, int, const uschar *, int, int, BOOL);
@@ -484,11 +485,12 @@ extern uschar *transport_rcpt_address(address_item *, BOOL);
extern BOOL transport_set_up_command(const uschar ***, uschar *,
BOOL, int, address_item *, uschar *, uschar **);
extern void transport_update_waiting(host_item *, uschar *);
-extern BOOL transport_write_block(int, uschar *, int);
+extern BOOL transport_write_block(transport_ctx *, uschar *, int, BOOL);
+extern void transport_write_reset(int);
extern BOOL transport_write_string(int, const char *, ...);
-extern BOOL transport_headers_send(int, transport_ctx *,
- BOOL (*)(int, transport_ctx *, uschar *, int));
-extern BOOL transport_write_message(int, transport_ctx *, int);
+extern BOOL transport_headers_send(transport_ctx *,
+ BOOL (*)(transport_ctx *, uschar *, int));
+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 *);
@@ -522,6 +524,7 @@ extern BOOL verify_sender(int *, uschar **);
extern BOOL verify_sender_preliminary(int *, uschar **);
extern void version_init(void);
+extern BOOL write_chunk(transport_ctx *, uschar *, int);
extern ssize_t write_to_fd_buf(int, const uschar *, size_t);
/* vi: aw
diff --git a/src/src/macros.h b/src/src/macros.h
index 0c1425f80..8b608f7f8 100644
--- a/src/src/macros.h
+++ b/src/src/macros.h
@@ -854,6 +854,8 @@ enum {
#define topt_no_body 0x040 /* Omit body */
#define topt_escape_headers 0x080 /* Apply escape check to headers */
#define topt_use_bdat 0x100 /* prepend chunks with RFC3030 BDAT header */
+#define topt_output_string 0x200 /* create string rather than write to fd */
+#define topt_continuation 0x400 /* do not reset buffer */
/* Options for smtp_write_command */
diff --git a/src/src/queue.c b/src/src/queue.c
index 50e4aaef3..7b8f727bc 100644
--- a/src/src/queue.c
+++ b/src/src/queue.c
@@ -1142,10 +1142,14 @@ if (action != MSG_SHOW_COPY) printf("Message %s ", id);
switch(action)
{
case MSG_SHOW_COPY:
- deliver_in_buffer = store_malloc(DELIVER_IN_BUFFER_SIZE);
- deliver_out_buffer = store_malloc(DELIVER_OUT_BUFFER_SIZE);
- transport_write_message(1, NULL, 0);
- break;
+ {
+ transport_ctx tctx = {0};
+ deliver_in_buffer = store_malloc(DELIVER_IN_BUFFER_SIZE);
+ deliver_out_buffer = store_malloc(DELIVER_OUT_BUFFER_SIZE);
+ tctx.u.fd = 1;
+ transport_write_message(&tctx, 0);
+ break;
+ }
case MSG_FREEZE:
diff --git a/src/src/smtp_out.c b/src/src/smtp_out.c
index 7ade9ba67..e10543b08 100644
--- a/src/src/smtp_out.c
+++ b/src/src/smtp_out.c
@@ -331,7 +331,7 @@ int rc;
int n = outblock->ptr - outblock->buffer;
HDEBUG(D_transport|D_acl) debug_printf_indent("cmd buf flush %d bytes%s\n", n,
- mode == SCMD_MORE ? " (with MORE annotation)" : "");
+ mode == SCMD_MORE ? " (more expected)" : "");
#ifdef SUPPORT_TLS
if (tls_out.active == outblock->sock)
diff --git a/src/src/string.c b/src/src/string.c
index 4850fd958..0d5a09703 100644
--- a/src/src/string.c
+++ b/src/src/string.c
@@ -1081,7 +1081,7 @@ Arguments:
characters, updated to the new offset
s points to characters to add
count count of characters to add; must not exceed the length of s, if s
- is a C string. If -1 given, strlen(s) is used.
+ is a C string.
If string is given as NULL, *size and *ptr should both be zero.
diff --git a/src/src/structs.h b/src/src/structs.h
index 60e7ccd9d..474b85577 100644
--- a/src/src/structs.h
+++ b/src/src/structs.h
@@ -235,6 +235,10 @@ typedef int (*tpt_chunk_cmd_cb)(struct transport_context *, unsigned, unsigned);
/* Structure for information about a delivery-in-progress */
typedef struct transport_context {
+ union { /* discriminated by option topt_output_string */
+ int fd; /* file descriptor to write message to */
+ uschar * msg; /* allocated string with written message */
+ } u;
transport_instance * tblock; /* transport */
struct address_item * addr;
uschar * check_string; /* string replacement */
@@ -244,6 +248,10 @@ typedef struct transport_context {
/* items below only used with option topt_use_bdat */
tpt_chunk_cmd_cb chunk_cb; /* per-datachunk callback */
void * smtp_context;
+
+ /* items below only used with option topt_output_string */
+ int msg_size;
+ int msg_ptr;
} transport_ctx;
diff --git a/src/src/transport.c b/src/src/transport.c
index 594e02cde..0f20efe1b 100644
--- a/src/src/transport.c
+++ b/src/src/transport.c
@@ -11,10 +11,6 @@ transports. */
#include "exim.h"
-#ifdef HAVE_LINUX_SENDFILE
-#include <sys/sendfile.h>
-#endif
-
/* Structure for keeping list of addresses that have been added to
Envelope-To:, in order to avoid duplication. */
@@ -204,7 +200,7 @@ evermore, so stick a maximum repetition count on the loop to act as a
longstop.
Arguments:
- fd file descriptor to write to
+ tctx transport context: file descriptor or string to write to
block block of bytes to write
len number of bytes to write
@@ -212,11 +208,12 @@ Returns: TRUE on success, FALSE on failure (with errno preserved);
transport_count is incremented by the number of bytes written
*/
-BOOL
-transport_write_block(int fd, uschar *block, int len)
+static BOOL
+transport_write_block_fd(transport_ctx * tctx, uschar *block, int len, BOOL more)
{
int i, rc, save_errno;
int local_timeout = transport_write_timeout;
+int fd = tctx->u.fd;
/* This loop is for handling incomplete writes and other retries. In most
normal cases, it is only ever executed once. */
@@ -224,8 +221,8 @@ normal cases, it is only ever executed once. */
for (i = 0; i < 100; i++)
{
DEBUG(D_transport)
- debug_printf("writing data block fd=%d size=%d timeout=%d\n",
- fd, len, local_timeout);
+ debug_printf("writing data block fd=%d size=%d timeout=%d%s\n",
+ fd, len, local_timeout, more ? " (more expected)" : "");
/* This code makes use of alarm() in order to implement the timeout. This
isn't a very tidy way of doing things. Using non-blocking I/O with select()
@@ -234,10 +231,14 @@ for (i = 0; i < 100; i++)
if (transport_write_timeout <= 0) /* No timeout wanted */
{
- #ifdef SUPPORT_TLS
- if (tls_out.active == fd) rc = tls_write(FALSE, block, len); else
- #endif
- rc = write(fd, block, len);
+ rc =
+#ifdef SUPPORT_TLS
+ (tls_out.active == fd) ? tls_write(FALSE, block, len) :
+#endif
+#ifdef MSG_MORE
+ more ? send(fd, block, len, MSG_MORE) :
+#endif
+ write(fd, block, len);
save_errno = errno;
}
@@ -246,12 +247,16 @@ for (i = 0; i < 100; i++)
else
{
alarm(local_timeout);
+
+ rc =
#ifdef SUPPORT_TLS
- if (tls_out.active == fd)
- rc = tls_write(FALSE, block, len);
- else
+ (tls_out.active == fd) ? tls_write(FALSE, block, len) :
+#endif
+#ifdef MSG_MORE
+ more ? send(fd, block, len, MSG_MORE) :
#endif
- rc = write(fd, block, len);
+ write(fd, block, len);
+
save_errno = errno;
local_timeout = alarm(0);
if (sigalrm_seen)
@@ -323,6 +328,25 @@ return FALSE;
}
+BOOL
+transport_write_block(transport_ctx * tctx, uschar *block, int len, BOOL more)
+{
+if (!(tctx->options & topt_output_string))
+ return transport_write_block_fd(tctx, block, len, more);
+
+/* Write to expanding-string. NOTE: not NUL-terminated */
+
+if (!tctx->u.msg)
+ {
+ tctx->u.msg = store_get(tctx->msg_size = 1024);
+ tctx->msg_ptr = 0;
+ }
+
+tctx->u.msg = string_catn(tctx->u.msg, &tctx->msg_size, &tctx->msg_ptr, block, len);
+return TRUE;
+}
+
+
/*************************************************
@@ -342,17 +366,29 @@ Returns: the yield of transport_write_block()
BOOL
transport_write_string(int fd, const char *format, ...)
{
+transport_ctx tctx = {0};
va_list ap;
va_start(ap, format);
if (!string_vformat(big_buffer, big_buffer_size, format, ap))
log_write(0, LOG_MAIN|LOG_PANIC_DIE, "overlong formatted string in transport");
va_end(ap);
-return transport_write_block(fd, big_buffer, Ustrlen(big_buffer));
+tctx.u.fd = fd;
+return transport_write_block(&tctx, big_buffer, Ustrlen(big_buffer), FALSE);
}
+void
+transport_write_reset(int options)
+{
+if (!(options & topt_continuation)) chunk_ptr = deliver_out_buffer;
+nl_partial_match = -1;
+nl_check_length = nl_escape_length = 0;
+}
+
+
+
/*************************************************
* Write character chunk *
*************************************************/
@@ -366,18 +402,18 @@ Static data is used to handle the case when the last character of the previous
chunk was NL, or matched part of the data that has to be escaped.
Arguments:
- fd file descript to write to
+ tctx transport context - processing to be done during output,
+ and file descriptor to write to
chunk pointer to data to write
len length of data to write
- tctx transport context - processing to be done during output
In addition, the static nl_xxx variables must be set as required.
Returns: TRUE on success, FALSE on failure (with errno preserved)
*/
-static BOOL
-write_chunk(int fd, transport_ctx * tctx, uschar *chunk, int len)
+BOOL
+write_chunk(transport_ctx * tctx, uschar *chunk, int len)
{
uschar *start = chunk;
uschar *end = chunk + len;
@@ -436,13 +472,13 @@ for (ptr = start; ptr < end; ptr++)
if (tctx && tctx->options & topt_use_bdat && tctx->chunk_cb)
{
if ( tctx->chunk_cb(tctx, (unsigned)len, 0) != OK
- || !transport_write_block(fd, deliver_out_buffer, len)
+ || !transport_write_block(tctx, deliver_out_buffer, len, FALSE)
|| tctx->chunk_cb(tctx, 0, tc_reap_prev) != OK
)
return FALSE;
}
else
- if (!transport_write_block(fd, deliver_out_buffer, len))
+ if (!transport_write_block(tctx, deliver_out_buffer, len, FALSE))
return FALSE;
chunk_ptr = deliver_out_buffer;
}
@@ -569,15 +605,15 @@ Arguments:
pplist address of anchor of the list of addresses not to output
pdlist address of anchor of the list of processed addresses
first TRUE if this is the first address; set it FALSE afterwards
- fd the file descriptor to write to
tctx transport context - processing to be done during output
+ and the file descriptor to write to
Returns: FALSE if writing failed
*/
static BOOL
write_env_to(address_item *p, struct aci **pplist, struct aci **pdlist,
- BOOL *first, int fd, transport_ctx * tctx)
+ BOOL *first, transport_ctx * tctx)
{
address_item *pp;
struct aci *ppp;
@@ -599,7 +635,7 @@ for (pp = p;; pp = pp->parent)
address_item *dup;
for (dup = addr_duplicate; dup; dup = dup->next)
if (dup->dupof == pp) /* a dup of our address */
- if (!write_env_to(dup, pplist, pdlist, first, fd, tctx))
+ if (!write_env_to(dup, pplist, pdlist, first, tctx))
return FALSE;
if (!pp->parent) break;
}
@@ -616,9 +652,9 @@ ppp->next = *pplist;
*pplist = ppp;
ppp->ptr = pp;
-if (!*first && !write_chunk(fd, tctx, US",\n ", 3)) return FALSE;
+if (!*first && !write_chunk(tctx, US",\n ", 3)) return FALSE;
*first = FALSE;
-return write_chunk(fd, tctx, pp->address, Ustrlen(pp->address));
+return write_chunk(tctx, pp->address, Ustrlen(pp->address));
}
@@ -632,15 +668,14 @@ Globals:
Arguments:
addr (chain of) addresses (for extra headers), or NULL;
only the first address is used
- fd file descriptor to write the message to
tctx transport context
sendfn function for output (transport or verify)
Returns: TRUE on success; FALSE on failure.
*/
BOOL
-transport_headers_send(int fd, transport_ctx * tctx,
- BOOL (*sendfn)(int fd, transport_ctx * tctx, uschar * s, int len))
+transport_headers_send(transport_ctx * tctx,
+ BOOL (*sendfn)(transport_ctx * tctx, uschar * s, int len))
{
header_line *h;
const uschar *list;
@@ -700,7 +735,7 @@ for (h = header_list; h; h = h->next) if (h->type != htype_old)
if ((hh = rewrite_header(h, NULL, NULL, tblock->rewrite_rules,
tblock->rewrite_existflags, FALSE)))
{
- if (!sendfn(fd, tctx, hh->text, hh->slen)) return FALSE;
+ if (!sendfn(tctx, hh->text, hh->slen)) return FALSE;
store_reset(reset_point);
continue; /* With the next header line */
}
@@ -708,7 +743,7 @@ for (h = header_list; h; h = h->next) if (h->type != htype_old)
/* Either no rewriting rules, or it didn't get rewritten */
- if (!sendfn(fd, tctx, h->text, h->slen)) return FALSE;
+ if (!sendfn(tctx, h->text, h->slen)) return FALSE;
}
/* Header removed */
@@ -743,7 +778,7 @@ if (addr)
hprev = h;
if (i == 1)
{
- if (!sendfn(fd, tctx, h->text, h->slen)) return FALSE;
+ if (!sendfn(tctx, h->text, h->slen)) return FALSE;
DEBUG(D_transport)
debug_printf("added header line(s):\n%s---\n", h->text);
}
@@ -768,8 +803,8 @@ if (tblock && (list = CUS tblock->add_headers))
int len = Ustrlen(s);
if (len > 0)
{
- if (!sendfn(fd, tctx, s, len)) return FALSE;
- if (s[len-1] != '\n' && !sendfn(fd, tctx, US"\n", 1))
+ if (!sendfn(tctx, s, len)) return FALSE;
+ if (s[len-1] != '\n' && !sendfn(tctx, US"\n", 1))
return FALSE;
DEBUG(D_transport)
{
@@ -785,7 +820,7 @@ if (tblock && (list = CUS tblock->add_headers))
/* Separate headers from body with a blank line */
-return sendfn(fd, tctx, US"\n", 1);
+return sendfn(tctx, US"\n", 1);
}
@@ -818,8 +853,10 @@ can include timeouts for certain transports, which are requested by setting
transport_write_timeout non-zero.
Arguments:
- fd file descriptor to write the message to
tctx
+ (fd, msg) Either and fd, to write the message to,
+ or a string: if null write message to allocated space
+ otherwire take content as headers.
addr (chain of) addresses (for extra headers), or NULL;
only the first address is used
tblock optional transport instance block (NULL signifies NULL/0):
@@ -850,18 +887,17 @@ Returns: TRUE on success; FALSE (with errno) on failure.
is incremented by the number of bytes written.
*/
-static BOOL
-internal_transport_write_message(int fd, transport_ctx * tctx, int size_limit)
+BOOL
+internal_transport_write_message(transport_ctx * tctx, int size_limit)
{
int len;
/* Initialize pointer in output buffer. */
-chunk_ptr = deliver_out_buffer;
+transport_write_reset(tctx->options);
/* Set up the data for start-of-line data checking and escaping */
-nl_partial_match = -1;
if (tctx->check_string && tctx->escape_string)
{
nl_check = tctx->check_string;
@@ -869,21 +905,19 @@ if (tctx->check_string && tctx->escape_string)
nl_escape = tctx->escape_string;
nl_escape_length = Ustrlen(nl_escape);
}
-else
- nl_check_length = nl_escape_length = 0;
-
-/* Whether the escaping mechanism is applied to headers or not is controlled by
-an option (set for SMTP, not otherwise). Negate the length if not wanted till
-after the headers. */
-
-if (!(tctx->options & topt_escape_headers))
- nl_check_length = -nl_check_length;
/* Write the headers if required, including any that have to be added. If there
are header rewriting rules, apply them. */
if (!(tctx->options & topt_no_headers))
{
+ /* Whether the escaping mechanism is applied to headers or not is controlled by
+ an option (set for SMTP, not otherwise). Negate the length if not wanted till
+ after the headers. */
+
+ if (!(tctx->options & topt_escape_headers))
+ nl_check_length = -nl_check_length;
+
/* Add return-path: if requested. */
if (tctx->options & topt_add_return_path)
@@ -891,7 +925,7 @@ if (!(tctx->options & topt_no_headers))
uschar buffer[ADDRESS_MAXLENGTH + 20];
int n = sprintf(CS buffer, "Return-path: <%.*s>\n", ADDRESS_MAXLENGTH,
return_path);
- if (!write_chunk(fd, tctx, buffer, n)) return FALSE;
+ if (!write_chunk(tctx, buffer, n)) return FALSE;
}
/* Add envelope-to: if requested */
@@ -904,19 +938,19 @@ if (!(tctx->options & topt_no_headers))
struct aci *dlist = NULL;
void *reset_point = store_get(0);
- if (!write_chunk(fd, tctx, US"Envelope-to: ", 13)) return FALSE;
+ if (!write_chunk(tctx, US"Envelope-to: ", 13)) return FALSE;
/* Pick up from all the addresses. The plist and dlist variables are
anchors for lists of addresses already handled; they have to be defined at
this level because write_env_to() calls itself recursively. */
for (p = tctx->addr; p; p = p->next)
- if (!write_env_to(p, &plist, &dlist, &first, fd, tctx))
+ if (!write_env_to(p, &plist, &dlist, &first, tctx))
return FALSE;
/* Add a final newline and reset the store used for tracking duplicates */
- if (!write_chunk(fd, tctx, US"\n", 1)) return FALSE;
+ if (!write_chunk(tctx, US"\n", 1)) return FALSE;
store_reset(reset_point);
}
@@ -926,7 +960,7 @@ if (!(tctx->options & topt_no_headers))
{
uschar buffer[100];
int n = sprintf(CS buffer, "Delivery-date: %s\n", tod_stamp(tod_full));
- if (!write_chunk(fd, tctx, buffer, n)) return FALSE;
+ if (!write_chunk(tctx, buffer, n)) return FALSE;
}
/* Then the message's headers. Don't write any that are flagged as "old";
@@ -935,7 +969,7 @@ if (!(tctx->options & topt_no_headers))
match any entries therein. Then check addr->prop.remove_headers too, provided that
addr is not NULL. */
- if (!transport_headers_send(fd, tctx, &write_chunk))
+ if (!transport_headers_send(tctx, &write_chunk))
return FALSE;
}
@@ -967,6 +1001,9 @@ if (tctx->options & topt_use_bdat)
size = hsize + fsize;
if (tctx->options & topt_use_crlf)
size += body_linecount; /* account for CRLF-expansion */
+
+ /* With topt_use_bdat we never do dot-stuffing; no need to
+ account for any expansion due to that. */
}
/* If the message is large, emit first a non-LAST chunk with just the
@@ -980,7 +1017,7 @@ if (tctx->options & topt_use_bdat)
DEBUG(D_transport)
debug_printf("sending small initial BDAT; hsize=%d\n", hsize);
if ( tctx->chunk_cb(tctx, hsize, 0) != OK
- || !transport_write_block(fd, deliver_out_buffer, hsize)
+ || !transport_write_block(tctx, deliver_out_buffer, hsize, FALSE)
|| tctx->chunk_cb(tctx, 0, tc_reap_prev) != OK
)
return FALSE;
@@ -1013,7 +1050,7 @@ if (!(tctx->options & topt_no_body))
while ( (len = MAX(DELIVER_IN_BUFFER_SIZE, size)) > 0
&& (len = read(deliver_datafile, deliver_in_buffer, len)) > 0)
{
- if (!write_chunk(fd, tctx, deliver_in_buffer, len))
+ if (!write_chunk(tctx, deliver_in_buffer, len))
return FALSE;
size -= len;
}
@@ -1029,206 +1066,15 @@ nl_check_length = nl_escape_length = 0;
/* If requested, add a terminating "." line (SMTP output). */
-if (tctx->options & topt_end_dot && !write_chunk(fd, tctx, US".\n", 2))
+if (tctx->options & topt_end_dot && !write_chunk(tctx, US".\n", 2))
return FALSE;
/* Write out any remaining data in the buffer before returning. */
return (len = chunk_ptr - deliver_out_buffer) <= 0 ||
- transport_write_block(fd, deliver_out_buffer, len);
-}
-
-
-#ifndef DISABLE_DKIM
-
-/***************************************************************************************************
-* External interface to write the message, while signing it with DKIM and/or Domainkeys *
-***************************************************************************************************/
-
-/* This function is a wrapper around transport_write_message().
- It is only called from the smtp transport if DKIM or Domainkeys support
- is compiled in. The function sets up a replacement fd into a -K file,
- then calls the normal function. This way, the exact bits that exim would
- have put "on the wire" will end up in the file (except for TLS
- encapsulation, which is the very very last thing). When we are done
- signing the file, send the signed message down the original fd (or TLS fd).
-
-Arguments:
- as for internal_transport_write_message() above, with additional arguments
- for DKIM.
-
-Returns: TRUE on success; FALSE (with errno) for any failure
-*/
-
-BOOL
-dkim_transport_write_message(int out_fd, transport_ctx * tctx,
- struct ob_dkim * dkim, const uschar ** err)
-{
-int dkim_fd;
-int save_errno = 0;
-BOOL rc;
-uschar * dkim_spool_name;
-uschar * dkim_signature = NULL;
-int sread = 0, wwritten = 0, siglen = 0, options;
-off_t k_file_size;
-const uschar * errstr;
-
-/* If we can't sign, just call the original function. */
-
-if (!(dkim->dkim_private_key && dkim->dkim_domain && dkim->dkim_selector))
- return transport_write_message(out_fd, tctx, 0);
-
-dkim_spool_name = spool_fname(US"input", message_subdir, message_id,
- string_sprintf("-%d-K", (int)getpid()));
-
-if ((dkim_fd = Uopen(dkim_spool_name, O_RDWR|O_CREAT|O_TRUNC, SPOOL_MODE)) < 0)
- {
- /* Can't create spool file. Ugh. */
- rc = FALSE;
- save_errno = errno;
- *err = string_sprintf("dkim spoolfile create: %s", strerror(errno));
- goto CLEANUP;
- }
-
-/* Call original function to write the -K file; does the CRLF expansion
-(but, in the CHUNKING case, not dot-stuffing and dot-termination). */
-
-options = tctx->options;
-tctx->options &= ~topt_use_bdat;
-rc = transport_write_message(dkim_fd, tctx, 0);
-tctx->options = options;
-
-/* Save error state. We must clean up before returning. */
-if (!rc)
- {
- save_errno = errno;
- goto CLEANUP;
- }
-
-/* Rewind file and feed it to the goats^W DKIM lib */
-dkim->dot_stuffed = !!(options & topt_end_dot);
-lseek(dkim_fd, 0, SEEK_SET);
-if ((dkim_signature = dkim_exim_sign(dkim_fd, dkim, &errstr)))
- siglen = Ustrlen(dkim_signature);
-else if (dkim->dkim_strict)
- {
- uschar *dkim_strict_result = expand_string(dkim->dkim_strict);
- if (dkim_strict_result)
- if ( (strcmpic(dkim->dkim_strict,US"1") == 0) ||
- (strcmpic(dkim->dkim_strict,US"true") == 0) )
- {
- /* Set errno to something halfway meaningful */
- save_errno = EACCES;
- log_write(0, LOG_MAIN, "DKIM: message could not be signed,"
- " and dkim_strict is set. Deferring message delivery.");
- *err = errstr;
- rc = FALSE;
- goto CLEANUP;
- }
- }
-
-#ifndef HAVE_LINUX_SENDFILE
-if (options & topt_use_bdat)
-#endif
- k_file_size = lseek(dkim_fd, 0, SEEK_END); /* Fetch file size */
-
-if (options & topt_use_bdat)
- {
-
- /* On big messages output a precursor chunk to get any pipelined
- MAIL & RCPT commands flushed, then reap the responses so we can
- error out on RCPT rejects before sending megabytes. */
-
- if (siglen + k_file_size > DELIVER_OUT_BUFFER_SIZE && siglen > 0)
- {
- if ( tctx->chunk_cb(tctx, siglen, 0) != OK
- || !transport_write_block(out_fd, dkim_signature, siglen)
- || tctx->chunk_cb(tctx, 0, tc_reap_prev) != OK
- )
- goto err;
- siglen = 0;
- }
-
- /* Send the BDAT command for the entire message, as a single LAST-marked
- chunk. */
-
- if (tctx->chunk_cb(tctx, siglen + k_file_size, tc_chunk_last) != OK)
- goto err;
- }
-
-if(siglen > 0 && !transport_write_block(out_fd, dkim_signature, siglen))
- goto err;
-
-#ifdef HAVE_LINUX_SENDFILE
-/* We can use sendfile() to shove the file contents
- to the socket. However only if we don't use TLS,
- as then there's another layer of indirection
- before the data finally hits the socket. */
-if (tls_out.active != out_fd)
- {
- ssize_t copied = 0;
- off_t offset = 0;
-
- /* Rewind file */
- lseek(dkim_fd, 0, SEEK_SET);
-
- while(copied >= 0 && offset < k_file_size)
- copied = sendfile(out_fd, dkim_fd, &offset, k_file_size - offset);
- if (copied < 0)
- goto err;
- }
-else
-
-#endif
-
- {
- /* Rewind file */
- lseek(dkim_fd, 0, SEEK_SET);
-
- /* Send file down the original fd */
- while((sread = read(dkim_fd, deliver_out_buffer, DELIVER_OUT_BUFFER_SIZE)) >0)
- {
- uschar * p = deliver_out_buffer;
- /* write the chunk */
-
- while (sread)
- {
-#ifdef SUPPORT_TLS
- wwritten = tls_out.active == out_fd
- ? tls_write(FALSE, p, sread)
- : write(out_fd, CS p, sread);
-#else
- wwritten = write(out_fd, CS p, sread);
-#endif
- if (wwritten == -1)
- goto err;
- p += wwritten;
- sread -= wwritten;
- }
- }
-
- if (sread == -1)
- {
- save_errno = errno;
- rc = FALSE;
- }
- }
-
-CLEANUP:
- /* unlink -K file */
- (void)close(dkim_fd);
- Uunlink(dkim_spool_name);
- errno = save_errno;
- return rc;
-
-err:
- save_errno = errno;
- rc = FALSE;
- goto CLEANUP;
+ transport_write_block(tctx, deliver_out_buffer, len, FALSE);
}
-#endif
-
/*************************************************
@@ -1239,7 +1085,8 @@ err:
the real work, passing over all the arguments from this function. Otherwise,
set up a filtering process, fork another process to call the internal function
to write to the filter, and in this process just suck from the filter and write
-down the given fd. At the end, tidy up the pipes and the processes.
+down the fd in the transport context. At the end, tidy up the pipes and the
+processes.
Arguments: as for internal_transport_write_message() above
@@ -1248,7 +1095,7 @@ Returns: TRUE on success; FALSE (with errno) for any failure
*/
BOOL
-transport_write_message(int fd, transport_ctx * tctx, int size_limit)
+transport_write_message(transport_ctx * tctx, int size_limit)
{
BOOL last_filter_was_NL = TRUE;
int rc, len, yield, fd_read, fd_write, save_errno;
@@ -1256,8 +1103,6 @@ int pfd[2] = {-1, -1};
pid_t filter_pid, write_pid;
static transport_ctx dummy_tctx = {0};
-if (!tctx) tctx = &dummy_tctx;
-
transport_filter_timed_out = FALSE;
/* If there is no filter command set up, call the internal function that does
@@ -1267,7 +1112,7 @@ if ( !transport_filter_argv
|| !*transport_filter_argv
|| !**transport_filter_argv
)
- return internal_transport_write_message(fd, tctx, size_limit);
+ return internal_transport_write_message(tctx, size_limit);
/* Otherwise the message must be written to a filter process and read back
before being written to the incoming fd. First set up the special processing to
@@ -1297,11 +1142,11 @@ yield = FALSE;
write_pid = (pid_t)(-1);
{
- int bits = fcntl(fd, F_GETFD);
- (void)fcntl(fd, F_SETFD, bits | FD_CLOEXEC);
+ int bits = fcntl(tctx->u.fd, F_GETFD);
+ (void)fcntl(tctx->u.fd, F_SETFD, bits | FD_CLOEXEC);
filter_pid = child_open(USS transport_filter_argv, NULL, 077,
&fd_write, &fd_read, FALSE);
- (void)fcntl(fd, F_SETFD, bits & ~FD_CLOEXEC);
+ (void)fcntl(tctx->u.fd, F_SETFD, bits & ~FD_CLOEXEC);
}
if (filter_pid < 0) goto TIDY_UP; /* errno set */
@@ -1321,10 +1166,11 @@ if ((write_pid = fork()) == 0)
(void)close(pfd[pipe_read]);
nl_check_length = nl_escape_length = 0;
+ tctx->u.fd = fd_write;
tctx->check_string = tctx->escape_string = NULL;
tctx->options &= ~(topt_use_crlf | topt_end_dot | topt_use_bdat);
- rc = internal_transport_write_message(fd_write, tctx, size_limit);
+ rc = internal_transport_write_message(tctx, size_limit);
save_errno = errno;
if ( write(pfd[pipe_write], (void *)&rc, sizeof(BOOL))
@@ -1391,7 +1237,7 @@ for (;;)
if (len > 0)
{
- if (!write_chunk(fd, tctx, deliver_in_buffer, len)) goto TIDY_UP;
+ if (!write_chunk(tctx, deliver_in_buffer, len)) goto TIDY_UP;
last_filter_was_NL = (deliver_in_buffer[len-1] == '\n');
}
@@ -1477,8 +1323,8 @@ if (yield)
nl_check_length = nl_escape_length = 0;
if ( tctx->options & topt_end_dot
&& ( last_filter_was_NL
- ? !write_chunk(fd, tctx, US".\n", 2)
- : !write_chunk(fd, tctx, US"\n.\n", 3)
+ ? !write_chunk(tctx, US".\n", 2)
+ : !write_chunk(tctx, US"\n.\n", 3)
) )
yield = FALSE;
@@ -1486,7 +1332,7 @@ if (yield)
else
yield = (len = chunk_ptr - deliver_out_buffer) <= 0
- || transport_write_block(fd, deliver_out_buffer, len);
+ || transport_write_block(tctx, deliver_out_buffer, len, FALSE);
}
else
errno = save_errno; /* From some earlier error */
diff --git a/src/src/transports/appendfile.c b/src/src/transports/appendfile.c
index 9b3379be2..760e96039 100644
--- a/src/src/transports/appendfile.c
+++ b/src/src/transports/appendfile.c
@@ -916,6 +916,9 @@ copy_mbx_message(int to_fd, int from_fd, off_t saved_size)
int used;
off_t size;
struct stat statbuf;
+transport_ctx tctx = {0};
+
+tctx.u.fd = to_fd;
/* If the current mailbox size is zero, write a header block */
@@ -928,7 +931,7 @@ if (saved_size == 0)
(long int)time(NULL));
for (i = 0; i < MBX_NUSERFLAGS; i++)
sprintf (CS(s += Ustrlen(s)), "\015\012");
- if (!transport_write_block (to_fd, deliver_out_buffer, MBX_HDRSIZE))
+ if (!transport_write_block (&tctx, deliver_out_buffer, MBX_HDRSIZE, FALSE))
return DEFER;
}
@@ -957,7 +960,7 @@ while (size > 0)
if (len == 0) errno = ERRNO_MBXLENGTH;
return DEFER;
}
- if (!transport_write_block(to_fd, deliver_out_buffer, used + len))
+ if (!transport_write_block(&tctx, deliver_out_buffer, used + len, FALSE))
return DEFER;
size -= len;
used = 0;
@@ -2874,13 +2877,14 @@ at initialization time. */
if (yield == OK)
{
transport_ctx tctx = {
+ fd,
tblock,
addr,
ob->check_string,
ob->escape_string,
ob->options
};
- if (!transport_write_message(fd, &tctx, 0))
+ if (!transport_write_message(&tctx, 0))
yield = DEFER;
}
diff --git a/src/src/transports/autoreply.c b/src/src/transports/autoreply.c
index f07cd83cf..cdc4bdd05 100644
--- a/src/src/transports/autoreply.c
+++ b/src/src/transports/autoreply.c
@@ -692,6 +692,7 @@ if (return_message)
:
US"------ This is a copy of the message, including all the headers.\n";
transport_ctx tctx = {
+ fileno(f),
tblock,
addr,
NULL, NULL,
@@ -720,7 +721,7 @@ if (return_message)
fflush(f);
transport_count = 0;
- transport_write_message(fileno(f), &tctx, bounce_return_size_limit);
+ transport_write_message(&tctx, bounce_return_size_limit);
}
/* End the message and wait for the child process to end; no timeout. */
diff --git a/src/src/transports/lmtp.c b/src/src/transports/lmtp.c
index c4606ef8b..610320c25 100644
--- a/src/src/transports/lmtp.c
+++ b/src/src/transports/lmtp.c
@@ -610,6 +610,7 @@ if (send_data)
{
BOOL ok;
transport_ctx tctx = {
+ fd_in,
tblock,
addrlist,
US".", US"..",
@@ -634,7 +635,7 @@ if (send_data)
debug_printf(" LMTP>> writing message and terminating \".\"\n");
transport_count = 0;
- ok = transport_write_message(fd_in, &tctx, 0);
+ ok = transport_write_message(&tctx, 0);
/* Failure can either be some kind of I/O disaster (including timeout),
or the failure of a transport filter or the expansion of added headers. */
diff --git a/src/src/transports/pipe.c b/src/src/transports/pipe.c
index 8b87e4a95..f7f0e590a 100644
--- a/src/src/transports/pipe.c
+++ b/src/src/transports/pipe.c
@@ -552,6 +552,7 @@ const uschar *envlist = ob->environment;
uschar *cmd, *ss;
uschar *eol = ob->use_crlf ? US"\r\n" : US"\n";
transport_ctx tctx = {
+ 0,
tblock,
addr,
ob->check_string,
@@ -739,6 +740,7 @@ if ((pid = child_open(USS argv, envp, ob->umask, &fd_in, &fd_out, TRUE)) < 0)
strerror(errno));
return FALSE;
}
+tctx.u.fd = fd_in;
/* Now fork a process to handle the output that comes down the pipe. */
@@ -829,7 +831,7 @@ if (ob->message_prefix != NULL)
expand_string_message);
return FALSE;
}
- if (!transport_write_block(fd_in, prefix, Ustrlen(prefix)))
+ if (!transport_write_block(&tctx, prefix, Ustrlen(prefix), FALSE))
goto END_WRITE;
}
@@ -857,7 +859,7 @@ if (ob->use_bsmtp)
/* Now the actual message */
-if (!transport_write_message(fd_in, &tctx, 0))
+if (!transport_write_message(&tctx, 0))
goto END_WRITE;
/* Now any configured suffix */
@@ -873,7 +875,7 @@ if (ob->message_suffix)
expand_string_message);
return FALSE;
}
- if (!transport_write_block(fd_in, suffix, Ustrlen(suffix)))
+ if (!transport_write_block(&tctx, suffix, Ustrlen(suffix), FALSE))
goto END_WRITE;
}
diff --git a/src/src/transports/smtp.c b/src/src/transports/smtp.c
index f65463ea0..e28a5bfe6 100644
--- a/src/src/transports/smtp.c
+++ b/src/src/transports/smtp.c
@@ -2848,6 +2848,7 @@ if (!(sx.peer_offered & PEER_OFFERED_CHUNKING) && !sx.ok)
else
{
transport_ctx tctx = {
+ sx.inblock.sock,
tblock,
addrlist,
US".", US"..", /* Escaping strings */
@@ -2896,10 +2897,9 @@ else
transport_count = 0;
#ifndef DISABLE_DKIM
- sx.ok = dkim_transport_write_message(sx.inblock.sock, &tctx, &sx.ob->dkim,
- CUSS &message);
+ sx.ok = dkim_transport_write_message(&tctx, &sx.ob->dkim, CUSS &message);
#else
- sx.ok = transport_write_message(sx.inblock.sock, &tctx, 0);
+ sx.ok = transport_write_message(&tctx, 0);
#endif
/* transport_write_message() uses write() because it is called from other
diff --git a/src/src/verify.c b/src/src/verify.c
index de4ffbe48..e46d2020b 100644
--- a/src/src/verify.c
+++ b/src/src/verify.c
@@ -1308,9 +1308,9 @@ return cutthrough_response(cutthrough.fd, '3', NULL, CUTTHROUGH_DATA_TIMEOUT) ==
}
-/* fd and tctx args only to match write_chunk() */
+/* tctx arg only to match write_chunk() */
static BOOL
-cutthrough_write_chunk(int fd, transport_ctx * tctx, uschar * s, int len)
+cutthrough_write_chunk(transport_ctx * tctx, uschar * s, int len)
{
uschar * s2;
while(s && (s2 = Ustrchr(s, '\n')))
@@ -1339,13 +1339,14 @@ if(cutthrough.fd < 0 || cutthrough.callout_hold_only)
*/
HDEBUG(D_acl) debug_printf_indent("----------- start cutthrough headers send -----------\n");
+tctx.u.fd = cutthrough.fd;
tctx.tblock = cutthrough.addr.transport;
tctx.addr = &cutthrough.addr;
tctx.check_string = US".";
tctx.escape_string = US"..";
tctx.options = topt_use_crlf;
-if (!transport_headers_send(cutthrough.fd, &tctx, &cutthrough_write_chunk))
+if (!transport_headers_send(&tctx, &cutthrough_write_chunk))
return FALSE;
HDEBUG(D_acl) debug_printf_indent("----------- done cutthrough headers send ------------\n");
diff --git a/test/log/4521 b/test/log/4521
index 052569fa9..c5f1e3b5f 100644
--- a/test/log/4521
+++ b/test/log/4521
@@ -2,7 +2,7 @@
1999-03-02 09:44:33 10HmaX-0005vi-00 => a@test.ex R=to_server T=remote_smtp_dkim H=127.0.0.1 [127.0.0.1] K C="250- 661 byte chunk, total 661\\n250 OK id=10HmaY-0005vi-00"
1999-03-02 09:44:33 10HmaX-0005vi-00 Completed
1999-03-02 09:44:33 10HmaZ-0005vi-00 <= sender@testhost.test.ex U=sender P=local S=sss for b@test.ex
-1999-03-02 09:44:33 10HmaZ-0005vi-00 => b@test.ex R=to_server T=remote_smtp_dkim H=127.0.0.1 [127.0.0.1] K C="250- 8520 byte chunk, total 8848\\n250 OK id=10HmbA-0005vi-00"
+1999-03-02 09:44:33 10HmaZ-0005vi-00 => b@test.ex R=to_server T=remote_smtp_dkim H=127.0.0.1 [127.0.0.1] K C="250- 8196 byte chunk, total 8848\\n250 OK id=10HmbA-0005vi-00"
1999-03-02 09:44:33 10HmaZ-0005vi-00 Completed
******** SERVER ********