* 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. */
/* The main code for delivering a message. */
#include "exim.h"
+#include "transports/smtp.h"
+#include <sys/uio.h>
#include <assert.h>
+/*************************************************
+* read as much as requested *
+*************************************************/
+
+/* The syscall read(2) doesn't always returns as much as we want. For
+several reasons it might get less. (Not talking about signals, as syscalls
+are restartable). When reading from a network or pipe connection the sender
+might send in smaller chunks, with delays between these chunks. The read(2)
+may return such a chunk.
+
+The more the writer writes and the smaller the pipe between write and read is,
+the more we get the chance of reading leass than requested. (See bug 2130)
+
+This function read(2)s until we got all the data we *requested*.
+
+Note: This function may block. Use it only if you're sure about the
+amount of data you will get.
+
+Argument:
+ fd the file descriptor to read from
+ buffer pointer to a buffer of size len
+ len the requested(!) amount of bytes
+
+Returns: the amount of bytes read
+*/
+static ssize_t
+readn(int fd, void * buffer, size_t len)
+{
+ void * next = buffer;
+ void * end = buffer + len;
+
+ while (next < end)
+ {
+ ssize_t got = read(fd, next, end - next);
+
+ /* I'm not sure if there are signals that can interrupt us,
+ for now I assume the worst */
+ if (got == -1 && errno == EINTR) continue;
+ if (got <= 0) return next - buffer;
+ next += got;
+ }
+
+ return len;
+}
+
+
/*************************************************
* Make a new address item *
*************************************************/
addr2->transport_return = addr->transport_return;
addr2->basic_errno = addr->basic_errno;
addr2->more_errno = addr->more_errno;
+ addr2->delivery_usec = addr->delivery_usec;
addr2->special_action = addr->special_action;
addr2->message = addr->message;
addr2->user_message = addr->user_message;
fields on incoming lines only.
Arguments:
- s The log line buffer
- sizep Pointer to the buffer size
- ptrp Pointer to current index into buffer
+ g The log line
addr The address to be logged
Returns: New value for s
*/
-static uschar *
-d_log_interface(uschar *s, int *sizep, int *ptrp)
+static gstring *
+d_log_interface(gstring * g)
{
if (LOGGING(incoming_interface) && LOGGING(outgoing_interface)
&& sending_ip_address)
{
- s = string_append(s, sizep, ptrp, 2, US" I=[", sending_ip_address);
- s = LOGGING(outgoing_port)
- ? string_append(s, sizep, ptrp, 2, US"]:",
- string_sprintf("%d", sending_port))
- : string_catn(s, sizep, ptrp, US"]", 1);
+ g = string_append(g, 2, US" I=[", sending_ip_address);
+ g = LOGGING(outgoing_port)
+ ? string_append(g, 2, US"]:", string_sprintf("%d", sending_port))
+ : string_catn(g, US"]", 1);
}
-return s;
+return g;
}
-static uschar *
-d_hostlog(uschar * s, int * sp, int * pp, address_item * addr)
+static gstring *
+d_hostlog(gstring * g, address_item * addr)
{
host_item * h = addr->host_used;
-s = string_append(s, sp, pp, 2, US" H=", h->name);
+g = string_append(g, 2, US" H=", h->name);
if (LOGGING(dnssec) && h->dnssec == DS_YES)
- s = string_catn(s, sp, pp, US" DS", 3);
+ g = string_catn(g, US" DS", 3);
-s = string_append(s, sp, pp, 3, US" [", h->address, US"]");
+g = string_append(g, 3, US" [", h->address, US"]");
if (LOGGING(outgoing_port))
- s = string_append(s, sp, pp, 2, US":", string_sprintf("%d", h->port));
+ g = string_append(g, 2, US":", string_sprintf("%d", h->port));
#ifdef SUPPORT_SOCKS
if (LOGGING(proxy) && proxy_local_address)
{
- s = string_append(s, sp, pp, 3, US" PRX=[", proxy_local_address, US"]");
+ g = string_append(g, 3, US" PRX=[", proxy_local_address, US"]");
if (LOGGING(outgoing_port))
- s = string_append(s, sp, pp, 2, US":", string_sprintf("%d",
- proxy_local_port));
+ g = string_append(g, 2, US":", string_sprintf("%d", proxy_local_port));
}
#endif
-return d_log_interface(s, sp, pp);
+g = d_log_interface(g);
+
+if (testflag(addr, af_tcp_fastopen))
+ g = string_catn(g, US" TFO", 4);
+
+return g;
}
#ifdef SUPPORT_TLS
-static uschar *
-d_tlslog(uschar * s, int * sizep, int * ptrp, address_item * addr)
+static gstring *
+d_tlslog(gstring * s, address_item * addr)
{
if (LOGGING(tls_cipher) && addr->cipher)
- s = string_append(s, sizep, ptrp, 2, US" X=", addr->cipher);
+ s = string_append(s, 2, US" X=", addr->cipher);
if (LOGGING(tls_certificate_verified) && addr->cipher)
- s = string_append(s, sizep, ptrp, 2, US" CV=",
+ s = string_append(s, 2, US" CV=",
testflag(addr, af_cert_verified)
?
#ifdef EXPERIMENTAL_DANE
"yes"
: "no");
if (LOGGING(tls_peerdn) && addr->peerdn)
- s = string_append(s, sizep, ptrp, 3, US" DN=\"",
- string_printing(addr->peerdn), US"\"");
+ s = string_append(s, 3, US" DN=\"", string_printing(addr->peerdn), US"\"");
return s;
}
#endif
addr->host_used
|| Ustrcmp(addr->transport->driver_name, "smtp") == 0
|| Ustrcmp(addr->transport->driver_name, "lmtp") == 0
- ? addr->message : NULL);
+ ? addr->message : NULL);
deliver_host_port = save_port;
deliver_host_address = save_address;
Arguments:
addr the address being logged
yield the current dynamic buffer pointer
- sizeptr points to current size
- ptrptr points to current insert pointer
Returns: the new value of the buffer pointer
*/
-static uschar *
-string_get_localpart(address_item *addr, uschar *yield, int *sizeptr,
- int *ptrptr)
+static gstring *
+string_get_localpart(address_item * addr, gstring * yield)
{
uschar * s;
if (testflag(addr, af_utf8_downcvt))
s = string_localpart_utf8_to_alabel(s, NULL);
#endif
- yield = string_cat(yield, sizeptr, ptrptr, s);
+ yield = string_cat(yield, s);
}
s = addr->local_part;
if (testflag(addr, af_utf8_downcvt))
s = string_localpart_utf8_to_alabel(s, NULL);
#endif
-yield = string_cat(yield, sizeptr, ptrptr, s);
+yield = string_cat(yield, s);
s = addr->suffix;
if (testflag(addr, af_include_affixes) && s)
if (testflag(addr, af_utf8_downcvt))
s = string_localpart_utf8_to_alabel(s, NULL);
#endif
- yield = string_cat(yield, sizeptr, ptrptr, s);
+ yield = string_cat(yield, s);
}
return yield;
case, we include the affixes here too.
Arguments:
- str points to start of growing string, or NULL
- size points to current allocation for string
- ptr points to offset for append point; updated on exit
+ g points to growing-string struct
addr bottom (ultimate) address
all_parents if TRUE, include all parents
success TRUE for successful delivery
Returns: a growable string in dynamic store
*/
-static uschar *
-string_log_address(uschar * str, int * size, int * ptr,
+static gstring *
+string_log_address(gstring * g,
address_item *addr, BOOL all_parents, BOOL success)
{
BOOL add_topaddr = TRUE;
) )
{
if (testflag(addr, af_file) && addr->local_part[0] != '/')
- str = string_catn(str, size, ptr, CUS"save ", 5);
- str = string_get_localpart(addr, str, size, ptr);
+ g = string_catn(g, CUS"save ", 5);
+ g = string_get_localpart(addr, g);
}
/* Other deliveries start with the full address. It we have split it into local
else
{
- uschar * cmp = str + *ptr;
+ uschar * cmp = g->s + g->ptr;
if (addr->local_part)
{
const uschar * s;
- str = string_get_localpart(addr, str, size, ptr);
- str = string_catn(str, size, ptr, US"@", 1);
+ g = string_get_localpart(addr, g);
+ g = string_catn(g, US"@", 1);
s = addr->domain;
#ifdef SUPPORT_I18N
if (testflag(addr, af_utf8_downcvt))
s = string_localpart_utf8_to_alabel(s, NULL);
#endif
- str = string_cat(str, size, ptr, s);
+ g = string_cat(g, s);
}
else
- str = string_cat(str, size, ptr, addr->address);
+ g = string_cat(g, addr->address);
/* If the address we are going to print is the same as the top address,
and all parents are not being included, don't add on the top address. First
of all, do a caseless comparison; if this succeeds, do a caseful comparison
on the local parts. */
- str[*ptr] = 0;
+ string_from_gstring(g); /* ensure nul-terminated */
if ( strcmpic(cmp, topaddr->address) == 0
&& Ustrncmp(cmp, topaddr->address, Ustrchr(cmp, '@') - cmp) == 0
&& !addr->onetime_parent
address_item *addr2;
for (addr2 = addr->parent; addr2 != topaddr; addr2 = addr2->parent)
{
- str = string_catn(str, size, ptr, s, 2);
- str = string_cat (str, size, ptr, addr2->address);
+ g = string_catn(g, s, 2);
+ g = string_cat (g, addr2->address);
if (!all_parents) break;
s = US", ";
}
- str = string_catn(str, size, ptr, US")", 1);
+ g = string_catn(g, US")", 1);
}
/* Add the top address if it is required */
if (add_topaddr)
- str = string_append(str, size, ptr, 3,
+ g = string_append(g, 3,
US" <",
addr->onetime_parent ? addr->onetime_parent : topaddr->address,
US">");
-return str;
+return g;
+}
+
+
+
+void
+timesince(struct timeval * diff, struct timeval * then)
+{
+gettimeofday(diff, NULL);
+diff->tv_sec -= then->tv_sec;
+if ((diff->tv_usec -= then->tv_usec) < 0)
+ {
+ diff->tv_sec--;
+ diff->tv_usec += 1000*1000;
+ }
+}
+
+
+
+uschar *
+string_timediff(struct timeval * diff)
+{
+static uschar buf[sizeof("0.000s")];
+
+if (diff->tv_sec >= 5 || !LOGGING(millisec))
+ return readconf_printtime((int)diff->tv_sec);
+
+sprintf(CS buf, "%d.%03ds", (int)diff->tv_sec, (int)diff->tv_usec/1000);
+return buf;
}
+uschar *
+string_timesince(struct timeval * then)
+{
+struct timeval diff;
+
+timesince(&diff, then);
+return string_timediff(&diff);
+}
+
/******************************************************************************/
void
delivery_log(int flags, address_item * addr, int logchar, uschar * msg)
{
-int size = 256; /* Used for a temporary, */
-int ptr = 0; /* expanding buffer, for */
-uschar * s; /* building log lines; */
+gstring * g; /* Used for a temporary, expanding buffer, for building log lines */
void * reset_point; /* released afterwards. */
/* Log the delivery on the main log. We use an extensible string to build up
lookup_dnssec_authenticated = NULL;
#endif
-s = reset_point = store_get(size);
+g = reset_point = string_get(256);
if (msg)
- s = string_append(s, &size, &ptr, 2, host_and_ident(TRUE), US" ");
+ g = string_append(g, 2, host_and_ident(TRUE), US" ");
else
{
- s[ptr++] = logchar;
- s = string_catn(s, &size, &ptr, US"> ", 2);
+ g->s[0] = logchar; g->ptr = 1;
+ g = string_catn(g, US"> ", 2);
}
-s = string_log_address(s, &size, &ptr, addr, LOGGING(all_parents), TRUE);
+g = string_log_address(g, addr, LOGGING(all_parents), TRUE);
if (LOGGING(sender_on_delivery) || msg)
- s = string_append(s, &size, &ptr, 3, US" F=<",
+ g = string_append(g, 3, US" F=<",
#ifdef SUPPORT_I18N
testflag(addr, af_utf8_downcvt)
? string_address_utf8_to_alabel(sender_address, NULL)
US">");
if (*queue_name)
- s = string_append(s, &size, &ptr, 2, US" Q=", queue_name);
+ g = string_append(g, 2, US" Q=", queue_name);
#ifdef EXPERIMENTAL_SRS
if(addr->prop.srs_sender)
- s = string_append(s, &size, &ptr, 3, US" SRS=<", addr->prop.srs_sender, US">");
+ g = string_append(g, 3, US" SRS=<", addr->prop.srs_sender, US">");
#endif
/* You might think that the return path must always be set for a successful
being run at all. */
if (used_return_path && LOGGING(return_path_on_delivery))
- s = string_append(s, &size, &ptr, 3, US" P=<", used_return_path, US">");
+ g = string_append(g, 3, US" P=<", used_return_path, US">");
if (msg)
- s = string_append(s, &size, &ptr, 2, US" ", msg);
+ g = string_append(g, 2, US" ", msg);
/* For a delivery from a system filter, there may not be a router */
if (addr->router)
- s = string_append(s, &size, &ptr, 2, US" R=", addr->router->name);
+ g = string_append(g, 2, US" R=", addr->router->name);
-s = string_append(s, &size, &ptr, 2, US" T=", addr->transport->name);
+g = string_append(g, 2, US" T=", addr->transport->name);
if (LOGGING(delivery_size))
- s = string_append(s, &size, &ptr, 2, US" S=",
+ g = string_append(g, 2, US" S=",
string_sprintf("%d", transport_count));
/* Local delivery */
if (addr->transport->info->local)
{
if (addr->host_list)
- s = string_append(s, &size, &ptr, 2, US" H=", addr->host_list->name);
- s = d_log_interface(s, &size, &ptr);
+ g = string_append(g, 2, US" H=", addr->host_list->name);
+ g = d_log_interface(g);
if (addr->shadow_message)
- s = string_cat(s, &size, &ptr, addr->shadow_message);
+ g = string_cat(g, addr->shadow_message);
}
/* Remote delivery */
{
if (addr->host_used)
{
- s = d_hostlog(s, &size, &ptr, addr);
+ g = d_hostlog(g, addr);
if (continue_sequence > 1)
- s = string_catn(s, &size, &ptr, US"*", 1);
+ g = string_catn(g, US"*", 1);
#ifndef DISABLE_EVENT
deliver_host_address = addr->host_used->address;
}
#ifdef SUPPORT_TLS
- s = d_tlslog(s, &size, &ptr, addr);
+ g = d_tlslog(g, addr);
#endif
if (addr->authenticator)
{
- s = string_append(s, &size, &ptr, 2, US" A=", addr->authenticator);
+ g = string_append(g, 2, US" A=", addr->authenticator);
if (addr->auth_id)
{
- s = string_append(s, &size, &ptr, 2, US":", addr->auth_id);
+ g = string_append(g, 2, US":", addr->auth_id);
if (LOGGING(smtp_mailauth) && addr->auth_sndr)
- s = string_append(s, &size, &ptr, 2, US":", addr->auth_sndr);
+ g = string_append(g, 2, US":", addr->auth_sndr);
}
}
#ifndef DISABLE_PRDR
- if (addr->flags & af_prdr_used)
- s = string_catn(s, &size, &ptr, US" PRDR", 5);
+ if (testflag(addr, af_prdr_used))
+ g = string_catn(g, US" PRDR", 5);
#endif
- if (addr->flags & af_chunking_used)
- s = string_catn(s, &size, &ptr, US" K", 2);
+ if (testflag(addr, af_chunking_used))
+ g = string_catn(g, US" K", 2);
}
/* confirmation message (SMTP (host_used) and LMTP (driver_name)) */
}
*p++ = '\"';
*p = 0;
- s = string_append(s, &size, &ptr, 2, US" C=", big_buffer);
+ g = string_append(g, 2, US" C=", big_buffer);
}
/* Time on queue and actual time taken to deliver */
if (LOGGING(queue_time))
- s = string_append(s, &size, &ptr, 2, US" QT=",
- readconf_printtime( (int) ((long)time(NULL) - (long)received_time)) );
+ g = string_append(g, 2, US" QT=",
+ string_timesince(&received_time));
if (LOGGING(deliver_time))
- s = string_append(s, &size, &ptr, 2, US" DT=",
- readconf_printtime(addr->more_errno));
+ {
+ struct timeval diff = {.tv_sec = addr->more_errno, .tv_usec = addr->delivery_usec};
+ g = string_append(g, 2, US" DT=", string_timediff(&diff));
+ }
/* string_cat() always leaves room for the terminator. Release the
store we used to build the line after writing it. */
-s[ptr] = 0;
-log_write(0, flags, "%s", s);
+log_write(0, flags, "%s", string_from_gstring(g));
#ifndef DISABLE_EVENT
if (!msg) msg_event_raise(US"msg:delivery", addr);
deferral_log(address_item * addr, uschar * now,
int logflags, uschar * driver_name, uschar * driver_kind)
{
-int size = 256; /* Used for a temporary, */
-int ptr = 0; /* expanding buffer, for */
-uschar * s; /* building log lines; */
-void * reset_point; /* released afterwards. */
-
-uschar ss[32];
+gstring * g;
+void * reset_point;
/* Build up the line that is used for both the message log and the main
log. */
-s = reset_point = store_get(size);
+g = reset_point = string_get(256);
/* Create the address string for logging. Must not do this earlier, because
an OK result may be changed to FAIL when a pipe returns text. */
-s = string_log_address(s, &size, &ptr, addr, LOGGING(all_parents), FALSE);
+g = string_log_address(g, addr, LOGGING(all_parents), FALSE);
if (*queue_name)
- s = string_append(s, &size, &ptr, 2, US" Q=", queue_name);
+ g = string_append(g, 2, US" Q=", queue_name);
/* Either driver_name contains something and driver_kind contains
" router" or " transport" (note the leading space), or driver_name is
if (driver_name)
{
if (driver_kind[1] == 't' && addr->router)
- s = string_append(s, &size, &ptr, 2, US" R=", addr->router->name);
- Ustrcpy(ss, " ?=");
- ss[1] = toupper(driver_kind[1]);
- s = string_append(s, &size, &ptr, 2, ss, driver_name);
+ g = string_append(g, 2, US" R=", addr->router->name);
+ g = string_cat(g, string_sprintf(" %c=%s", toupper(driver_kind[1]), driver_name));
}
else if (driver_kind)
- s = string_append(s, &size, &ptr, 2, US" ", driver_kind);
+ g = string_append(g, 2, US" ", driver_kind);
/*XXX need an s+s+p sprintf */
-sprintf(CS ss, " defer (%d)", addr->basic_errno);
-s = string_cat(s, &size, &ptr, ss);
+g = string_cat(g, string_sprintf(" defer (%d)", addr->basic_errno));
if (addr->basic_errno > 0)
- s = string_append(s, &size, &ptr, 2, US": ",
+ g = string_append(g, 2, US": ",
US strerror(addr->basic_errno));
if (addr->host_used)
{
- s = string_append(s, &size, &ptr, 5,
+ g = string_append(g, 5,
US" H=", addr->host_used->name,
US" [", addr->host_used->address, US"]");
if (LOGGING(outgoing_port))
{
int port = addr->host_used->port;
- s = string_append(s, &size, &ptr, 2,
+ g = string_append(g, 2,
US":", port == PORT_NONE ? US"25" : string_sprintf("%d", port));
}
}
if (addr->message)
- s = string_append(s, &size, &ptr, 2, US": ", addr->message);
+ g = string_append(g, 2, US": ", addr->message);
-s[ptr] = 0;
+(void) string_from_gstring(g);
/* Log the deferment in the message log, but don't clutter it
up with retry-time defers after the first delivery attempt. */
if (deliver_firsttime || addr->basic_errno > ERRNO_RETRY_BASE)
- deliver_msglog("%s %s\n", now, s);
+ deliver_msglog("%s %s\n", now, g->s);
/* Write the main log and reset the store.
For errors of the type "retry time not reached" (also remotes skipped
log_write(addr->basic_errno <= ERRNO_RETRY_BASE ? L_retry_defer : 0, logflags,
- "== %s", s);
+ "== %s", g->s);
store_reset(reset_point);
return;
static void
failure_log(address_item * addr, uschar * driver_kind, uschar * now)
{
-int size = 256; /* Used for a temporary, */
-int ptr = 0; /* expanding buffer, for */
-uschar * s; /* building log lines; */
-void * reset_point; /* released afterwards. */
+void * reset_point;
+gstring * g = reset_point = string_get(256);
/* Build up the log line for the message and main logs */
-s = reset_point = store_get(size);
-
/* Create the address string for logging. Must not do this earlier, because
an OK result may be changed to FAIL when a pipe returns text. */
-s = string_log_address(s, &size, &ptr, addr, LOGGING(all_parents), FALSE);
+g = string_log_address(g, addr, LOGGING(all_parents), FALSE);
if (LOGGING(sender_on_delivery))
- s = string_append(s, &size, &ptr, 3, US" F=<", sender_address, US">");
+ g = string_append(g, 3, US" F=<", sender_address, US">");
if (*queue_name)
- s = string_append(s, &size, &ptr, 2, US" Q=", queue_name);
+ g = string_append(g, 2, US" Q=", queue_name);
/* Return path may not be set if no delivery actually happened */
if (used_return_path && LOGGING(return_path_on_delivery))
- s = string_append(s, &size, &ptr, 3, US" P=<", used_return_path, US">");
+ g = string_append(g, 3, US" P=<", used_return_path, US">");
if (addr->router)
- s = string_append(s, &size, &ptr, 2, US" R=", addr->router->name);
+ g = string_append(g, 2, US" R=", addr->router->name);
if (addr->transport)
- s = string_append(s, &size, &ptr, 2, US" T=", addr->transport->name);
+ g = string_append(g, 2, US" T=", addr->transport->name);
if (addr->host_used)
- s = d_hostlog(s, &size, &ptr, addr);
+ g = d_hostlog(g, addr);
#ifdef SUPPORT_TLS
-s = d_tlslog(s, &size, &ptr, addr);
+g = d_tlslog(g, addr);
#endif
if (addr->basic_errno > 0)
- s = string_append(s, &size, &ptr, 2, US": ", US strerror(addr->basic_errno));
+ g = string_append(g, 2, US": ", US strerror(addr->basic_errno));
if (addr->message)
- s = string_append(s, &size, &ptr, 2, US": ", addr->message);
+ g = string_append(g, 2, US": ", addr->message);
-s[ptr] = 0;
+(void) string_from_gstring(g);
/* Do the logging. For the message log, "routing failed" for those cases,
just to make it clearer. */
if (driver_kind)
- deliver_msglog("%s %s failed for %s\n", now, driver_kind, s);
+ deliver_msglog("%s %s failed for %s\n", now, driver_kind, g->s);
else
- deliver_msglog("%s %s\n", now, s);
+ deliver_msglog("%s %s\n", now, g->s);
-log_write(0, LOG_MAIN, "** %s", s);
+log_write(0, LOG_MAIN, "** %s", g->s);
#ifndef DISABLE_EVENT
msg_event_raise(US"msg:fail:delivery", addr);
/* Set up driver kind and name for logging. Disable logging if the router or
transport has disabled it. */
-if (driver_type == DTYPE_TRANSPORT)
+if (driver_type == EXIM_DTYPE_TRANSPORT)
{
if (addr->transport)
{
}
else driver_kind = US"transporting";
}
-else if (driver_type == DTYPE_ROUTER)
+else if (driver_type == EXIM_DTYPE_ROUTER)
{
if (addr->router)
{
later (with a log entry). */
if (!*sender_address && message_age >= ignore_bounce_errors_after)
- setflag(addr, af_ignore_error);
+ addr->prop.ignore_error = TRUE;
/* Freeze the message if requested, or if this is a bounce message (or other
message with null sender) and this address does not have its own errors
to ignore occurs later, instead of sending a message. Logging of freezing
occurs later, just before writing the -H file. */
- if ( !testflag(addr, af_ignore_error)
+ if ( !addr->prop.ignore_error
&& ( addr->special_action == SPECIAL_FREEZE
|| (sender_address[0] == 0 && !addr->prop.errors_address)
) )
addr->return_filename =
spool_fname(US"msglog", message_subdir, message_id,
string_sprintf("-%d-%d", getpid(), return_count++));
-
+
if ((addr->return_file = open_msglog_file(addr->return_filename, 0400, &error)) < 0)
{
common_error(TRUE, addr, errno, US"Unable to %s file for %s transport "
|| (ret = write(pfd[pipe_write], &addr2->flags, sizeof(addr2->flags))) != sizeof(addr2->flags)
|| (ret = write(pfd[pipe_write], &addr2->basic_errno, sizeof(int))) != sizeof(int)
|| (ret = write(pfd[pipe_write], &addr2->more_errno, sizeof(int))) != sizeof(int)
+ || (ret = write(pfd[pipe_write], &addr2->delivery_usec, sizeof(int))) != sizeof(int)
|| (ret = write(pfd[pipe_write], &addr2->special_action, sizeof(int))) != sizeof(int)
|| (ret = write(pfd[pipe_write], &addr2->transport,
sizeof(transport_instance *))) != sizeof(transport_instance *)
len = read(pfd[pipe_read], &addr2->flags, sizeof(addr2->flags));
len = read(pfd[pipe_read], &addr2->basic_errno, sizeof(int));
len = read(pfd[pipe_read], &addr2->more_errno, sizeof(int));
+ len = read(pfd[pipe_read], &addr2->delivery_usec, sizeof(int));
len = read(pfd[pipe_read], &addr2->special_action, sizeof(int));
len = read(pfd[pipe_read], &addr2->transport,
sizeof(transport_instance *));
next = addr->next;
addr->message = US"concurrency limit reached for transport";
addr->basic_errno = ERRNO_TRETRY;
- post_process_one(addr, DEFER, LOG_MAIN, DTYPE_TRANSPORT, 0);
+ post_process_one(addr, DEFER, LOG_MAIN, EXIM_DTYPE_TRANSPORT, 0);
} while ((addr = next));
return TRUE;
}
while (addr_local)
{
- time_t delivery_start;
- int deliver_time;
+ struct timeval delivery_start;
+ struct timeval deliver_time;
address_item *addr2, *addr3, *nextaddr;
int logflags = LOG_MAIN;
int logchar = dont_deliver? '*' : '=';
addr->message = addr->router
? string_sprintf("No transport set by %s router", addr->router->name)
: string_sprintf("No transport set by system filter");
- post_process_one(addr, DEFER, logflags, DTYPE_TRANSPORT, 0);
+ post_process_one(addr, DEFER, logflags, EXIM_DTYPE_TRANSPORT, 0);
continue;
}
BOOL ok =
tp == next->transport
&& !previously_transported(next, TRUE)
- && (addr->flags & (af_pfr|af_file)) == (next->flags & (af_pfr|af_file))
+ && testflag(addr, af_pfr) == testflag(next, af_pfr)
+ && testflag(addr, af_file) == testflag(next, af_file)
&& (!uses_lp || Ustrcmp(next->local_part, addr->local_part) == 0)
&& (!uses_dom || Ustrcmp(next->domain, addr->domain) == 0)
&& same_strings(next->prop.errors_address, addr->prop.errors_address)
while (addr)
{
addr2 = addr->next;
- post_process_one(addr, rc, logflags, DTYPE_TRANSPORT, 0);
+ post_process_one(addr, rc, logflags, EXIM_DTYPE_TRANSPORT, 0);
addr = addr2;
}
continue; /* With next batch of addresses */
this->basic_errno = ERRNO_LRETRY;
addr2 = addr3 ? (addr3->next = addr2->next)
: (addr = addr2->next);
- post_process_one(this, DEFER, logflags, DTYPE_TRANSPORT, 0);
+ post_process_one(this, DEFER, logflags, EXIM_DTYPE_TRANSPORT, 0);
}
}
do
{
addr = addr->next;
- post_process_one(addr, DEFER, logflags, DTYPE_TRANSPORT, 0);
+ post_process_one(addr, DEFER, logflags, EXIM_DTYPE_TRANSPORT, 0);
} while ((addr = addr2));
}
continue; /* Loop for the next set of addresses. */
single delivery. */
deliver_set_expansions(addr);
- delivery_start = time(NULL);
+
+ gettimeofday(&delivery_start, NULL);
deliver_local(addr, FALSE);
- deliver_time = (int)(time(NULL) - delivery_start);
+ timesince(&deliver_time, &delivery_start);
/* If a shadow transport (which must perforce be another local transport), is
defined, and its condition is met, we must pass the message to the shadow
/* Done with this address */
- if (result == OK) addr2->more_errno = deliver_time;
- post_process_one(addr2, result, logflags, DTYPE_TRANSPORT, logchar);
+ if (result == OK)
+ {
+ addr2->more_errno = deliver_time.tv_sec;
+ addr2->delivery_usec = deliver_time.tv_usec;
+ }
+ post_process_one(addr2, result, logflags, EXIM_DTYPE_TRANSPORT, logchar);
/* If a pipe delivery generated text to be sent back, the result may be
changed to FAIL, and we must copy this for subsequent addresses in the
often bigger) so even if we are reading while the subprocess is still going, we
should never have only a partial item in the buffer.
+hs12: This assumption is not true anymore, since we got quit large items (certificate
+information and such)
+
Argument:
poffset the offset of the parlist item
eop TRUE if the process has completed
address_item *addr = p->addr;
pid_t pid = p->pid;
int fd = p->fd;
-uschar *endptr = big_buffer;
-uschar *ptr = endptr;
+
uschar *msg = p->msg;
BOOL done = p->done;
-BOOL finished = FALSE;
-/* minimum size to read is header size including id, subid and length */
-int required = PIPE_HEADER_SIZE;
/* Loop through all items, reading from the pipe when necessary. The pipe
-is set up to be non-blocking, but there are two different Unix mechanisms in
-use. Exim uses O_NONBLOCK if it is defined. This returns 0 for end of file,
-and EAGAIN for no more data. If O_NONBLOCK is not defined, Exim uses O_NDELAY,
-which returns 0 for both end of file and no more data. We distinguish the
-two cases by taking 0 as end of file only when we know the process has
-completed.
-
-Each separate item is written to the pipe in a single write(), and as they are
-all short items, the writes will all be atomic and we should never find
-ourselves in the position of having read an incomplete item. "Short" in this
-case can mean up to about 1K in the case when there is a long error message
-associated with an address. */
-
-DEBUG(D_deliver) debug_printf("reading pipe for subprocess %d (%s)\n",
- (int)p->pid, eop? "ended" : "not ended");
+used to be non-blocking. But I do not see a reason for using non-blocking I/O
+here, as the preceding select() tells us, if data is available for reading.
-while (!done)
- {
- retry_item *r, **rp;
- int remaining = endptr - ptr;
- uschar header[PIPE_HEADER_SIZE + 1];
- uschar id, subid;
- uschar *endc;
+A read() on a "selected" handle should never block, but(!) it may return
+less data then we expected. (The buffer size we pass to read() shouldn't be
+understood as a "request", but as a "limit".)
- /* Read (first time) or top up the chars in the buffer if necessary.
- There will be only one read if we get all the available data (i.e. don't
- fill the buffer completely). */
+Each separate item is written to the pipe in a timely manner. But, especially for
+larger items, the read(2) may already return partial data from the write(2).
- if (remaining < required && !finished)
- {
- int len;
- int available = big_buffer_size - remaining;
+The write is atomic mostly (depending on the amount written), but atomic does
+not imply "all or noting", it just is "not intermixed" with other writes on the
+same channel (pipe).
- if (remaining > 0) memmove(big_buffer, ptr, remaining);
-
- ptr = big_buffer;
- endptr = big_buffer + remaining;
- len = read(fd, endptr, available);
-
- DEBUG(D_deliver) debug_printf("read() yielded %d\n", len);
-
- /* If the result is EAGAIN and the process is not complete, just
- stop reading any more and process what we have already. */
-
- if (len < 0)
- {
- if (!eop && errno == EAGAIN) len = 0; else
- {
- msg = string_sprintf("failed to read pipe from transport process "
- "%d for transport %s: %s", pid, addr->transport->driver_name,
- strerror(errno));
- break;
- }
- }
-
- /* If the length is zero (eof or no-more-data), just process what we
- already have. Note that if the process is still running and we have
- read all the data in the pipe (but less that "available") then we
- won't read any more, as "finished" will get set. */
-
- endptr += len;
- remaining += len;
- finished = len != available;
- }
+*/
- /* If we are at the end of the available data, exit the loop. */
- if (ptr >= endptr) break;
+DEBUG(D_deliver) debug_printf("reading pipe for subprocess %d (%s)\n",
+ (int)p->pid, eop? "ended" : "not ended yet");
- /* copy and read header */
- memcpy(header, ptr, PIPE_HEADER_SIZE);
- header[PIPE_HEADER_SIZE] = '\0';
- id = header[0];
- subid = header[1];
- required = Ustrtol(header + 2, &endc, 10) + PIPE_HEADER_SIZE; /* header + data */
- if (*endc)
- {
- msg = string_sprintf("failed to read pipe from transport process "
- "%d for transport %s: error reading size from header", pid, addr->transport->driver_name);
+while (!done)
+ {
+ retry_item *r, **rp;
+ uschar pipeheader[PIPE_HEADER_SIZE+1];
+ uschar *id = &pipeheader[0];
+ uschar *subid = &pipeheader[1];
+ uschar *ptr = big_buffer;
+ size_t required = PIPE_HEADER_SIZE; /* first the pipehaeder, later the data */
+ ssize_t got;
+
+ DEBUG(D_deliver) debug_printf(
+ "expect %lu bytes (pipeheader) from tpt process %d\n", (u_long)required, pid);
+
+ /* We require(!) all the PIPE_HEADER_SIZE bytes here, as we know,
+ they're written in a timely manner, so waiting for the write shouldn't hurt a lot.
+ If we get less, we can assume the subprocess do be done and do not expect any further
+ information from it. */
+
+ if ((got = readn(fd, pipeheader, required)) != required)
+ {
+ msg = string_sprintf("got " SSIZE_T_FMT " of %d bytes (pipeheader) "
+ "from transport process %d for transport %s",
+ got, PIPE_HEADER_SIZE, pid, addr->transport->driver_name);
done = TRUE;
break;
}
+ pipeheader[PIPE_HEADER_SIZE] = '\0';
DEBUG(D_deliver)
- debug_printf("header read id:%c,subid:%c,size:%s,required:%d,remaining:%d,finished:%d\n",
- id, subid, header+2, required, remaining, finished);
+ debug_printf("got %ld bytes (pipeheader) from transport process %d\n",
+ (long) got, pid);
- /* is there room for the dataset we want to read ? */
- if (required > big_buffer_size - PIPE_HEADER_SIZE)
+ {
+ /* If we can't decode the pipeheader, the subprocess seems to have a
+ problem, we do not expect any furher information from it. */
+ char *endc;
+ required = Ustrtol(pipeheader+2, &endc, 10);
+ if (*endc)
{
- msg = string_sprintf("failed to read pipe from transport process "
- "%d for transport %s: big_buffer too small! required size=%d buffer size=%d", pid, addr->transport->driver_name,
- required, big_buffer_size - PIPE_HEADER_SIZE);
+ msg = string_sprintf("failed to read pipe "
+ "from transport process %d for transport %s: error decoding size from header",
+ pid, addr->transport->driver_name);
done = TRUE;
break;
}
+ }
- /* We wrote all datasets with atomic write() calls. Remaining < required only
- happens if big_buffer was too small to get all available data from pipe;
- finished has to be false as well. */
+ DEBUG(D_deliver)
+ debug_printf("expect %lu bytes (pipedata) from transport process %d\n",
+ (u_long)required, pid);
- if (remaining < required)
+ /* Same as above, the transport process will write the bytes announced
+ in a timely manner, so we can just wait for the bytes, getting less than expected
+ is considered a problem of the subprocess, we do not expect anything else from it. */
+ if ((got = readn(fd, big_buffer, required)) != required)
{
- if (!finished)
- continue;
- msg = string_sprintf("failed to read pipe from transport process "
- "%d for transport %s: required size=%d > remaining size=%d and finished=true",
- pid, addr->transport->driver_name, required, remaining);
+ msg = string_sprintf("got only " SSIZE_T_FMT " of " SIZE_T_FMT
+ " bytes (pipedata) from transport process %d for transport %s",
+ got, required, pid, addr->transport->driver_name);
done = TRUE;
break;
}
- /* Step past the header */
- ptr += PIPE_HEADER_SIZE;
-
/* Handle each possible type of item, assuming the complete item is
available in store. */
- switch (id)
+ switch (*id)
{
/* Host items exist only if any hosts were marked unusable. Match
up by checking the IP address. */
r->flags = *ptr++;
r->key = string_copy(ptr);
while (*ptr++);
- memcpy(&(r->basic_errno), ptr, sizeof(r->basic_errno));
+ memcpy(&r->basic_errno, ptr, sizeof(r->basic_errno));
ptr += sizeof(r->basic_errno);
- memcpy(&(r->more_errno), ptr, sizeof(r->more_errno));
+ memcpy(&r->more_errno, ptr, sizeof(r->more_errno));
ptr += sizeof(r->more_errno);
r->message = *ptr ? string_copy(ptr) : NULL;
DEBUG(D_deliver|D_retry) debug_printf(" added %s item\n",
#ifdef SUPPORT_TLS
case 'X':
if (!addr) goto ADDR_MISMATCH; /* Below, in 'A' handler */
- switch (subid)
+ switch (*subid)
{
case '1':
addr->cipher = NULL;
#endif /*SUPPORT_TLS*/
case 'C': /* client authenticator information */
- switch (subid)
+ switch (*subid)
{
case '1': addr->authenticator = *ptr ? string_copy(ptr) : NULL; break;
case '2': addr->auth_id = *ptr ? string_copy(ptr) : NULL; break;
#ifndef DISABLE_PRDR
case 'P':
- addr->flags |= af_prdr_used;
+ setflag(addr, af_prdr_used);
break;
#endif
case 'K':
- addr->flags |= af_chunking_used;
+ setflag(addr, af_chunking_used);
+ break;
+
+ case 'T':
+ setflag(addr, af_tcp_fastopen_conn);
+ if (*subid > '0') setflag(addr, af_tcp_fastopen);
break;
case 'D':
break;
}
- switch (subid)
+ switch (*subid)
{
#ifdef SUPPORT_SOCKS
case '2': /* proxy information; must arrive before A0 and applies to that addr XXX oops*/
DEBUG(D_deliver) debug_printf("A0 %s tret %d\n", addr->address, *ptr);
addr->transport_return = *ptr++;
addr->special_action = *ptr++;
- memcpy(&(addr->basic_errno), ptr, sizeof(addr->basic_errno));
+ memcpy(&addr->basic_errno, ptr, sizeof(addr->basic_errno));
ptr += sizeof(addr->basic_errno);
- memcpy(&(addr->more_errno), ptr, sizeof(addr->more_errno));
+ memcpy(&addr->more_errno, ptr, sizeof(addr->more_errno));
ptr += sizeof(addr->more_errno);
- memcpy(&(addr->flags), ptr, sizeof(addr->flags));
+ memcpy(&addr->delivery_usec, ptr, sizeof(addr->delivery_usec));
+ ptr += sizeof(addr->delivery_usec);
+ memcpy(&addr->flags, ptr, sizeof(addr->flags));
ptr += sizeof(addr->flags);
addr->message = *ptr ? string_copy(ptr) : NULL;
while(*ptr++);
p->done = done;
/* If the process hadn't finished, and we haven't seen the end of the data
-or suffered a disaster, update the rest of the state, and return FALSE to
+or if we suffered a disaster, update the rest of the state, and return FALSE to
indicate "not finished". */
if (!eop && !done)
addr->transport_return = DEFER;
addr->special_action = SPECIAL_FREEZE;
addr->message = msg;
+ log_write(0, LOG_MAIN|LOG_PANIC, "Delivery status for %s: %s\n", addr->address, addr->message);
}
/* Return TRUE to indicate we have got all we need from this process, even
addr->transport_return = DEFER;
}
(void)post_process_one(addr, addr->transport_return, logflags,
- DTYPE_TRANSPORT, addr->special_action);
+ EXIM_DTYPE_TRANSPORT, addr->special_action);
}
/* Next address */
{
readycount--;
if (par_read_pipe(poffset, FALSE)) /* Finished with this pipe */
- {
for (;;) /* Loop for signals */
{
pid_t endedpid = waitpid(pid, &status, 0);
"%d (errno = %d) from waitpid() for process %d",
(int)endedpid, errno, (int)pid);
}
- }
}
}
}
}
-
-
-
static void
-rmt_dlv_checked_write(int fd, char id, char subid, void * buf, int size)
+rmt_dlv_checked_write(int fd, char id, char subid, void * buf, ssize_t size)
{
-uschar writebuffer[PIPE_HEADER_SIZE + BIG_BUFFER_SIZE];
-int header_length;
-int ret;
+uschar pipe_header[PIPE_HEADER_SIZE+1];
+size_t total_len = PIPE_HEADER_SIZE + size;
+
+struct iovec iov[2] = {
+ { pipe_header, PIPE_HEADER_SIZE }, /* indication about the data to expect */
+ { buf, size } /* *the* data */
+};
+
+ssize_t ret;
/* we assume that size can't get larger then BIG_BUFFER_SIZE which currently is set to 16k */
/* complain to log if someone tries with buffer sizes we can't handle*/
-if (size > 99999)
+if (size > BIG_BUFFER_SIZE-1)
{
log_write(0, LOG_MAIN|LOG_PANIC_DIE,
- "Failed writing transport result to pipe: can't handle buffers > 99999 bytes. truncating!\n");
- size = 99999;
+ "Failed writing transport result to pipe: can't handle buffers > %d bytes. truncating!\n",
+ BIG_BUFFER_SIZE-1);
+ size = BIG_BUFFER_SIZE;
}
-/* to keep the write() atomic we build header in writebuffer and copy buf behind */
-/* two write() calls would increase the complexity of reading from pipe */
+/* Should we check that we do not write more than PIPE_BUF? What would
+that help? */
/* convert size to human readable string prepended by id and subid */
-header_length = snprintf(CS writebuffer, PIPE_HEADER_SIZE+1, "%c%c%05d", id, subid, size);
-if (header_length != PIPE_HEADER_SIZE)
- {
+if (PIPE_HEADER_SIZE != snprintf(CS pipe_header, PIPE_HEADER_SIZE+1, "%c%c%05ld",
+ id, subid, (long)size))
log_write(0, LOG_MAIN|LOG_PANIC_DIE, "header snprintf failed\n");
- writebuffer[0] = '\0';
- }
-DEBUG(D_deliver) debug_printf("header write id:%c,subid:%c,size:%d,final:%s\n",
- id, subid, size, writebuffer);
+DEBUG(D_deliver) debug_printf("header write id:%c,subid:%c,size:%ld,final:%s\n",
+ id, subid, (long)size, pipe_header);
-if (buf && size > 0)
- memcpy(writebuffer + PIPE_HEADER_SIZE, buf, size);
-
-size += PIPE_HEADER_SIZE;
-if ((ret = write(fd, writebuffer, size)) != size)
- log_write(0, LOG_MAIN|LOG_PANIC_DIE, "Failed writing transport result to pipe: %s\n",
- ret == -1 ? strerror(errno) : "short write");
+if ((ret = writev(fd, iov, 2)) != total_len)
+ log_write(0, LOG_MAIN|LOG_PANIC_DIE,
+ "Failed writing transport result to pipe (%ld of %ld bytes): %s",
+ (long)ret, (long)total_len, ret == -1 ? strerror(errno) : "short write");
}
/*************************************************
if (continue_transport)
{
BOOL ok = Ustrcmp(continue_transport, tp->name) == 0;
- if (ok && addr->host_list)
+
+ /* If the transport is about to override the host list do not check
+ it here but take the cost of running the transport process to discover
+ if the continued_hostname connection is suitable. This is a layering
+ violation which is unfortunate as it requires we haul in the smtp
+ include file. */
+
+ if (ok)
{
- host_item *h;
- ok = FALSE;
- for (h = addr->host_list; h; h = h->next)
- if (Ustrcmp(h->name, continue_hostname) == 0)
-/*XXX should also check port here */
- { ok = TRUE; break; }
+ smtp_transport_options_block * ob;
+
+ if ( !( Ustrcmp(tp->info->driver_name, "smtp") == 0
+ && (ob = (smtp_transport_options_block *)tp->options_block)
+ && ob->hosts_override && ob->hosts
+ )
+ && addr->host_list
+ )
+ {
+ host_item * h;
+ ok = FALSE;
+ for (h = addr->host_list; h; h = h->next)
+ if (Ustrcmp(h->name, continue_hostname) == 0)
+ /*XXX should also check port here */
+ { ok = TRUE; break; }
+ }
}
/* Addresses not suitable; defer or queue for fallback hosts (which
that it can use either of them, though it prefers O_NONBLOCK, which
distinguishes between EOF and no-more-data. */
+/* The data appears in a timely manner and we already did a select on
+all pipes, so I do not see a reason to use non-blocking IO here
+
#ifdef O_NONBLOCK
(void)fcntl(pfd[pipe_read], F_SETFL, O_NONBLOCK);
#else
(void)fcntl(pfd[pipe_read], F_SETFL, O_NDELAY);
#endif
+*/
/* If the maximum number of subprocesses already exist, wait for a process
to finish. If we ran out of file descriptors, parmax will have been reduced
search_tidyup();
+
if ((pid = fork()) == 0)
{
int fd = pfd[pipe_write];
host_item *h;
+ DEBUG(D_deliver) debug_selector |= D_pid; // hs12
/* Setting this global in the subprocess means we need never clear it */
transport_name = tp->name;
}
#ifndef DISABLE_PRDR
- if (addr->flags & af_prdr_used)
+ if (testflag(addr, af_prdr_used))
rmt_dlv_checked_write(fd, 'P', '0', NULL, 0);
#endif
- if (addr->flags & af_chunking_used)
+ if (testflag(addr, af_chunking_used))
rmt_dlv_checked_write(fd, 'K', '0', NULL, 0);
+ if (testflag(addr, af_tcp_fastopen_conn))
+ rmt_dlv_checked_write(fd, 'T',
+ testflag(addr, af_tcp_fastopen) ? '1' : '0', NULL, 0);
+
memcpy(big_buffer, &addr->dsn_aware, sizeof(addr->dsn_aware));
rmt_dlv_checked_write(fd, 'D', '0', big_buffer, sizeof(addr->dsn_aware));
{
sprintf(CS big_buffer, "%c%.500s", r->flags, r->key);
ptr = big_buffer + Ustrlen(big_buffer+2) + 3;
- memcpy(ptr, &(r->basic_errno), sizeof(r->basic_errno));
+ memcpy(ptr, &r->basic_errno, sizeof(r->basic_errno));
ptr += sizeof(r->basic_errno);
- memcpy(ptr, &(r->more_errno), sizeof(r->more_errno));
+ memcpy(ptr, &r->more_errno, sizeof(r->more_errno));
ptr += sizeof(r->more_errno);
if (!r->message) *ptr++ = 0; else
{
sprintf(CS big_buffer, "%c%c", addr->transport_return, addr->special_action);
ptr = big_buffer + 2;
- memcpy(ptr, &(addr->basic_errno), sizeof(addr->basic_errno));
+ memcpy(ptr, &addr->basic_errno, sizeof(addr->basic_errno));
ptr += sizeof(addr->basic_errno);
- memcpy(ptr, &(addr->more_errno), sizeof(addr->more_errno));
+ memcpy(ptr, &addr->more_errno, sizeof(addr->more_errno));
ptr += sizeof(addr->more_errno);
- memcpy(ptr, &(addr->flags), sizeof(addr->flags));
+ memcpy(ptr, &addr->delivery_usec, sizeof(addr->delivery_usec));
+ ptr += sizeof(addr->delivery_usec);
+ memcpy(ptr, &addr->flags, sizeof(addr->flags));
ptr += sizeof(addr->flags);
if (!addr->message) *ptr++ = 0; else
{
ptr += sprintf(CS ptr, "%.256s", addr->host_used->name) + 1;
ptr += sprintf(CS ptr, "%.64s", addr->host_used->address) + 1;
- memcpy(ptr, &(addr->host_used->port), sizeof(addr->host_used->port));
+ memcpy(ptr, &addr->host_used->port, sizeof(addr->host_used->port));
ptr += sizeof(addr->host_used->port);
/* DNS lookup status */
/* Fork failed; defer with error message */
- if (pid < 0)
+ if (pid == -1)
{
(void)close(pfd[pipe_read]);
panicmsg = string_sprintf("fork failed for remote delivery to %s: %s",
static uschar *
next_emf(FILE *f, uschar *which)
{
-int size = 256;
-int ptr = 0;
-uschar *para, *yield;
+uschar *yield;
+gstring * para;
uschar buffer[256];
if (!f) return NULL;
if (!Ufgets(buffer, sizeof(buffer), f) || Ustrcmp(buffer, "****\n") == 0)
return NULL;
-para = store_get(size);
+para = string_get(256);
for (;;)
{
- para = string_cat(para, &size, &ptr, buffer);
+ para = string_cat(para, buffer);
if (!Ufgets(buffer, sizeof(buffer), f) || Ustrcmp(buffer, "****\n") == 0)
break;
}
-para[ptr] = 0;
-
-if ((yield = expand_string(para)))
+if ((yield = expand_string(string_from_gstring(para))))
return yield;
log_write(0, LOG_MAIN|LOG_PANIC, "Failed to expand string from "
if (rc != spool_read_hdrerror)
{
- received_time = 0;
+ received_time.tv_sec = received_time.tv_usec = 0;
+ /*XXX subsec precision?*/
for (i = 0; i < 6; i++)
- received_time = received_time * BASE_62 + tab62[id[i] - '0'];
+ received_time.tv_sec = received_time.tv_sec * BASE_62 + tab62[id[i] - '0'];
}
/* If we've had this malformed message too long, sling it. */
- if (now - received_time > keep_malformed)
+ if (now - received_time.tv_sec > keep_malformed)
{
Uunlink(spool_fname(US"msglog", message_subdir, id, US""));
Uunlink(spool_fname(US"input", message_subdir, id, US"-D"));
uschar *type;
p->uid = uid;
p->gid = gid;
- setflag(p, af_uid_set |
- af_gid_set |
- af_allow_file |
- af_allow_pipe |
- af_allow_reply);
+ setflag(p, af_uid_set);
+ setflag(p, af_gid_set);
+ setflag(p, af_allow_file);
+ setflag(p, af_allow_pipe);
+ setflag(p, af_allow_reply);
/* Find the name of the system filter's appropriate pfr transport */
p = p->next;
if (!addr_last) addr_new = p; else addr_last->next = p;
badp->local_part = badp->address; /* Needed for log line */
- post_process_one(badp, DEFER, LOG_MAIN|LOG_PANIC, DTYPE_ROUTER, 0);
+ post_process_one(badp, DEFER, LOG_MAIN|LOG_PANIC, EXIM_DTYPE_ROUTER, 0);
continue;
}
} /* End of pfr handling */
new->dsn_flags = r->dsn_flags & rf_dsnflags;
new->dsn_orcpt = r->orcpt;
DEBUG(D_deliver) debug_printf("DSN: set orcpt: %s flags: %d\n",
- new->dsn_orcpt, new->dsn_flags);
+ new->dsn_orcpt ? new->dsn_orcpt : US"", new->dsn_flags);
switch (process_recipients)
{
case RECIP_FAIL_LOOP:
new->message = US"Too many \"Received\" headers - suspected mail loop";
- post_process_one(new, FAIL, LOG_MAIN, DTYPE_ROUTER, 0);
+ post_process_one(new, FAIL, LOG_MAIN, EXIM_DTYPE_ROUTER, 0);
break;
addr->local_part = addr->address;
addr->message =
US"filter autoreply generated syntactically invalid recipient";
- setflag(addr, af_ignore_error);
- (void)post_process_one(addr, FAIL, LOG_MAIN, DTYPE_ROUTER, 0);
+ addr->prop.ignore_error = TRUE;
+ (void) post_process_one(addr, FAIL, LOG_MAIN, EXIM_DTYPE_ROUTER, 0);
continue; /* with the next new address */
}
{
addr->basic_errno = ERRNO_FORBIDFILE;
addr->message = US"delivery to file forbidden";
- (void)post_process_one(addr, FAIL, LOG_MAIN, DTYPE_ROUTER, 0);
+ (void)post_process_one(addr, FAIL, LOG_MAIN, EXIM_DTYPE_ROUTER, 0);
continue; /* with the next new address */
}
}
{
addr->basic_errno = ERRNO_FORBIDPIPE;
addr->message = US"delivery to pipe forbidden";
- (void)post_process_one(addr, FAIL, LOG_MAIN, DTYPE_ROUTER, 0);
+ (void)post_process_one(addr, FAIL, LOG_MAIN, EXIM_DTYPE_ROUTER, 0);
continue; /* with the next new address */
}
}
{
addr->basic_errno = ERRNO_FORBIDREPLY;
addr->message = US"autoreply forbidden";
- (void)post_process_one(addr, FAIL, LOG_MAIN, DTYPE_ROUTER, 0);
+ (void)post_process_one(addr, FAIL, LOG_MAIN, EXIM_DTYPE_ROUTER, 0);
continue; /* with the next new address */
}
if (addr->basic_errno == ERRNO_BADTRANSPORT)
{
- (void)post_process_one(addr, DEFER, LOG_MAIN, DTYPE_ROUTER, 0);
+ (void)post_process_one(addr, DEFER, LOG_MAIN, EXIM_DTYPE_ROUTER, 0);
continue;
}
{
uschar *save = addr->transport->name;
addr->transport->name = US"**bypassed**";
- (void)post_process_one(addr, OK, LOG_MAIN, DTYPE_TRANSPORT, '=');
+ (void)post_process_one(addr, OK, LOG_MAIN, EXIM_DTYPE_TRANSPORT, '=');
addr->transport->name = save;
continue; /* with the next new address */
}
{
addr->message = US"cannot check percent_hack_domains";
addr->basic_errno = ERRNO_LISTDEFER;
- (void)post_process_one(addr, DEFER, LOG_MAIN, DTYPE_NONE, 0);
+ (void)post_process_one(addr, DEFER, LOG_MAIN, EXIM_DTYPE_NONE, 0);
continue;
}
addr->message = US"domain is held";
addr->basic_errno = ERRNO_HELD;
}
- (void)post_process_one(addr, DEFER, LOG_MAIN, DTYPE_NONE, 0);
+ (void)post_process_one(addr, DEFER, LOG_MAIN, EXIM_DTYPE_NONE, 0);
continue;
}
{
addr->message = US"reusing SMTP connection skips previous routing defer";
addr->basic_errno = ERRNO_RRETRY;
- (void)post_process_one(addr, DEFER, LOG_MAIN, DTYPE_ROUTER, 0);
+ (void)post_process_one(addr, DEFER, LOG_MAIN, EXIM_DTYPE_ROUTER, 0);
}
/* If we are in a queue run, defer routing unless there is no retry data or
{
addr->message = US"retry time not reached";
addr->basic_errno = ERRNO_RRETRY;
- (void)post_process_one(addr, DEFER, LOG_MAIN, DTYPE_ROUTER, 0);
+ (void)post_process_one(addr, DEFER, LOG_MAIN, EXIM_DTYPE_ROUTER, 0);
}
/* The domain is OK for routing. Remember if retry data exists so it
{
addr->basic_errno = ERRNO_LISTDEFER;
addr->message = US"queue_domains lookup deferred";
- (void)post_process_one(addr, DEFER, LOG_MAIN, DTYPE_ROUTER, 0);
+ (void)post_process_one(addr, DEFER, LOG_MAIN, EXIM_DTYPE_ROUTER, 0);
}
else
{
{
addr->basic_errno = ERRNO_QUEUE_DOMAIN;
addr->message = US"domain is in queue_domains";
- (void)post_process_one(addr, DEFER, LOG_MAIN, DTYPE_ROUTER, 0);
+ (void)post_process_one(addr, DEFER, LOG_MAIN, EXIM_DTYPE_ROUTER, 0);
}
}
if (rc != OK)
{
- (void)post_process_one(addr, rc, LOG_MAIN, DTYPE_ROUTER, 0);
+ (void)post_process_one(addr, rc, LOG_MAIN, EXIM_DTYPE_ROUTER, 0);
continue; /* route next address */
}
addr2->host_list = addr->host_list;
addr2->fallback_hosts = addr->fallback_hosts;
addr2->prop.errors_address = addr->prop.errors_address;
- copyflag(addr2, addr, af_hide_child | af_local_host_removed);
+ copyflag(addr2, addr, af_hide_child);
+ copyflag(addr2, addr, af_local_host_removed);
DEBUG(D_deliver|D_route)
- {
debug_printf(">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>\n"
"routing %s\n"
"Routing for %s copied from %s\n",
addr2->address, addr2->address, addr->address);
- }
}
}
} /* Continue with routing the next address. */
if (journal_fd < 0)
{
uschar * fname = spool_fname(US"input", message_subdir, id, US"-J");
-
+
if ((journal_fd = Uopen(fname,
#ifdef O_CLOEXEC
O_CLOEXEC |
addr->next = NULL;
addr->basic_errno = ERRNO_LOCAL_ONLY;
addr->message = US"remote deliveries suppressed";
- (void)post_process_one(addr, DEFER, LOG_MAIN, DTYPE_TRANSPORT, 0);
+ (void)post_process_one(addr, DEFER, LOG_MAIN, EXIM_DTYPE_TRANSPORT, 0);
}
/* Handle remote deliveries */
DEBUG(D_deliver)
debug_printf(">>>>>>>>>>>>>>>> deliveries are done >>>>>>>>>>>>>>>>\n");
-cancel_cutthrough_connection(TRUE, "deliveries are done");
+cancel_cutthrough_connection(TRUE, US"deliveries are done");
/* Root privilege is no longer needed */
"DSN: envid: %s ret: %d\n"
"DSN: Final recipient: %s\n"
"DSN: Remote SMTP server supports DSN: %d\n",
- addr_dsntmp->router->name,
+ addr_dsntmp->router ? addr_dsntmp->router->name : US"(unknown)",
addr_dsntmp->address,
sender_address,
- addr_dsntmp->dsn_orcpt, addr_dsntmp->dsn_flags,
- dsn_envid, dsn_ret,
+ addr_dsntmp->dsn_orcpt ? addr_dsntmp->dsn_orcpt : US"NULL",
+ addr_dsntmp->dsn_flags,
+ dsn_envid ? dsn_envid : US"NULL", dsn_ret,
addr_dsntmp->address,
addr_dsntmp->dsn_aware
);
FILE *f = fdopen(fd, "wb");
/* header only as required by RFC. only failure DSN needs to honor RET=FULL */
uschar * bound;
- transport_ctx tctx = {0};
+ transport_ctx tctx = {{0}};
DEBUG(D_deliver)
debug_printf("sending error message to: %s\n", sender_address);
if (sender_address[0] == 0 && !addr_failed->prop.errors_address)
{
if ( !testflag(addr_failed, af_retry_timedout)
- && !testflag(addr_failed, af_ignore_error))
- {
+ && !addr_failed->prop.ignore_error)
log_write(0, LOG_MAIN|LOG_PANIC, "internal error: bounce message "
"failure is neither frozen nor ignored (it's been ignored)");
- }
- setflag(addr_failed, af_ignore_error);
+
+ addr_failed->prop.ignore_error = TRUE;
}
/* If the first address on the list has af_ignore_error set, just remove
it from the list, throw away any saved message file, log it, and
mark the recipient done. */
- if ( testflag(addr_failed, af_ignore_error)
+ if ( addr_failed->prop.ignore_error
|| ( addr_failed->dsn_flags & rf_dsnflags
&& (addr_failed->dsn_flags & rf_notify_failure) != rf_notify_failure
) )
transport_filter_argv = NULL; /* Just in case */
return_path = sender_address; /* In case not previously set */
{ /* Dummy transport for headers add */
- transport_ctx tctx = {0};
+ transport_ctx tctx = {{0}};
transport_instance tb = {0};
tctx.u.fd = fileno(f);
if (rc != 0)
{
uschar *s = US"";
- if (now - received_time < retry_maximum_timeout && !addr_defer)
+ if (now - received_time.tv_sec < retry_maximum_timeout && !addr_defer)
{
addr_defer = (address_item *)(+1);
deliver_freeze = TRUE;
/* Log the end of this message, with queue time if requested. */
if (LOGGING(queue_time_overall))
- log_write(0, LOG_MAIN, "Completed QT=%s",
- readconf_printtime( (int) ((long)time(NULL) - (long)received_time)) );
+ log_write(0, LOG_MAIN, "Completed QT=%s", string_timesince(&received_time));
else
log_write(0, LOG_MAIN, "Completed");
{
int count;
int show_time;
- int queue_time = time(NULL) - received_time;
+ int queue_time = time(NULL) - received_time.tv_sec;
/* When running in the test harness, there's an option that allows us to
fudge this time so as to get repeatability of the tests. Take the first
FILE *wmf = NULL;
FILE *f = fdopen(fd, "wb");
uschar * bound;
- transport_ctx tctx = {0};
+ transport_ctx tctx = {{0}};
if (warn_message_file)
if (!(wmf = Ufopen(warn_message_file, "rb")))
void
deliver_init(void)
{
+#ifdef EXIM_TFO_PROBE
+tfo_probe();
+#else
+tcp_fastopen_ok = TRUE;
+#endif
+
+
if (!regex_PIPELINING) regex_PIPELINING =
regex_must_compile(US"\\n250[\\s\\-]PIPELINING(\\s|\\n|$)", FALSE, TRUE);
void
delivery_re_exec(int exec_type)
{
-uschar * s;
+uschar * where;
if (cutthrough.fd >= 0 && cutthrough.callout_hold_only)
{
#ifdef SUPPORT_TLS
if (cutthrough.is_tls)
{
- smtp_peer_options |= PEER_OFFERED_TLS;
+ smtp_peer_options |= OPTION_TLS;
sending_ip_address = cutthrough.snd_ip;
sending_port = cutthrough.snd_port;
- s = US"socketpair";
+ where = US"socketpair";
if (socketpair(AF_UNIX, SOCK_STREAM, 0, pfd) != 0)
goto fail;
- s = US"fork";
+ where = US"fork";
if ((pid = fork()) < 0)
goto fail;
- else if (pid == 0) /* child: fork again to totally dosconnect */
+ else if (pid == 0) /* child: fork again to totally disconnect */
{
- close(pfd[1]);
- if ((pid = fork()))
- _exit(pid ? EXIT_FAILURE : EXIT_SUCCESS);
- smtp_proxy_tls(big_buffer, big_buffer_size, pfd[0], 5*60);
- exim_exit(0);
+ if (running_in_test_harness) millisleep(100); /* let parent debug out */
+ /* does not return */
+ smtp_proxy_tls(big_buffer, big_buffer_size, pfd, 5*60);
}
+ DEBUG(D_transport) debug_printf("proxy-proc inter-pid %d\n", pid);
close(pfd[0]);
waitpid(pid, NULL, 0);
(void) close(channel_fd); /* release the client socket */
}
else
{
- cancel_cutthrough_connection(TRUE, "non-continued delivery");
+ cancel_cutthrough_connection(TRUE, US"non-continued delivery");
(void) child_exec_exim(exec_type, FALSE, NULL, FALSE, 2, US"-Mc", message_id);
}
-/* Control does not return here. */
+return; /* compiler quietening; control does not reach here. */
fail:
log_write(0,
LOG_MAIN | (exec_type == CEE_EXEC_EXIT ? LOG_PANIC : LOG_PANIC_DIE),
- "delivery re-exec failed: %s", strerror(errno));
+ "delivery re-exec %s failed: %s", where, strerror(errno));
/* Get here if exec_type == CEE_EXEC_EXIT.
Note: this must be _exit(), not exit(). */