* Exim - an Internet mail transport agent *
*************************************************/
-/* Copyright (c) University of Cambridge 1995 - 2016 */
+/* Copyright (c) University of Cambridge 1995 - 2018 */
/* See the file NOTICE for conditions of use and distribution. */
/* General functions concerned with transportation, and generic options for all
#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. */
-
-struct aci {
- struct aci *next;
- address_item *ptr;
- };
-
-
-/* Static data for write_chunk() */
-
-static uschar *chunk_ptr; /* chunk pointer */
-static uschar *nl_check; /* string to look for at line start */
-static int nl_check_length; /* length of same */
-static uschar *nl_escape; /* string to insert */
-static int nl_escape_length; /* length of same */
-static int nl_partial_match; /* length matched at chunk end */
-
-
/* Generic options for transports, all of which live inside transport_instance
data blocks and which therefore have the opt_public flag set. Note that there
are other options living inside this structure which can be set only from
certain transports. */
optionlist optionlist_transports[] = {
+ /* name type value */
{ "*expand_group", opt_stringptr|opt_hidden|opt_public,
(void *)offsetof(transport_instance, expand_gid) },
{ "*expand_user", opt_stringptr|opt_hidden|opt_public,
int optionlist_transports_size = nelem(optionlist_transports);
+#ifdef MACRO_PREDEF
+
+# include "macro_predef.h"
void
-readconf_options_transports(void)
+options_transports(void)
{
struct transport_info * ti;
+uschar buf[64];
-readconf_options_from_list(optionlist_transports, nelem(optionlist_transports), US"TRANSPORTS", NULL);
+options_from_list(optionlist_transports, nelem(optionlist_transports), US"TRANSPORTS", NULL);
for (ti = transports_available; ti->driver_name[0]; ti++)
{
- macro_create(string_sprintf("_DRIVER_TRANSPORT_%T", ti->driver_name), US"y", FALSE, TRUE);
- readconf_options_from_list(ti->options, (unsigned)*ti->options_count, US"TRANSPORT", ti->driver_name);
+ spf(buf, sizeof(buf), US"_DRIVER_TRANSPORT_%T", ti->driver_name);
+ builtin_macro_create(buf);
+ options_from_list(ti->options, (unsigned)*ti->options_count, US"TRANSPORT", ti->driver_name);
}
}
+#else /*!MACRO_PREDEF*/
+
+/* Structure for keeping list of addresses that have been added to
+Envelope-To:, in order to avoid duplication. */
+
+struct aci {
+ struct aci *next;
+ address_item *ptr;
+ };
+
+
+/* Static data for write_chunk() */
+
+static uschar *chunk_ptr; /* chunk pointer */
+static uschar *nl_check; /* string to look for at line start */
+static int nl_check_length; /* length of same */
+static uschar *nl_escape; /* string to insert */
+static int nl_escape_length; /* length of same */
+static int nl_partial_match; /* length matched at chunk end */
+
+
/*************************************************
* Initialize transport list *
*************************************************/
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
+ more further data expected soon
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. */
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()
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.sock == fd ? tls_write(tls_out.active.tls_ctx, block, len, more) :
+#endif
+#ifdef MSG_MORE
+ more && !(tctx->options & topt_not_socket)
+ ? send(fd, block, len, MSG_MORE) :
+#endif
+ write(fd, block, len);
save_errno = errno;
}
else
{
- alarm(local_timeout);
+ ALARM(local_timeout);
+
+ rc =
#ifdef SUPPORT_TLS
- if (tls_out.active == fd)
- rc = tls_write(FALSE, block, len);
- else
+ tls_out.active.sock == fd ? tls_write(tls_out.active.tls_ctx, block, len, more) :
+#endif
+#ifdef MSG_MORE
+ more && !(tctx->options & topt_not_socket)
+ ? send(fd, block, len, MSG_MORE) :
#endif
- rc = write(fd, block, len);
+ write(fd, block, len);
+
save_errno = errno;
- local_timeout = alarm(0);
+ local_timeout = ALARM_CLR(0);
if (sigalrm_seen)
{
errno = ETIMEDOUT;
}
+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 = string_get(1024);
+
+tctx->u.msg = string_catn(tctx->u.msg, block, len);
+return TRUE;
+}
+
+
/*************************************************
BOOL
transport_write_string(int fd, const char *format, ...)
{
+transport_ctx tctx = {{0}};
+gstring gs = { .size = big_buffer_size, .ptr = 0, .s = big_buffer };
va_list ap;
+
va_start(ap, format);
-if (!string_vformat(big_buffer, big_buffer_size, format, ap))
+if (!string_vformat(&gs, FALSE, 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, gs.s, gs.ptr, 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 *
*************************************************/
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;
/* If CHUNKING, prefix with BDAT (size) NON-LAST. Also, reap responses
from previous SMTP commands. */
- if (tctx && tctx->options & topt_use_bdat && tctx->chunk_cb)
+ if (tctx->options & topt_use_bdat && tctx->chunk_cb)
{
- if ( tctx->chunk_cb(fd, tctx, (unsigned)len, 0) != OK
- || !transport_write_block(fd, deliver_out_buffer, len)
- || tctx->chunk_cb(fd, tctx, 0, tc_reap_prev) != OK
+ if ( tctx->chunk_cb(tctx, (unsigned)len, 0) != OK
+ || !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;
}
+ /* Remove CR before NL if required */
+
+ if ( *ptr == '\r' && ptr[1] == '\n'
+ && !(tctx->options & topt_use_crlf)
+ && f.spool_file_wireformat
+ )
+ ptr++;
+
if ((ch = *ptr) == '\n')
{
int left = end - ptr - 1; /* count of chars left after NL */
/* Insert CR before NL if required */
- if (tctx && tctx->options & topt_use_crlf) *chunk_ptr++ = '\r';
+ if (tctx->options & topt_use_crlf && !f.spool_file_wireformat)
+ *chunk_ptr++ = '\r';
*chunk_ptr++ = '\n';
transport_newlines++;
plen = (addr->prefix == NULL)? 0 : Ustrlen(addr->prefix);
slen = Ustrlen(addr->suffix);
-return string_sprintf("%.*s@%s", (at - addr->address - plen - slen),
+return string_sprintf("%.*s@%s", (int)(at - addr->address - plen - slen),
addr->address + plen, at + 1);
}
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;
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;
}
*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));
}
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;
int len;
if (i == 0)
- if (!(s = expand_string(s)) && !expand_string_forcedfail)
+ if (!(s = expand_string(s)) && !f.expand_string_forcedfail)
{
errno = ERRNO_CHHEADER_FAIL;
return FALSE;
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 */
}
/* 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 */
else
- {
DEBUG(D_transport) debug_printf("removed header line:\n%s---\n", h->text);
- }
}
/* Add on any address-specific headers. If there are multiple addresses,
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);
}
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)
{
}
}
}
- else if (!expand_string_forcedfail)
+ else if (!f.expand_string_forcedfail)
{ errno = ERRNO_CHHEADER_FAIL; return FALSE; }
}
/* Separate headers from body with a blank line */
-return sendfn(fd, tctx, US"\n", 1);
+return sendfn(tctx, US"\n", 1);
}
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):
*/
static BOOL
-internal_transport_write_message(int fd, transport_ctx * tctx, int size_limit)
+internal_transport_write_message(transport_ctx * tctx, int size_limit)
{
-int len;
+int len, size = 0;
/* 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;
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
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. */
+are header rewriting rules, apply them. The datasource is not the -D spoolfile
+so temporarily hide the global that adjusts for its format. */
if (!(tctx->options & topt_no_headers))
{
+ BOOL save_wireformat = f.spool_file_wireformat;
+ f.spool_file_wireformat = FALSE;
+
/* Add return-path: if requested. */
if (tctx->options & topt_add_return_path)
uschar buffer[ADDRESS_MAXLENGTH + 20];
int n = sprintf(CS buffer, "Return-path: <%.*s>\n", ADDRESS_MAXLENGTH,
return_path);
- if (!write_chunk(fd, tctx, buffer, n)) return FALSE;
+ if (!write_chunk(tctx, buffer, n)) goto bad;
}
/* Add envelope-to: if requested */
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)) goto bad;
/* 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))
- return FALSE;
+ if (!write_env_to(p, &plist, &dlist, &first, tctx)) goto bad;
/* 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)) goto bad;
store_reset(reset_point);
}
if (tctx->options & topt_add_delivery_date)
{
- 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;
+ uschar * s = tod_stamp(tod_full);
+
+ if ( !write_chunk(tctx, US"Delivery-date: ", 15)
+ || !write_chunk(tctx, s, Ustrlen(s))
+ || !write_chunk(tctx, US"\n", 1)) goto bad;
}
/* Then the message's headers. Don't write any that are flagged as "old";
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))
+ {
+bad:
+ f.spool_file_wireformat = save_wireformat;
return FALSE;
+ }
+
+ f.spool_file_wireformat = save_wireformat;
}
/* When doing RFC3030 CHUNKING output, work out how much data would be in a
if (tctx->options & topt_use_bdat)
{
off_t fsize;
- int hsize, size = 0;
+ int hsize;
if ((hsize = chunk_ptr - deliver_out_buffer) < 0)
hsize = 0;
if (size_limit > 0 && fsize > size_limit)
fsize = size_limit;
size = hsize + fsize;
- if (tctx->options & topt_use_crlf)
+ if (tctx->options & topt_use_crlf && !f.spool_file_wireformat)
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
{
DEBUG(D_transport)
debug_printf("sending small initial BDAT; hsize=%d\n", hsize);
- if ( tctx->chunk_cb(fd, tctx, hsize, 0) != OK
- || !transport_write_block(fd, deliver_out_buffer, hsize)
- || tctx->chunk_cb(fd, tctx, 0, tc_reap_prev) != OK
+ if ( tctx->chunk_cb(tctx, hsize, 0) != OK
+ || !transport_write_block(tctx, deliver_out_buffer, hsize, FALSE)
+ || tctx->chunk_cb(tctx, 0, tc_reap_prev) != OK
)
return FALSE;
chunk_ptr = deliver_out_buffer;
size -= hsize;
}
- /* Emit a LAST datachunk command. */
+ /* Emit a LAST datachunk command, and unmark the context for further
+ BDAT commands. */
- if (tctx->chunk_cb(fd, tctx, size, tc_chunk_last) != OK)
+ if (tctx->chunk_cb(tctx, size, tc_chunk_last) != OK)
return FALSE;
-
tctx->options &= ~topt_use_bdat;
}
is positioned at the start of its file (following the message id), then write
it, applying the size limit if required. */
+/* If we have a wireformat -D file (CRNL lines, non-dotstuffed, no ending dot)
+and we want to send a body without dotstuffing or ending-dot, in-clear,
+then we can just dump it using sendfile.
+This should get used for CHUNKING output and also for writing the -K file for
+dkim signing, when we had CHUNKING input. */
+
+#ifdef OS_SENDFILE
+if ( f.spool_file_wireformat
+ && !(tctx->options & (topt_no_body | topt_end_dot))
+ && !nl_check_length
+ && tls_out.active.sock != tctx->u.fd
+ )
+ {
+ ssize_t copied = 0;
+ off_t offset = SPOOL_DATA_START_OFFSET;
+
+ /* Write out any header data in the buffer */
+
+ if ((len = chunk_ptr - deliver_out_buffer) > 0)
+ {
+ if (!transport_write_block(tctx, deliver_out_buffer, len, TRUE))
+ return FALSE;
+ size -= len;
+ }
+
+ DEBUG(D_transport) debug_printf("using sendfile for body\n");
+
+ while(size > 0)
+ {
+ if ((copied = os_sendfile(tctx->u.fd, deliver_datafile, &offset, size)) <= 0) break;
+ size -= copied;
+ }
+ return copied >= 0;
+ }
+#else
+DEBUG(D_transport) debug_printf("cannot use sendfile for body: no support\n");
+#endif
+
+DEBUG(D_transport)
+ if (!(tctx->options & topt_no_body))
+ debug_printf("cannot use sendfile for body: %s\n",
+ !f.spool_file_wireformat ? "spoolfile not wireformat"
+ : tctx->options & topt_end_dot ? "terminating dot wanted"
+ : nl_check_length ? "dot- or From-stuffing wanted"
+ : "TLS output wanted");
+
if (!(tctx->options & topt_no_body))
{
- int size = size_limit;
+ unsigned long size = size_limit > 0 ? size_limit : ULONG_MAX;
nl_check_length = abs(nl_check_length);
nl_partial_match = 0;
if (lseek(deliver_datafile, SPOOL_DATA_START_OFFSET, SEEK_SET) < 0)
return FALSE;
- while ( (len = MAX(DELIVER_IN_BUFFER_SIZE, size)) > 0
+ while ( (len = MIN(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;
}
if (len != 0) return FALSE;
}
-/* Finished with the check string */
+/* Finished with the check string, and spool-format consideration */
nl_check_length = nl_escape_length = 0;
+f.spool_file_wireformat = FALSE;
/* 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);
+ transport_write_block(tctx, deliver_out_buffer, len, FALSE);
}
-#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(out_fd, tctx, siglen, 0) != OK
- || !transport_write_block(out_fd, dkim_signature, siglen)
- || tctx->chunk_cb(out_fd, tctx, 0, tc_reap_prev) != OK
- )
- goto err;
- siglen = 0;
- }
-
- if (tctx->chunk_cb(out_fd, 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;
-}
-
-#endif
-
/*************************************************
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.
-XXX
Arguments: as for internal_transport_write_message() above
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;
+BOOL save_spool_file_wireformat = f.spool_file_wireformat;
int rc, len, yield, fd_read, fd_write, save_errno;
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;
+f.transport_filter_timed_out = FALSE;
/* If there is no filter command set up, call the internal function that does
the actual work, passing it the incoming fd, and return its result. */
|| !*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
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 */
(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))
!= sizeof(int)
|| write(pfd[pipe_write], (void *)&tctx->addr->more_errno, sizeof(int))
!= sizeof(int)
+ || write(pfd[pipe_write], (void *)&tctx->addr->delivery_usec, sizeof(int))
+ != sizeof(int)
)
rc = FALSE; /* compiler quietening */
_exit(0);
/* When testing, let the subprocess get going */
-if (running_in_test_harness) millisleep(250);
+if (f.running_in_test_harness) millisleep(250);
DEBUG(D_transport)
debug_printf("process %d writing to transport filter\n", (int)write_pid);
/* Copy the output of the filter, remembering if the last character was NL. If
no data is returned, that counts as "ended with NL" (default setting of the
-variable is TRUE). */
+variable is TRUE). The output should always be unix-format as we converted
+any wireformat source on writing input to the filter. */
+f.spool_file_wireformat = FALSE;
chunk_ptr = deliver_out_buffer;
for (;;)
{
sigalrm_seen = FALSE;
- alarm(transport_filter_timeout);
+ ALARM(transport_filter_timeout);
len = read(fd_read, deliver_in_buffer, DELIVER_IN_BUFFER_SIZE);
- alarm(0);
+ ALARM_CLR(0);
if (sigalrm_seen)
{
errno = ETIMEDOUT;
- transport_filter_timed_out = TRUE;
+ f.transport_filter_timed_out = TRUE;
goto TIDY_UP;
}
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');
}
sure. Also apply a paranoia timeout. */
TIDY_UP:
+f.spool_file_wireformat = save_spool_file_wireformat;
save_errno = errno;
(void)close(fd_read);
else if (!ok)
{
int dummy = read(pfd[pipe_read], (void *)&save_errno, sizeof(int));
- dummy = read(pfd[pipe_read], (void *)&(tctx->addr->more_errno), sizeof(int));
+ dummy = read(pfd[pipe_read], (void *)&tctx->addr->more_errno, sizeof(int));
+ dummy = read(pfd[pipe_read], (void *)&tctx->addr->delivery_usec, sizeof(int));
+ dummy = dummy; /* compiler quietening */
yield = FALSE;
}
}
if (yield)
{
nl_check_length = nl_escape_length = 0;
+ f.spool_file_wireformat = FALSE;
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;
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 */
void
transport_update_waiting(host_item *hostlist, uschar *tpname)
{
-uschar buffer[256];
const uschar *prevname = US"";
host_item *host;
open_db dbblock;
/* Open the database for this transport */
-sprintf(CS buffer, "wait-%.200s", tpname);
-dbm_file = dbfn_open(buffer, O_RDWR, &dbblock, TRUE);
-if (dbm_file == NULL) return;
+if (!(dbm_file = dbfn_open(string_sprintf("wait-%.200s", tpname),
+ O_RDWR, &dbblock, TRUE)))
+ return;
/* Scan the list of hosts for which this message is waiting, and ensure
that the message id is in each host record. */
-for (host = hostlist; host!= NULL; host = host->next)
+for (host = hostlist; host; host = host->next)
{
BOOL already = FALSE;
dbdata_wait *host_record;
uschar *s;
int i, host_length;
+ uschar buffer[256];
/* Skip if this is the same host as we just processed; otherwise remember
the name for next time. */
/* Look up the host record; if there isn't one, make an empty one. */
- host_record = dbfn_read(dbm_file, host->name);
- if (host_record == NULL)
+ if (!(host_record = dbfn_read(dbm_file, host->name)))
{
host_record = store_get(sizeof(dbdata_wait) + MESSAGE_ID_LENGTH);
host_record->count = host_record->sequence = 0;
for (s = host_record->text; s < host_record->text + host_length;
s += MESSAGE_ID_LENGTH)
- {
if (Ustrncmp(s, message_id, MESSAGE_ID_LENGTH) == 0)
{ already = TRUE; break; }
- }
/* If we haven't found this message in the main record, search any
continuation records that exist. */
{
dbdata_wait *cont;
sprintf(CS buffer, "%.200s:%d", host->name, i);
- cont = dbfn_read(dbm_file, buffer);
- if (cont != NULL)
+ if ((cont = dbfn_read(dbm_file, buffer)))
{
int clen = cont->count * MESSAGE_ID_LENGTH;
for (s = cont->text; s < cont->text + clen; s += MESSAGE_ID_LENGTH)
- {
if (Ustrncmp(s, message_id, MESSAGE_ID_LENGTH) == 0)
{ already = TRUE; break; }
- }
}
}
int host_length;
open_db dbblock;
open_db *dbm_file;
-uschar buffer[256];
int i;
struct stat statbuf;
/* Open the waiting information database. */
-sprintf(CS buffer, "wait-%.200s", transport_name);
-dbm_file = dbfn_open(buffer, O_RDWR, &dbblock, TRUE);
-if (dbm_file == NULL) return FALSE;
+if (!(dbm_file = dbfn_open(string_sprintf("wait-%.200s", transport_name),
+ O_RDWR, &dbblock, TRUE)))
+ return FALSE;
/* See if there is a record for this host; if not, there's nothing to do. */
}
}
-/* Jeremy: check for a continuation record, this code I do not know how to
-test but the code should work */
+ /* Check for a continuation record. */
while (host_length <= 0)
{
int i;
dbdata_wait * newr = NULL;
+ uschar buffer[256];
/* Search for a continuation */
* Deliver waiting message down same socket *
*************************************************/
+/* Just the regain-root-privilege exec portion */
+void
+transport_do_pass_socket(const uschar *transport_name, const uschar *hostname,
+ const uschar *hostaddress, uschar *id, int socket_fd)
+{
+int i = 20;
+const uschar **argv;
+
+/* Set up the calling arguments; use the standard function for the basics,
+but we have a number of extras that may be added. */
+
+argv = CUSS child_exec_exim(CEE_RETURN_ARGV, TRUE, &i, FALSE, 0);
+
+if (f.smtp_authenticated) argv[i++] = US"-MCA";
+if (smtp_peer_options & OPTION_CHUNKING) argv[i++] = US"-MCK";
+if (smtp_peer_options & OPTION_DSN) argv[i++] = US"-MCD";
+if (smtp_peer_options & OPTION_PIPE) argv[i++] = US"-MCP";
+if (smtp_peer_options & OPTION_SIZE) argv[i++] = US"-MCS";
+#ifdef SUPPORT_TLS
+if (smtp_peer_options & OPTION_TLS)
+ if (tls_out.active.sock >= 0 || continue_proxy_cipher)
+ {
+ argv[i++] = US"-MCt";
+ argv[i++] = sending_ip_address;
+ argv[i++] = string_sprintf("%d", sending_port);
+ argv[i++] = tls_out.active.sock >= 0 ? tls_out.cipher : continue_proxy_cipher;
+ }
+ else
+ argv[i++] = US"-MCT";
+#endif
+
+if (queue_run_pid != (pid_t)0)
+ {
+ argv[i++] = US"-MCQ";
+ argv[i++] = string_sprintf("%d", queue_run_pid);
+ argv[i++] = string_sprintf("%d", queue_run_pipe);
+ }
+
+argv[i++] = US"-MC";
+argv[i++] = US transport_name;
+argv[i++] = US hostname;
+argv[i++] = US hostaddress;
+argv[i++] = string_sprintf("%d", continue_sequence + 1);
+argv[i++] = id;
+argv[i++] = NULL;
+
+/* Arrange for the channel to be on stdin. */
+
+if (socket_fd != 0)
+ {
+ (void)dup2(socket_fd, 0);
+ (void)close(socket_fd);
+ }
+
+DEBUG(D_exec) debug_print_argv(argv);
+exim_nullstd(); /* Ensure std{out,err} exist */
+execv(CS argv[0], (char *const *)argv);
+
+DEBUG(D_any) debug_printf("execv failed: %s\n", strerror(errno));
+_exit(errno); /* Note: must be _exit(), NOT exit() */
+}
+
+
+
/* Fork a new exim process to deliver the message, and do a re-exec, both to
get a clean delivery process, and to regain root privilege in cases where it
has been given away.
if ((pid = fork()) == 0)
{
- int i = 19;
- const uschar **argv;
-
/* Disconnect entirely from the parent process. If we are running in the
test harness, wait for a bit to allow the previous process time to finish,
write the log, etc., so that the output is always in the same order for
automatic comparison. */
- if ((pid = fork()) != 0) _exit(EXIT_SUCCESS);
- if (running_in_test_harness) sleep(1);
-
- /* Set up the calling arguments; use the standard function for the basics,
- but we have a number of extras that may be added. */
-
- argv = CUSS child_exec_exim(CEE_RETURN_ARGV, TRUE, &i, FALSE, 0);
-
- if (smtp_authenticated) argv[i++] = US"-MCA";
-
- if (smtp_peer_options & PEER_OFFERED_CHUNKING) argv[i++] = US"-MCK";
- if (smtp_peer_options & PEER_OFFERED_DSN) argv[i++] = US"-MCD";
- if (smtp_peer_options & PEER_OFFERED_PIPE) argv[i++] = US"-MCP";
- if (smtp_peer_options & PEER_OFFERED_SIZE) argv[i++] = US"-MCS";
-#ifdef SUPPORT_TLS
- if (smtp_peer_options & PEER_OFFERED_TLS)
- if (tls_out.active >= 0 || continue_proxy)
- {
- argv[i++] = US"-MCt";
- argv[i++] = sending_ip_address;
- argv[i++] = string_sprintf("%d", sending_port);
- }
- else
- argv[i++] = US"-MCT";
-#endif
-
- if (queue_run_pid != (pid_t)0)
+ if ((pid = fork()) != 0)
{
- argv[i++] = US"-MCQ";
- argv[i++] = string_sprintf("%d", queue_run_pid);
- argv[i++] = string_sprintf("%d", queue_run_pipe);
+ DEBUG(D_transport) debug_printf("transport_pass_socket succeeded (final-pid %d)\n", pid);
+ _exit(EXIT_SUCCESS);
}
+ if (f.running_in_test_harness) sleep(1);
- argv[i++] = US"-MC";
- argv[i++] = US transport_name;
- argv[i++] = US hostname;
- argv[i++] = US hostaddress;
- argv[i++] = string_sprintf("%d", continue_sequence + 1);
- argv[i++] = id;
- argv[i++] = NULL;
-
- /* Arrange for the channel to be on stdin. */
-
- if (socket_fd != 0)
- {
- (void)dup2(socket_fd, 0);
- (void)close(socket_fd);
- }
-
- DEBUG(D_exec) debug_print_argv(argv);
- exim_nullstd(); /* Ensure std{out,err} exist */
- execv(CS argv[0], (char *const *)argv);
-
- DEBUG(D_any) debug_printf("execv failed: %s\n", strerror(errno));
- _exit(errno); /* Note: must be _exit(), NOT exit() */
+ transport_do_pass_socket(transport_name, hostname, hostaddress,
+ id, socket_fd);
}
/* If the process creation succeeded, wait for the first-level child, which
{
int rc;
while ((rc = wait(&status)) != pid && (rc >= 0 || errno != ECHILD));
- DEBUG(D_transport) debug_printf("transport_pass_socket succeeded\n");
+ DEBUG(D_transport) debug_printf("transport_pass_socket succeeded (inter-pid %d)\n", pid);
return TRUE;
}
else
while (isspace(*s)) s++;
}
-argv[argcount] = (uschar *)0;
+argv[argcount] = US 0;
/* If *s != 0 we have run out of argument slots. */
DEBUG(D_transport)
{
debug_printf("direct command:\n");
- for (i = 0; argv[i] != (uschar *)0; i++)
+ for (i = 0; argv[i] != US 0; i++)
debug_printf(" argv[%d] = %s\n", i, string_printing(argv[i]));
}
addr->parent != NULL &&
Ustrcmp(addr->parent->address, "system-filter") == 0;
- for (i = 0; argv[i] != (uschar *)0; i++)
+ for (i = 0; argv[i] != US 0; i++)
{
/* Handle special fudge for passing an address list */
while (isspace(*s)) s++; /* strip space after arg */
}
- address_pipe_argv[address_pipe_argcount] = (uschar *)0;
+ address_pipe_argv[address_pipe_argcount] = US 0;
/* If *s != 0 we have run out of argument slots. */
if (*s != 0)
* [argv 0][argv 1][argv 2=pipeargv[0]][argv 3=pipeargv[1]][old argv 3][0]
*/
for (address_pipe_i = 0;
- address_pipe_argv[address_pipe_i] != (uschar *)0;
+ address_pipe_argv[address_pipe_i] != US 0;
address_pipe_i++)
{
argv[i++] = address_pipe_argv[address_pipe_i];
else
{
const uschar *expanded_arg;
- enable_dollar_recipients = allow_dollar_recipients;
+ f.enable_dollar_recipients = allow_dollar_recipients;
expanded_arg = expand_cstring(argv[i]);
- enable_dollar_recipients = FALSE;
+ f.enable_dollar_recipients = FALSE;
if (expanded_arg == NULL)
{
DEBUG(D_transport)
{
debug_printf("direct command after expansion:\n");
- for (i = 0; argv[i] != (uschar *)0; i++)
+ for (i = 0; argv[i] != US 0; i++)
debug_printf(" argv[%d] = %s\n", i, string_printing(argv[i]));
}
}
return TRUE;
}
+#endif /*!MACRO_PREDEF*/
/* vi: aw ai sw=2
*/
/* End of transport.c */