smtp_transport_options_block smtp_transport_option_defaults = {
.hosts = NULL,
- .hosts = NULL,
+ .fallback_hosts = NULL,
.hostlist = NULL,
.fallback_hostlist = NULL,
.authenticated_sender = NULL,
/* This writes to the main log and to the message log.
Arguments:
- addr the address item containing error information
host the current host
+ detail the current message (addr_item->message)
+ basic_errno the errno (addr_item->basic_errno)
Returns: nothing
*/
static void
-write_logs(address_item *addr, host_item *host)
+write_logs(const host_item *host, const uschar *suffix, int basic_errno)
{
-uschar * message = LOGGING(outgoing_port)
+
+
+uschar *message = LOGGING(outgoing_port)
? string_sprintf("H=%s [%s]:%d", host->name, host->address,
host->port == PORT_NONE ? 25 : host->port)
: string_sprintf("H=%s [%s]", host->name, host->address);
-if (addr->message)
+if (suffix)
{
- message = string_sprintf("%s: %s", message, addr->message);
- if (addr->basic_errno > 0)
- message = string_sprintf("%s: %s", message, strerror(addr->basic_errno));
- log_write(0, LOG_MAIN, "%s", message);
- deliver_msglog("%s %s\n", tod_stamp(tod_log), message);
+ message = string_sprintf("%s: %s", message, suffix);
+ if (basic_errno > 0)
+ message = string_sprintf("%s: %s", message, strerror(basic_errno));
}
else
- {
- const uschar * s = exim_errstr(addr->basic_errno);
- log_write(0, LOG_MAIN, "%s %s", message, s);
- deliver_msglog("%s %s %s\n", tod_stamp(tod_log), message, s);
- }
+ message = string_sprintf("%s %s", message, exim_errstr(basic_errno));
+
+log_write(0, LOG_MAIN, "%s", message);
+deliver_msglog("%s %s\n", tod_stamp(tod_log), message);
}
static void
switch (rc)
{
- case DNS_SUCCEED:
- if (sec) return OK;
-
- log_write(0, LOG_MAIN, "DANE error: TLSA lookup not DNSSEC");
- /*FALLTHROUGH*/
case DNS_AGAIN:
return DEFER; /* just defer this TLS'd conn */
+ case DNS_SUCCEED:
+ if (sec) return OK;
+ log_write(0, LOG_MAIN,
+ "DANE error: TLSA lookup for %s not DNSSEC", host->name);
+ /*FALLTRHOUGH*/
+
case DNS_NODATA: /* no TLSA RR for this lookup */
case DNS_NOMATCH: /* no records at all for this lookup */
return dane_required ? FAIL : FAIL_FORCED;
errno = ERRNO_SMTPCLOSED;
goto EHLOHELO_FAILED;
}
- Ustrncpy(sx->buffer, rsp, sizeof(sx->buffer)/2);
+ memmove(sx->buffer, rsp, Ustrlen(rsp));
goto RESPONSE_FAILED;
}
}
sx->send_quit = FALSE;
goto FAILED;
- /* This label is jumped to directly when a TLS negotiation has failed,
- or was not done for a host for which it is required. Values will be set
- in message and errno, and setting_up will always be true. Treat as
- a temporary error. */
-
EHLOHELO_FAILED:
code = '4';
message = string_sprintf("Remote host closed connection in response to %s"
sx->send_quit = FALSE;
goto FAILED;
+ /* This label is jumped to directly when a TLS negotiation has failed,
+ or was not done for a host for which it is required. Values will be set
+ in message and errno, and setting_up will always be true. Treat as
+ a temporary error. */
+
#ifdef SUPPORT_TLS
TLS_FAILED:
code = '4';
* Proxy TLS connection for another transport process *
******************************************************/
/*
-Use the given buffer as a staging area, and select on both the given fd
-and the TLS'd client-fd for data to read (per the coding in ip_recv() and
-fd_ready() this is legitimate). Do blocking full-size writes, and reads
-under a timeout.
+Close the unused end of the pipe, fork once more, then use the given buffer
+as a staging area, and select on both the given fd and the TLS'd client-fd for
+data to read (per the coding in ip_recv() and fd_ready() this is legitimate).
+Do blocking full-size writes, and reads under a timeout. Once both input
+channels are closed, exit the process.
Arguments:
buf space to use for buffering
bufsiz size of buffer
- proxy_fd comms to proxied process
+ pfd pipe filedescriptor array; [0] is comms to proxied process
timeout per-read timeout, seconds
*/
void
-smtp_proxy_tls(uschar * buf, size_t bsize, int proxy_fd, int timeout)
+smtp_proxy_tls(uschar * buf, size_t bsize, int * pfd, int timeout)
{
fd_set rfds, efds;
-int max_fd = MAX(proxy_fd, tls_out.active) + 1;
+int max_fd = MAX(pfd[0], tls_out.active) + 1;
int rc, i, fd_bits, nbytes;
+close(pfd[1]);
+if ((rc = fork()))
+ {
+ DEBUG(D_transport) debug_printf("proxy-proc final-pid %d\n", rc);
+ _exit(rc < 0 ? EXIT_FAILURE : EXIT_SUCCESS);
+ }
+
+if (running_in_test_harness) millisleep(100); /* let parent debug out */
set_process_info("proxying TLS connection for continued transport");
FD_ZERO(&rfds);
FD_SET(tls_out.active, &rfds);
-FD_SET(proxy_fd, &rfds);
+FD_SET(pfd[0], &rfds);
for (fd_bits = 3; fd_bits; )
{
if (rc <= 0)
{
DEBUG(D_transport) if (rc == 0) debug_printf("%s: timed out\n", __FUNCTION__);
- return;
+ goto done;
}
- if (FD_ISSET(tls_out.active, &efds) || FD_ISSET(proxy_fd, &efds))
+ if (FD_ISSET(tls_out.active, &efds) || FD_ISSET(pfd[0], &efds))
{
DEBUG(D_transport) debug_printf("select: exceptional cond on %s fd\n",
- FD_ISSET(proxy_fd, &efds) ? "proxy" : "tls");
- return;
+ FD_ISSET(pfd[0], &efds) ? "proxy" : "tls");
+ goto done;
}
}
- while (rc < 0 || !(FD_ISSET(tls_out.active, &rfds) || FD_ISSET(proxy_fd, &rfds)));
+ while (rc < 0 || !(FD_ISSET(tls_out.active, &rfds) || FD_ISSET(pfd[0], &rfds)));
/* handle inbound data */
if (FD_ISSET(tls_out.active, &rfds))
{
fd_bits &= ~1;
FD_CLR(tls_out.active, &rfds);
- shutdown(proxy_fd, SHUT_WR);
+ shutdown(pfd[0], SHUT_WR);
timeout = 5;
}
else
{
for (nbytes = 0; rc - nbytes > 0; nbytes += i)
- if ((i = write(proxy_fd, buf + nbytes, rc - nbytes)) < 0) return;
+ if ((i = write(pfd[0], buf + nbytes, rc - nbytes)) < 0) goto done;
}
else if (fd_bits & 1)
FD_SET(tls_out.active, &rfds);
/* handle outbound data */
- if (FD_ISSET(proxy_fd, &rfds))
- if ((rc = read(proxy_fd, buf, bsize)) <= 0)
+ if (FD_ISSET(pfd[0], &rfds))
+ if ((rc = read(pfd[0], buf, bsize)) <= 0)
{
fd_bits = 0;
tls_close(FALSE, TRUE);
{
for (nbytes = 0; rc - nbytes > 0; nbytes += i)
if ((i = tls_write(FALSE, buf + nbytes, rc - nbytes, FALSE)) < 0)
- return;
+ goto done;
}
else if (fd_bits & 2)
- FD_SET(proxy_fd, &rfds);
+ FD_SET(pfd[0], &rfds);
}
+
+done:
+ if (running_in_test_harness) millisleep(100); /* let logging complete */
+ exim_exit(0, US"TLS proxy");
}
#endif
else
sprintf(CS sx.buffer, "%.500s\n", addr->unique);
- DEBUG(D_deliver) debug_printf("S:journalling %s", sx.buffer);
+ DEBUG(D_deliver) debug_printf("S:journalling %s\n", sx.buffer);
len = Ustrlen(CS sx.buffer);
if (write(journal_fd, sx.buffer, len) != len)
log_write(0, LOG_MAIN|LOG_PANIC, "failed to write journal for "
set_rc = DEFER;
if (save_errno > 0)
message = US string_sprintf("%s: %s", message, strerror(save_errno));
- if (host->next != NULL) log_write(0, LOG_MAIN, "%s", message);
- msglog_line(host, message);
+
+ write_logs(host, message, sx.first_addr ? sx.first_addr->basic_errno : 0);
+
*message_defer = TRUE;
}
}
{
sx.send_quit = FALSE;
- /* If TLS is still active, we need to proxy it for the transport we
+ /* We have passed the client socket to a fresh transport process.
+ If TLS is still active, we need to proxy it for the transport we
just passed the baton to. Fork a child to to do it, and return to
get logging done asap. Which way to place the work makes assumptions
about post-fork prioritisation which may not hold on all platforms. */
if (tls_out.active >= 0)
{
int pid = fork();
+ if (pid == 0) /* child; fork again to disconnect totally */
+ {
+ if (running_in_test_harness) millisleep(100); /* let parent debug out */
+ /* does not return */
+ smtp_proxy_tls(sx.buffer, sizeof(sx.buffer), pfd,
+ sx.ob->command_timeout);
+ }
+
if (pid > 0) /* parent */
{
DEBUG(D_transport) debug_printf("proxy-proc inter-pid %d\n", pid);
close(pfd[0]);
+ /* tidy the inter-proc to disconn the proxy proc */
waitpid(pid, NULL, 0);
tls_close(FALSE, FALSE);
(void)close(sx.inblock.sock);
continue_hostname = NULL;
return yield;
}
- else if (pid == 0) /* child; fork again to disconnect totally */
- {
- close(pfd[1]);
- if ((pid = fork()))
- {
- DEBUG(D_transport) debug_printf("proxy-prox final-pid %d\n", pid);
- _exit(pid < 0 ? EXIT_FAILURE : EXIT_SUCCESS);
- }
- smtp_proxy_tls(sx.buffer, sizeof(sx.buffer), pfd[0], sx.ob->command_timeout);
- if (running_in_test_harness) millisleep(100); /* let logging complete */
- exim_exit(0, US"TLS proxy");
- }
+ log_write(0, LOG_PANIC_DIE, "fork failed");
}
#endif
}
smtp_transport_options_block *ob =
(smtp_transport_options_block *)(tblock->options_block);
host_item *hostlist = addrlist->host_list;
-host_item *host = NULL;
+host_item *host;
DEBUG(D_transport)
{
{
debug_printf("hostlist:\n");
for (host = hostlist; host; host = host->next)
- debug_printf(" %s:%d\n", host->name, host->port);
+ debug_printf(" '%s' IP %s port %d\n", host->name, host->address, host->port);
}
if (continue_hostname)
debug_printf("already connected to %s [%s] (on fd %d)\n",
{
host_item *nexthost = NULL;
int unexpired_hosts_tried = 0;
+ BOOL continue_host_tried = FALSE;
+retry_non_continued:
for (host = hostlist;
host
&& unexpired_hosts_tried < ob->hosts_max_try
result of the lookup. Set expired FALSE, to save the outer loop executing
twice. */
- if ( continue_hostname
- && ( Ustrcmp(continue_hostname, host->name) != 0
- || Ustrcmp(continue_host_address, host->address) != 0
- ) )
- {
- expired = FALSE;
- continue; /* With next host */
- }
+ if (continue_hostname)
+ if ( Ustrcmp(continue_hostname, host->name) != 0
+ || Ustrcmp(continue_host_address, host->address) != 0
+ )
+ {
+ expired = FALSE;
+ continue; /* With next host */
+ }
+ else
+ continue_host_tried = TRUE;
/* Reset the default next host in case a multihomed host whose addresses
are not looked up till just above added to the host list. */
if (rc == DEFER && first_addr->basic_errno != ERRNO_AUTHFAIL
&& first_addr->basic_errno != ERRNO_TLSFAILURE)
- write_logs(first_addr, host);
+ write_logs(host, first_addr->message, first_addr->basic_errno);
#ifndef DISABLE_EVENT
if (rc == DEFER)
rc = smtp_deliver(addrlist, thost, host_af, defport, interface, tblock,
&message_defer, TRUE);
if (rc == DEFER && first_addr->basic_errno != ERRNO_AUTHFAIL)
- write_logs(first_addr, host);
+ write_logs(host, first_addr->message, first_addr->basic_errno);
# ifndef DISABLE_EVENT
if (rc == DEFER)
deferred_event_raise(first_addr, host);
}
} /* End of loop for trying multiple hosts. */
+ /* If we failed to find a matching host in the list, for an already-open
+ connection, just close it and start over with the list. This can happen
+ for routing that changes from run to run, or big multi-IP sites with
+ round-robin DNS. */
+
+ if (continue_hostname && !continue_host_tried)
+ {
+ int fd = cutthrough.fd >= 0 ? cutthrough.fd : 0;
+
+ DEBUG(D_transport) debug_printf("no hosts match already-open connection\n");
+#ifdef SUPPORT_TLS
+ if (tls_out.active == fd)
+ {
+ (void) tls_write(FALSE, US"QUIT\r\n", 6, FALSE);
+ tls_close(FALSE, TRUE);
+ }
+ else
+#else
+ (void) write(fd, US"QUIT\r\n", 6);
+#endif
+ (void) close(fd);
+ cutthrough.fd = -1;
+ continue_hostname = NULL;
+ goto retry_non_continued;
+ }
+
/* This is the end of the loop that repeats iff expired is TRUE and
ob->delay_after_cutoff is FALSE. The second time round we will
try those hosts that haven't been tried since the message arrived. */