From 42055a338593d66f0abb6eeb6b03f0eaf4439f57 Mon Sep 17 00:00:00 2001 From: Jeremy Harris Date: Sun, 23 Apr 2017 12:20:43 +0100 Subject: [PATCH] DKIM: avoid use of temporary file for signing --- doc/doc-txt/ChangeLog | 5 + src/OS/Makefile-Base | 3 +- src/scripts/MakeLinks | 3 +- src/src/deliver.c | 9 +- src/src/dkim.c | 20 +- src/src/dkim.h | 2 +- src/src/dkim_transport.c | 346 +++++++++++++++++++++++++++++ src/src/functions.h | 13 +- src/src/macros.h | 2 + src/src/queue.c | 12 +- src/src/smtp_out.c | 2 +- src/src/string.c | 2 +- src/src/structs.h | 8 + src/src/transport.c | 378 ++++++++++---------------------- src/src/transports/appendfile.c | 10 +- src/src/transports/autoreply.c | 3 +- src/src/transports/lmtp.c | 3 +- src/src/transports/pipe.c | 8 +- src/src/transports/smtp.c | 6 +- src/src/verify.c | 7 +- test/log/4521 | 2 +- 21 files changed, 539 insertions(+), 305 deletions(-) create mode 100644 src/src/dkim_transport.c 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 +#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 -#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 ******** -- 2.25.1