* Exim - an Internet mail transport agent *
*************************************************/
-/* Copyright (c) University of Cambridge 1995 - 2016 */
+/* Copyright (c) University of Cambridge 1995 - 2017 */
/* See the file NOTICE for conditions of use and distribution. */
#include "../exim.h"
#include "smtp.h"
-#define PENDING 256
-#define PENDING_DEFER (PENDING + DEFER)
-#define PENDING_OK (PENDING + OK)
-
-#define DELIVER_BUFFER_SIZE 4096
-
/* Options specific to the smtp transport. This transport also supports LMTP
over TCP/IP. The options must be in alphabetic order (note that "_" comes
#ifdef SUPPORT_TLS
{ "hosts_nopass_tls", opt_stringptr,
(void *)offsetof(smtp_transport_options_block, hosts_nopass_tls) },
+ { "hosts_noproxy_tls", opt_stringptr,
+ (void *)offsetof(smtp_transport_options_block, hosts_noproxy_tls) },
#endif
{ "hosts_override", opt_bool,
(void *)offsetof(smtp_transport_options_block, hosts_override) },
int smtp_transport_options_count =
sizeof(smtp_transport_options)/sizeof(optionlist);
+
+#ifdef MACRO_PREDEF
+
+/* Dummy values */
+smtp_transport_options_block smtp_transport_option_defaults = {0};
+void smtp_transport_init(transport_instance *tblock) {}
+BOOL smtp_transport_entry(transport_instance *tblock, address_item *addr) {return FALSE;}
+void smtp_transport_closedown(transport_instance *tblock) {}
+
+#else /*!MACRO_PREDEF*/
+
+
/* Default private options block for the smtp transport. */
smtp_transport_options_block smtp_transport_option_defaults = {
NULL, /* hosts_verify_avoid_tls */
NULL, /* hosts_avoid_pipelining */
NULL, /* hosts_avoid_esmtp */
+#ifdef SUPPORT_TLS
NULL, /* hosts_nopass_tls */
+ US"*", /* hosts_noproxy_tls */
+#endif
5*60, /* command_timeout */
5*60, /* connect_timeout; shorter system default overrides */
5*60, /* data timeout */
static uschar *mail_command; /* Points to MAIL cmd for error messages */
static uschar *data_command = US""; /* Points to DATA cmd for error messages */
static BOOL update_waiting; /* TRUE to update the "wait" database */
+
+/*XXX move to smtp_context */
static BOOL pipelining_active; /* current transaction is in pipe mode */
/* Pass back options if required. This interface is getting very messy. */
-if (tf != NULL)
+if (tf)
{
tf->interface = ob->interface;
tf->port = ob->port;
list. */
if (!testflag(addrlist, af_local_host_removed))
- {
- for (; addrlist != NULL; addrlist = addrlist->next)
- if (addrlist->fallback_hosts == NULL)
- addrlist->fallback_hosts = ob->fallback_hostlist;
- }
+ for (; addrlist; addrlist = addrlist->next)
+ if (!addrlist->fallback_hosts) addrlist->fallback_hosts = ob->fallback_hostlist;
return OK;
}
{
addr->basic_errno = errno_value;
addr->more_errno |= orvalue;
- if (msg != NULL)
+ if (msg)
{
addr->message = msg;
if (pass_message) setflag(addr, af_pass_message);
uschar *buffer, int *yield, uschar **message, BOOL *pass_message)
{
uschar * pl = pipelining_active ? US"pipelined " : US"";
+const uschar * s;
*yield = '4'; /* Default setting is to give a temporary error */
-/* Handle response timeout */
-
-if (*errno_value == ETIMEDOUT)
- {
- *message = US string_sprintf("SMTP timeout after %s%s",
- pl, smtp_command);
- if (transport_count > 0)
- *message = US string_sprintf("%s (%d bytes written)", *message,
- transport_count);
- return FALSE;
- }
-
-/* Handle malformed SMTP response */
-
-if (*errno_value == ERRNO_SMTPFORMAT)
- {
- const uschar *malfresp = string_printing(buffer);
- while (isspace(*malfresp)) malfresp++;
- *message = *malfresp == 0
- ? string_sprintf("Malformed SMTP reply (an empty line) "
- "in response to %s%s", pl, smtp_command)
- : string_sprintf("Malformed SMTP reply in response to %s%s: %s",
- pl, smtp_command, malfresp);
- return FALSE;
- }
-
-/* Handle a failed filter process error; can't send QUIT as we mustn't
-end the DATA. */
-
-if (*errno_value == ERRNO_FILTER_FAIL)
- {
- *message = US string_sprintf("transport filter process failed (%d)%s",
- more_errno,
- (more_errno == EX_EXECFAILED)? ": unable to execute command" : "");
- return FALSE;
- }
-
-/* Handle a failed add_headers expansion; can't send QUIT as we mustn't
-end the DATA. */
-
-if (*errno_value == ERRNO_CHHEADER_FAIL)
- {
- *message =
- US string_sprintf("failed to expand headers_add or headers_remove: %s",
- expand_string_message);
- return FALSE;
- }
-
-/* Handle failure to write a complete data block */
-
-if (*errno_value == ERRNO_WRITEINCOMPLETE)
+switch(*errno_value)
{
- *message = US string_sprintf("failed to write a data block");
- return FALSE;
- }
+ case ETIMEDOUT: /* Handle response timeout */
+ *message = US string_sprintf("SMTP timeout after %s%s",
+ pl, smtp_command);
+ if (transport_count > 0)
+ *message = US string_sprintf("%s (%d bytes written)", *message,
+ transport_count);
+ return FALSE;
+
+ case ERRNO_SMTPFORMAT: /* Handle malformed SMTP response */
+ s = string_printing(buffer);
+ while (isspace(*s)) s++;
+ *message = *s == 0
+ ? string_sprintf("Malformed SMTP reply (an empty line) "
+ "in response to %s%s", pl, smtp_command)
+ : string_sprintf("Malformed SMTP reply in response to %s%s: %s",
+ pl, smtp_command, s);
+ return FALSE;
+
+ case ERRNO_FILTER_FAIL: /* Handle a failed filter process error;
+ can't send QUIT as we mustn't end the DATA. */
+ *message = string_sprintf("transport filter process failed (%d)%s",
+ more_errno,
+ more_errno == EX_EXECFAILED ? ": unable to execute command" : "");
+ return FALSE;
+
+ case ERRNO_CHHEADER_FAIL: /* Handle a failed add_headers expansion;
+ can't send QUIT as we mustn't end the DATA. */
+ *message =
+ string_sprintf("failed to expand headers_add or headers_remove: %s",
+ expand_string_message);
+ return FALSE;
+
+ case ERRNO_WRITEINCOMPLETE: /* failure to write a complete data block */
+ *message = string_sprintf("failed to write a data block");
+ return FALSE;
#ifdef SUPPORT_I18N
-/* Handle lack of advertised SMTPUTF8, for international message */
-if (*errno_value == ERRNO_UTF8_FWD)
- {
- *message = US"utf8 support required but not offered for forwarding";
- DEBUG(D_deliver|D_transport) debug_printf("%s\n", *message);
- return TRUE;
- }
+ case ERRNO_UTF8_FWD: /* no advertised SMTPUTF8, for international message */
+ *message = US"utf8 support required but not offered for forwarding";
+ DEBUG(D_deliver|D_transport) debug_printf("%s\n", *message);
+ return TRUE;
#endif
+ }
/* Handle error responses from the remote mailer. */
if (buffer[0] != 0)
{
- const uschar *s = string_printing(buffer);
- *message = US string_sprintf("SMTP error from remote mail server after %s%s: "
- "%s", pl, smtp_command, s);
+ *message = string_sprintf("SMTP error from remote mail server after %s%s: "
+ "%s", pl, smtp_command, s = string_printing(buffer));
*pass_message = TRUE;
*yield = buffer[0];
return TRUE;
{
*errno_value = ERRNO_SMTPCLOSED;
*message = US string_sprintf("Remote host closed connection "
- "in response to %s%s", pl, smtp_command);
+ "in response to %s%s", pl, smtp_command);
}
-else *message = US string_sprintf("%s [%s]", host->name, host->address);
+else
+ *message = US string_sprintf("%s [%s]", host->name, host->address);
return FALSE;
}
converted to OK at the end.
Arguments:
- addrlist the complete address list
- include_affixes TRUE if affixes include in RCPT
- sync_addr ptr to the ptr of the one to start scanning at (updated)
- host the host we are connected to
+ sx smtp connection context
count the number of responses to read
- address_retry_
- include_sender true if 4xx retry is to include the sender it its key
- pending_MAIL true if the first response is for MAIL
pending_DATA 0 if last command sent was not DATA
+1 if previously had a good recipient
-1 if not previously had a good recipient
- inblock incoming SMTP block
- timeout timeout value
- buffer buffer for reading response
- buffsize size of buffer
Returns: 3 if at least one address had 2xx and one had 5xx
2 if at least one address had 5xx but none had 2xx
*/
static int
-sync_responses(address_item *addrlist, BOOL include_affixes,
- address_item **sync_addr, host_item *host, int count,
- BOOL address_retry_include_sender, BOOL pending_MAIL,
- int pending_DATA, smtp_inblock *inblock, int timeout, uschar *buffer,
- int buffsize)
+sync_responses(smtp_context * sx, int count, int pending_DATA)
{
-address_item *addr = *sync_addr;
+address_item *addr = sx->sync_addr;
+smtp_transport_options_block *ob =
+ (smtp_transport_options_block *)sx->tblock->options_block;
int yield = 0;
/* Handle the response for a MAIL command. On error, reinstate the original
command in big_buffer for error message use, and flush any further pending
responses before returning, except after I/O errors and timeouts. */
-if (pending_MAIL)
+if (sx->pending_MAIL)
{
count--;
- if (!smtp_read_response(inblock, buffer, buffsize, '2', timeout))
+ if (!smtp_read_response(&sx->inblock, sx->buffer, sizeof(sx->buffer),
+ '2', ob->command_timeout))
{
DEBUG(D_transport) debug_printf("bad response for MAIL\n");
Ustrcpy(big_buffer, mail_command); /* Fits, because it came from there! */
- if (errno == 0 && buffer[0] != 0)
+ if (errno == 0 && sx->buffer[0] != 0)
{
uschar flushbuffer[4096];
int save_errno = 0;
- if (buffer[0] == '4')
+ if (sx->buffer[0] == '4')
{
save_errno = ERRNO_MAIL4XX;
- addr->more_errno |= ((buffer[1] - '0')*10 + buffer[2] - '0') << 8;
+ addr->more_errno |= ((sx->buffer[1] - '0')*10 + sx->buffer[2] - '0') << 8;
}
while (count-- > 0)
{
- if (!smtp_read_response(inblock, flushbuffer, sizeof(flushbuffer),
- '2', timeout)
+ if (!smtp_read_response(&sx->inblock, flushbuffer, sizeof(flushbuffer),
+ '2', ob->command_timeout)
&& (errno != 0 || flushbuffer[0] == 0))
break;
}
while (count-- > 0) /* Mark any pending addrs with the host used */
{
while (addr->transport_return != PENDING_DEFER) addr = addr->next;
- addr->host_used = host;
+ addr->host_used = sx->host;
addr = addr->next;
}
return -3;
while (count-- > 0)
{
- while (addr->transport_return != PENDING_DEFER) addr = addr->next;
+ while (addr->transport_return != PENDING_DEFER)
+ if (!(addr = addr->next))
+ return -2;
/* The address was accepted */
- addr->host_used = host;
+ addr->host_used = sx->host;
- if (smtp_read_response(inblock, buffer, buffsize, '2', timeout))
+ if (smtp_read_response(&sx->inblock, sx->buffer, sizeof(sx->buffer),
+ '2', ob->command_timeout))
{
yield |= 1;
addr->transport_return = PENDING_OK;
else if (errno == ETIMEDOUT)
{
uschar *message = string_sprintf("SMTP timeout after RCPT TO:<%s>",
- transport_rcpt_address(addr, include_affixes));
- set_errno_nohost(addrlist, ETIMEDOUT, message, DEFER, FALSE);
+ transport_rcpt_address(addr, sx->tblock->rcpt_include_affixes));
+ set_errno_nohost(sx->first_addr, ETIMEDOUT, message, DEFER, FALSE);
retry_add_item(addr, addr->address_retry_key, 0);
update_waiting = FALSE;
return -1;
big_buffer for which we are checking the response, so the error message
makes sense. */
- else if (errno != 0 || buffer[0] == 0)
+ else if (errno != 0 || sx->buffer[0] == 0)
{
string_format(big_buffer, big_buffer_size, "RCPT TO:<%s>",
- transport_rcpt_address(addr, include_affixes));
+ transport_rcpt_address(addr, sx->tblock->rcpt_include_affixes));
return -2;
}
{
addr->message =
string_sprintf("SMTP error from remote mail server after RCPT TO:<%s>: "
- "%s", transport_rcpt_address(addr, include_affixes),
- string_printing(buffer));
+ "%s", transport_rcpt_address(addr, sx->tblock->rcpt_include_affixes),
+ string_printing(sx->buffer));
setflag(addr, af_pass_message);
- msglog_line(host, addr->message);
+ if (!sx->verify)
+ msglog_line(sx->host, addr->message);
/* The response was 5xx */
- if (buffer[0] == '5')
+ if (sx->buffer[0] == '5')
{
addr->transport_return = FAIL;
yield |= 2;
{
addr->transport_return = DEFER;
addr->basic_errno = ERRNO_RCPT4XX;
- addr->more_errno |= ((buffer[1] - '0')*10 + buffer[2] - '0') << 8;
+ addr->more_errno |= ((sx->buffer[1] - '0')*10 + sx->buffer[2] - '0') << 8;
+ if (!sx->verify)
+ {
#ifndef DISABLE_EVENT
- event_defer_errno = addr->more_errno;
- msg_event_raise(US"msg:rcpt:host:defer", addr);
+ event_defer_errno = addr->more_errno;
+ msg_event_raise(US"msg:rcpt:host:defer", addr);
#endif
- /* Log temporary errors if there are more hosts to be tried.
- If not, log this last one in the == line. */
+ /* Log temporary errors if there are more hosts to be tried.
+ If not, log this last one in the == line. */
- if (host->next)
- log_write(0, LOG_MAIN, "H=%s [%s]: %s", host->name, host->address, addr->message);
+ if (sx->host->next)
+ log_write(0, LOG_MAIN, "H=%s [%s]: %s",
+ sx->host->name, sx->host->address, addr->message);
#ifndef DISABLE_EVENT
- else
- msg_event_raise(US"msg:rcpt:defer", addr);
+ else
+ msg_event_raise(US"msg:rcpt:defer", addr);
#endif
- /* Do not put this message on the list of those waiting for specific
- hosts, as otherwise it is likely to be tried too often. */
+ /* Do not put this message on the list of those waiting for specific
+ hosts, as otherwise it is likely to be tried too often. */
- update_waiting = FALSE;
+ update_waiting = FALSE;
- /* Add a retry item for the address so that it doesn't get tried again
- too soon. If address_retry_include_sender is true, add the sender address
- to the retry key. */
+ /* Add a retry item for the address so that it doesn't get tried again
+ too soon. If address_retry_include_sender is true, add the sender address
+ to the retry key. */
- if (address_retry_include_sender)
- {
- uschar *altkey = string_sprintf("%s:<%s>", addr->address_retry_key,
- sender_address);
- retry_add_item(addr, altkey, 0);
- }
- else retry_add_item(addr, addr->address_retry_key, 0);
+ retry_add_item(addr,
+ ob->address_retry_include_sender
+ ? string_sprintf("%s:<%s>", addr->address_retry_key, sender_address)
+ : addr->address_retry_key,
+ 0);
+ }
}
}
} /* Loop for next RCPT response */
/* Update where to start at for the next block of responses, unless we
have already handled all the addresses. */
-if (addr != NULL) *sync_addr = addr->next;
+if (addr) sx->sync_addr = addr->next;
/* Handle a response to DATA. If we have not had any good recipients, either
previously or in this block, the response is ignored. */
if (pending_DATA != 0 &&
- !smtp_read_response(inblock, buffer, buffsize, '3', timeout))
+ !smtp_read_response(&sx->inblock, sx->buffer, sizeof(sx->buffer),
+ '3', ob->command_timeout))
{
int code;
uschar *msg;
BOOL pass_message;
if (pending_DATA > 0 || (yield & 1) != 0)
{
- if (errno == 0 && buffer[0] == '4')
+ if (errno == 0 && sx->buffer[0] == '4')
{
errno = ERRNO_DATA4XX;
- addrlist->more_errno |= ((buffer[1] - '0')*10 + buffer[2] - '0') << 8;
+ sx->first_addr->more_errno |= ((sx->buffer[1] - '0')*10 + sx->buffer[2] - '0') << 8;
}
return -3;
}
- (void)check_response(host, &errno, 0, buffer, &code, &msg, &pass_message);
+ (void)check_response(sx->host, &errno, 0, sx->buffer, &code, &msg, &pass_message);
DEBUG(D_transport) debug_printf("%s\nerror for DATA ignored: pipelining "
"is in use and there were no good recipients\n", msg);
}
/* move this out to host.c given the similarity to dns_lookup() ? */
uschar buffer[300];
const uschar * fullname = buffer;
+int rc;
+BOOL sec;
/* TLSA lookup string */
(void)sprintf(CS buffer, "_%d._tcp.%.256s", host->port, host->name);
-switch (dns_lookup(dnsa, buffer, T_TLSA, &fullname))
+rc = dns_lookup(dnsa, buffer, T_TLSA, &fullname);
+sec = dns_is_secure(dnsa);
+DEBUG(D_transport)
+ debug_printf("TLSA lookup ret %d %sDNSSEC\n", rc, sec ? "" : "not ");
+
+switch (rc)
{
case DNS_SUCCEED:
- if (!dns_is_secure(dnsa))
- {
- log_write(0, LOG_MAIN, "DANE error: TLSA lookup not DNSSEC");
- return DEFER;
- }
- return OK;
+ 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 */
-uschar
-ehlo_response(uschar * buf, size_t bsize, uschar checks)
+static uschar
+ehlo_response(uschar * buf, uschar checks)
{
+size_t bsize = Ustrlen(buf);
+
#ifdef SUPPORT_TLS
if ( checks & PEER_OFFERED_TLS
&& pcre_exec(regex_STARTTLS, NULL, CS buf, bsize, 0, PCRE_EOPT, NULL, 0) < 0)
If given a nonzero size, first flush any buffered SMTP commands
then emit the command.
-Reap previous SMTP command responses if requested.
-Reap one SMTP command response if requested.
+Reap previous SMTP command responses if requested, and always reap
+the response from a previous BDAT command.
+
+Args:
+ tctx transport context
+ chunk_size value for SMTP BDAT command
+ flags
+ tc_chunk_last add LAST option to SMTP BDAT command
+ tc_reap_prev reap response to previous SMTP commands
Returns: OK or ERROR
*/
static int
-smtp_chunk_cmd_callback(int fd, transport_ctx * tctx,
- unsigned chunk_size, unsigned flags)
+smtp_chunk_cmd_callback(transport_ctx * tctx, unsigned chunk_size,
+ unsigned flags)
{
smtp_transport_options_block * ob =
(smtp_transport_options_block *)(tctx->tblock->options_block);
+smtp_context * sx = tctx->smtp_context;
int cmd_count = 0;
int prev_cmd_count;
-uschar * buffer = tctx->buffer;
-
-/* Write SMTP chunk header command */
+/* Write SMTP chunk header command. If not reaping responses, note that
+there may be more writes (like, the chunk data) done soon. */
if (chunk_size > 0)
{
- if((cmd_count = smtp_write_command(tctx->outblock, FALSE, "BDAT %u%s\r\n",
- chunk_size,
- flags & tc_chunk_last ? " LAST" : "")
+ if((cmd_count = smtp_write_command(&sx->outblock,
+ flags & tc_reap_prev ? SCMD_FLUSH : SCMD_MORE,
+ "BDAT %u%s\r\n", chunk_size, flags & tc_chunk_last ? " LAST" : "")
) < 0) return ERROR;
if (flags & tc_chunk_last)
data_command = string_copy(big_buffer); /* Save for later error message */
}
-prev_cmd_count = cmd_count += tctx->cmd_count;
+prev_cmd_count = cmd_count += sx->cmd_count;
/* Reap responses for any previous, but not one we just emitted */
if (chunk_size > 0)
prev_cmd_count--;
-if (tctx->pending_BDAT)
+if (sx->pending_BDAT)
prev_cmd_count--;
if (flags & tc_reap_prev && prev_cmd_count > 0)
DEBUG(D_transport) debug_printf("look for %d responses"
" for previous pipelined cmds\n", prev_cmd_count);
- switch(sync_responses(tctx->first_addr, tctx->tblock->rcpt_include_affixes,
- tctx->sync_addr, tctx->host, prev_cmd_count,
- ob->address_retry_include_sender,
- tctx->pending_MAIL, 0,
- tctx->inblock,
- ob->command_timeout,
- buffer, DELIVER_BUFFER_SIZE))
+ switch(sync_responses(sx, prev_cmd_count, 0))
{
case 1: /* 2xx (only) => OK */
- case 3: tctx->good_RCPT = TRUE; /* 2xx & 5xx => OK & progress made */
- case 2: *tctx->completed_address = TRUE; /* 5xx (only) => progress made */
+ case 3: sx->good_RCPT = TRUE; /* 2xx & 5xx => OK & progress made */
+ case 2: sx->completed_addr = TRUE; /* 5xx (only) => progress made */
case 0: break; /* No 2xx or 5xx, but no probs */
case -1: /* Timeout on RCPT */
default: return ERROR; /* I/O error, or any MAIL/DATA error */
}
cmd_count = 1;
- if (!tctx->pending_BDAT)
+ if (!sx->pending_BDAT)
pipelining_active = FALSE;
}
/* Reap response for an outstanding BDAT */
-if (tctx->pending_BDAT)
+if (sx->pending_BDAT)
{
DEBUG(D_transport) debug_printf("look for one response for BDAT\n");
- if (!smtp_read_response(tctx->inblock, buffer, DELIVER_BUFFER_SIZE, '2',
+ if (!smtp_read_response(&sx->inblock, sx->buffer, sizeof(sx->buffer), '2',
ob->command_timeout))
{
- if (errno == 0 && buffer[0] == '4')
+ if (errno == 0 && sx->buffer[0] == '4')
{
errno = ERRNO_DATA4XX; /*XXX does this actually get used? */
- tctx->first_addr->more_errno |=
- ((buffer[1] - '0')*10 + buffer[2] - '0') << 8;
+ sx->addrlist->more_errno |=
+ ((sx->buffer[1] - '0')*10 + sx->buffer[2] - '0') << 8;
}
return ERROR;
}
cmd_count--;
- tctx->pending_BDAT = FALSE;
+ sx->pending_BDAT = FALSE;
pipelining_active = FALSE;
}
else if (chunk_size > 0)
- tctx->pending_BDAT = TRUE;
+ sx->pending_BDAT = TRUE;
-tctx->cmd_count = cmd_count;
+sx->cmd_count = cmd_count;
return OK;
}
* Make connection for given message *
*************************************************/
-typedef struct {
- address_item * addrlist;
- host_item * host;
- int host_af;
- int port;
- uschar * interface;
-
- BOOL lmtp:1;
- BOOL smtps:1;
- BOOL ok:1;
- BOOL send_rset:1;
- BOOL send_quit:1;
- BOOL setting_up:1;
- BOOL esmtp:1;
- BOOL esmtp_sent:1;
- BOOL pending_MAIL:1;
-#ifndef DISABLE_PRDR
- BOOL prdr_active:1;
-#endif
-#ifdef SUPPORT_I18N
- BOOL utf8_needed:1;
-#endif
- BOOL dsn_all_lasthop:1;
-#if defined(SUPPORT_TLS) && defined(EXPERIMENTAL_DANE)
- BOOL dane:1;
- BOOL dane_required:1;
-#endif
-
- int max_rcpt;
-
- uschar peer_offered;
- uschar * igquotstr;
- uschar * helo_data;
-#ifdef EXPERIMENTAL_DSN_INFO
- uschar * smtp_greeting;
- uschar * helo_response;
-#endif
-
- smtp_inblock inblock;
- smtp_outblock outblock;
- uschar buffer[DELIVER_BUFFER_SIZE];
- uschar inbuffer[4096];
- uschar outbuffer[4096];
-
- transport_instance * tblock;
- smtp_transport_options_block * ob;
-} smtp_context;
-
/*
Arguments:
ctx connection context
- message_defer return set TRUE if yield is OK, but all addresses were deferred
- because of a non-recipient, non-host failure, that is, a
- 4xx response to MAIL FROM, DATA, or ".". This is a defer
- that is specific to the message.
suppress_tls if TRUE, don't attempt a TLS connection - this is set for
a second attempt after TLS initialization fails
- verify TRUE if connection is for a verify callout, FALSE for
- a delivery attempt
Returns: OK - the connection was made and the delivery attempted;
fd is set in the conn context, tls_out set up.
to expand
*/
int
-smtp_setup_conn(smtp_context * sx, BOOL * message_defer, BOOL suppress_tls,
- BOOL verify)
+smtp_setup_conn(smtp_context * sx, BOOL suppress_tls)
{
#if defined(SUPPORT_TLS) && defined(EXPERIMENTAL_DANE)
dns_answer tlsa_dnsa;
#endif
BOOL pass_message = FALSE;
-
uschar * message = NULL;
-int save_errno;
int yield = OK;
int rc;
-sx->ob = (smtp_transport_options_block *)(sx->tblock->options_block);
+sx->ob = (smtp_transport_options_block *) sx->tblock->options_block;
sx->lmtp = strcmpic(sx->ob->protocol, US"lmtp") == 0;
sx->smtps = strcmpic(sx->ob->protocol, US"smtps") == 0;
#endif
if ((sx->max_rcpt = sx->tblock->max_addresses) == 0) sx->max_rcpt = 999999;
-sx->helo_data = NULL;
sx->peer_offered = 0;
sx->igquotstr = US"";
+if (!sx->helo_data) sx->helo_data = sx->ob->helo_data;
#ifdef EXPERIMENTAL_DSN_INFO
sx->smtp_greeting = NULL;
sx->helo_response = NULL;
#endif
-*message_defer = FALSE;
smtp_command = US"initial connection";
sx->buffer[0] = '\0';
/* Flip the legacy TLS-related variables over to the outbound set in case
they're used in the context of the transport. Don't bother resetting
-afterward as we're in a subprocess. */
+afterward (when being used by a transport) as we're in a subprocess.
+For verify, unflipped once the callout is dealt with */
tls_modify_variables(&tls_out);
the initial interaction and HELO/EHLO/LHLO. Connect timeout errors are handled
specially so they can be identified for retries. */
-if (continue_hostname == NULL)
+if (!continue_hostname)
{
- /* This puts port into host->port */
- sx->inblock.sock = sx->outblock.sock =
- smtp_connect(sx->host, sx->host_af, sx->port, sx->interface,
- sx->ob->connect_timeout, sx->tblock);
+ if (sx->verify)
+ HDEBUG(D_verify) debug_printf("interface=%s port=%d\n", sx->interface, sx->port);
- if (sx->inblock.sock < 0)
- {
- set_errno_nohost(sx->addrlist, errno == ETIMEDOUT ? ERRNO_CONNECTTIMEOUT : errno,
- NULL, DEFER, FALSE);
- return DEFER;
- }
+ /* Get the actual port the connection will use, into sx->host */
+
+ smtp_port_for_connect(sx->host, sx->port);
#if defined(SUPPORT_TLS) && defined(EXPERIMENTAL_DANE)
+ /* Do TLSA lookup for DANE */
{
tls_out.dane_verified = FALSE;
tls_out.tlsa_usage = 0;
)
switch (rc = tlsa_lookup(sx->host, &tlsa_dnsa, sx->dane_required))
{
- case OK: sx->dane = TRUE; break;
+ case OK: sx->dane = TRUE;
+ sx->ob->tls_tempfail_tryclear = FALSE;
+ break;
case FAIL_FORCED: break;
default: set_errno_nohost(sx->addrlist, ERRNO_DNSDEFER,
string_sprintf("DANE error: tlsa lookup %s",
FAIL, FALSE);
return FAIL;
}
-
- if (sx->dane)
- sx->ob->tls_tempfail_tryclear = FALSE;
}
#endif /*DANE*/
+ /* Make the TCP connection */
+
+ sx->inblock.sock = sx->outblock.sock =
+ smtp_connect(sx->host, sx->host_af, sx->interface,
+ sx->ob->connect_timeout, sx->tblock);
+
+ if (sx->inblock.sock < 0)
+ {
+ uschar * msg = NULL;
+ if (sx->verify)
+ {
+ msg = US strerror(errno);
+ HDEBUG(D_verify) debug_printf("connect: %s\n", msg);
+ }
+ set_errno_nohost(sx->addrlist,
+ errno == ETIMEDOUT ? ERRNO_CONNECTTIMEOUT : errno,
+ sx->verify ? string_sprintf("could not connect: %s", msg)
+ : NULL,
+ DEFER, FALSE);
+ sx->send_quit = FALSE;
+ return DEFER;
+ }
+
/* Expand the greeting message while waiting for the initial response. (Makes
sense if helo_data contains ${lookup dnsdb ...} stuff). The expansion is
delayed till here so that $sending_interface and $sending_port are set. */
- sx->helo_data = expand_string(sx->ob->helo_data);
+ if (sx->helo_data)
+ if (!(sx->helo_data = expand_string(sx->helo_data)))
+ if (sx->verify)
+ log_write(0, LOG_MAIN|LOG_PANIC,
+ "<%s>: failed to expand transport's helo_data value for callout: %s",
+ sx->addrlist->address, expand_string_message);
+
#ifdef SUPPORT_I18N
if (sx->helo_data)
{
- uschar * errstr = NULL;
- if ((sx->helo_data = string_domain_utf8_to_alabel(sx->helo_data, &errstr)), errstr)
- {
- errstr = string_sprintf("failed to expand helo_data: %s", errstr);
- set_errno_nohost(sx->addrlist, ERRNO_EXPANDFAIL, errstr, DEFER, FALSE);
- yield = DEFER;
- goto SEND_QUIT;
- }
+ expand_string_message = NULL;
+ if ((sx->helo_data = string_domain_utf8_to_alabel(sx->helo_data,
+ &expand_string_message)),
+ expand_string_message)
+ if (sx->verify)
+ log_write(0, LOG_MAIN|LOG_PANIC,
+ "<%s>: failed to expand transport's helo_data value for callout: %s",
+ sx->addrlist->address, expand_string_message);
+ else
+ sx->helo_data = NULL;
}
#endif
if (sx->esmtp)
{
- if (smtp_write_command(&sx->outblock, FALSE, "%s %s\r\n",
+ if (smtp_write_command(&sx->outblock, SCMD_FLUSH, "%s %s\r\n",
sx->lmtp ? "LHLO" : "EHLO", sx->helo_data) < 0)
goto SEND_FAILED;
sx->esmtp_sent = TRUE;
if (sx->esmtp_sent && (n = Ustrlen(sx->buffer)) < sizeof(sx->buffer)/2)
{ rsp = sx->buffer + n + 1; n = sizeof(sx->buffer) - n; }
- if (smtp_write_command(&sx->outblock, FALSE, "HELO %s\r\n", sx->helo_data) < 0)
+ if (smtp_write_command(&sx->outblock, SCMD_FLUSH, "HELO %s\r\n", sx->helo_data) < 0)
goto SEND_FAILED;
good_response = smtp_read_response(&sx->inblock, rsp, n,
'2', sx->ob->command_timeout);
if (rsp != sx->buffer && rsp[0] == 0 && (errno == 0 || errno == ECONNRESET))
{
- sx->send_quit = FALSE;
- save_errno = ERRNO_SMTPCLOSED;
- message = string_sprintf("Remote host closed connection "
- "in response to %s (EHLO response was: %s)",
- smtp_command, sx->buffer);
- goto FAILED;
+ errno = ERRNO_SMTPCLOSED;
+ goto EHLOHELO_FAILED;
}
Ustrncpy(sx->buffer, rsp, sizeof(sx->buffer)/2);
goto RESPONSE_FAILED;
if (sx->esmtp || sx->lmtp)
{
- sx->peer_offered = ehlo_response(sx->buffer, Ustrlen(sx->buffer),
+ sx->peer_offered = ehlo_response(sx->buffer,
PEER_OFFERED_TLS /* others checked later */
);
}
}
-/* For continuing deliveries down the same channel, the socket is the standard
-input, and we don't need to redo EHLO here (but may need to do so for TLS - see
-below). Set up the pointer to where subsequent commands will be left, for
+/* For continuing deliveries down the same channel, having re-exec'd the socket
+is the standard input; for a socket held open from verify it is recorded
+in the cutthrough context block. Either way we don't need to redo EHLO here
+(but may need to do so for TLS - see below).
+Set up the pointer to where subsequent commands will be left, for
error messages. Note that smtp_peer_options will have been
set from the command line if they were set in the process that passed the
connection on. */
else
{
- sx->inblock.sock = sx->outblock.sock = fileno(stdin);
+ if (cutthrough.fd >= 0 && cutthrough.callout_hold_only)
+ {
+ sx->inblock.sock = sx->outblock.sock = cutthrough.fd;
+ sx->host->port = sx->port = cutthrough.host.port;
+ }
+ else
+ {
+ sx->inblock.sock = sx->outblock.sock = 0; /* stdin */
+ smtp_port_for_connect(sx->host, sx->port); /* Record the port that was used */
+ }
smtp_command = big_buffer;
- sx->host->port = sx->port; /* Record the port that was used */
+ sx->helo_data = NULL; /* ensure we re-expand ob->helo_data */
+
+ /* For a continued connection with TLS being proxied for us, or a
+ held-open verify connection with TLS, nothing more to do. */
+
+ if ( continue_proxy_cipher
+ || (cutthrough.fd >= 0 && cutthrough.callout_hold_only && cutthrough.is_tls)
+ )
+ {
+ sx->peer_offered = smtp_peer_options;
+ pipelining_active = !!(smtp_peer_options & PEER_OFFERED_PIPE);
+ HDEBUG(D_transport) debug_printf("continued connection, %s TLS\n",
+ continue_proxy_cipher ? "proxied" : "verify conn with");
+ return OK;
+ }
+ HDEBUG(D_transport) debug_printf("continued connection, no TLS\n");
}
/* If TLS is available on this connection, whether continued or not, attempt to
#ifdef SUPPORT_TLS
if ( smtp_peer_options & PEER_OFFERED_TLS
&& !suppress_tls
- && verify_check_given_host(&sx->ob->hosts_avoid_tls, sx->host) != OK)
+ && verify_check_given_host(&sx->ob->hosts_avoid_tls, sx->host) != OK
+ && ( !sx->verify
+ || verify_check_given_host(&sx->ob->hosts_verify_avoid_tls, sx->host) != OK
+ ) )
{
uschar buffer2[4096];
- if (smtp_write_command(&sx->outblock, FALSE, "STARTTLS\r\n") < 0)
+ if (smtp_write_command(&sx->outblock, SCMD_FLUSH, "STARTTLS\r\n") < 0)
goto SEND_FAILED;
/* If there is an I/O error, transmission of this message is deferred. If
)
{
Ustrncpy(sx->buffer, buffer2, sizeof(sx->buffer));
+ sx->buffer[sizeof(sx->buffer)-1] = '\0';
goto RESPONSE_FAILED;
}
}
TLS_NEGOTIATE:
{
address_item * addr;
- int rc = tls_client_start(sx->inblock.sock, sx->host, sx->addrlist, sx->tblock
+ uschar * errstr;
+ int rc = tls_client_start(sx->inblock.sock, sx->host, sx->addrlist, sx->tblock,
# ifdef EXPERIMENTAL_DANE
- , sx->dane ? &tlsa_dnsa : NULL
+ sx->dane ? &tlsa_dnsa : NULL,
# endif
- );
+ &errstr);
/* TLS negotiation failed; give an error. From outside, this function may
be called again to try in clear on a new connection, if the options permit
{
# ifdef EXPERIMENTAL_DANE
if (sx->dane) log_write(0, LOG_MAIN,
- "DANE attempt failed; no TLS connection to %s [%s]",
- sx->host->name, sx->host->address);
+ "DANE attempt failed; TLS connection to %s [%s]: %s",
+ sx->host->name, sx->host->address, errstr);
# endif
- save_errno = ERRNO_TLSFAILURE;
- message = US"failure while setting up TLS session";
+ errno = ERRNO_TLSFAILURE;
+ message = string_sprintf("TLS session: %s", errstr);
sx->send_quit = FALSE;
goto TLS_FAILED;
}
debug_printf("not sending EHLO (host matches hosts_avoid_esmtp)\n");
}
- if (smtp_write_command(&sx->outblock, FALSE, "%s %s\r\n",
+ if (smtp_write_command(&sx->outblock, SCMD_FLUSH, "%s %s\r\n",
sx->lmtp ? "LHLO" : greeting_cmd, sx->helo_data) < 0)
goto SEND_FAILED;
good_response = smtp_read_response(&sx->inblock, sx->buffer, sizeof(sx->buffer),
|| verify_check_given_host(&sx->ob->hosts_require_tls, sx->host) == OK
)
{
- save_errno = ERRNO_TLSREQUIRED;
+ errno = ERRNO_TLSREQUIRED;
message = string_sprintf("a TLS session is required, but %s",
smtp_peer_options & PEER_OFFERED_TLS
? "an attempt to start TLS failed" : "the server did not offer TLS support");
{
if (sx->esmtp || sx->lmtp)
{
- sx->peer_offered = ehlo_response(sx->buffer, Ustrlen(sx->buffer),
+ sx->peer_offered = ehlo_response(sx->buffer,
0 /* no TLS */
| (sx->lmtp && sx->ob->lmtp_ignore_quota ? PEER_OFFERED_IGNQ : 0)
| PEER_OFFERED_CHUNKING
{
int code;
- uschar * set_message;
RESPONSE_FAILED:
- {
- save_errno = errno;
message = NULL;
- sx->send_quit = check_response(sx->host, &save_errno, sx->addrlist->more_errno,
+ sx->send_quit = check_response(sx->host, &errno, sx->addrlist->more_errno,
sx->buffer, &code, &message, &pass_message);
goto FAILED;
- }
SEND_FAILED:
- {
- save_errno = errno;
code = '4';
message = US string_sprintf("send() to %s [%s] failed: %s",
- sx->host->name, sx->host->address, strerror(save_errno));
+ sx->host->name, sx->host->address, strerror(errno));
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 save_errno, and setting_up will always be true. Treat as
+ 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"
+ " (EHLO response was: %s)", smtp_command, sx->buffer);
+ sx->send_quit = FALSE;
+ goto FAILED;
+
#ifdef SUPPORT_TLS
TLS_FAILED:
- code = '4';
+ code = '4';
+ goto FAILED;
#endif
/* The failure happened while setting up the call; see if the failure was
a 5xx response (this will either be on connection, or following HELO - a 5xx
- after EHLO causes it to try HELO). If so, fail all addresses, as this host is
- never going to accept them. For other errors during setting up (timeouts or
- whatever), defer all addresses, and yield DEFER, so that the host is not
- tried again for a while. */
-
- FAILED:
+ after EHLO causes it to try HELO). If so, and there are no more hosts to try,
+ fail all addresses, as this host is never going to accept them. For other
+ errors during setting up (timeouts or whatever), defer all addresses, and
+ yield DEFER, so that the host is not tried again for a while.
+
+ XXX This peeking for another host feels like a layering violation. We want
+ to note the host as unusable, but down here we shouldn't know if this was
+ the last host to try for the addr(list). Perhaps the upper layer should be
+ the one to do set_errno() ? The problem is that currently the addr is where
+ errno etc. are stashed, but until we run out of hosts to try the errors are
+ host-specific. Maybe we should enhance the host_item definition? */
+
+FAILED:
sx->ok = FALSE; /* For when reached by GOTO */
- set_message = message;
-
- yield = code == '5'
+ set_errno(sx->addrlist, errno, message,
+ sx->host->next
+ ? DEFER
+ : code == '5'
#ifdef SUPPORT_I18N
- || errno == ERRNO_UTF8_FWD
+ || errno == ERRNO_UTF8_FWD
#endif
- ? FAIL : DEFER;
-
- set_errno(sx->addrlist, save_errno, set_message, yield, pass_message, sx->host
+ ? FAIL : DEFER,
+ pass_message, sx->host
#ifdef EXPERIMENTAL_DSN_INFO
, sx->smtp_greeting, sx->helo_response
#endif
);
+ yield = DEFER;
}
SEND_QUIT:
if (sx->send_quit)
- (void)smtp_write_command(&sx->outblock, FALSE, "QUIT\r\n");
-
-/*END_OFF:*/
+ (void)smtp_write_command(&sx->outblock, SCMD_FLUSH, "QUIT\r\n");
#ifdef SUPPORT_TLS
tls_close(FALSE, TRUE);
/* Close the socket, and return the appropriate value, first setting
works because the NULL setting is passed back to the calling process, and
remote_max_parallel is forced to 1 when delivering over an existing connection,
+*/
-If all went well and continue_more is set, we shouldn't actually get here if
-there are further addresses, as the return above will be taken. However,
-writing RSET might have failed, or there may be other addresses whose hosts are
-specified in the transports, and therefore not visible at top level, in which
-case continue_more won't get set. */
-
-HDEBUG(D_transport|D_acl|D_v) debug_printf(" SMTP(close)>>\n");
+HDEBUG(D_transport|D_acl|D_v) debug_printf_indent(" SMTP(close)>>\n");
if (sx->send_quit)
{
shutdown(sx->outblock.sock, SHUT_WR);
if (fcntl(sx->inblock.sock, F_SETFL, O_NONBLOCK) == 0)
for (rc = 16; read(sx->inblock.sock, sx->inbuffer, sizeof(sx->inbuffer)) > 0 && rc > 0;)
rc--; /* drain socket */
+ sx->send_quit = FALSE;
}
(void)close(sx->inblock.sock);
+sx->inblock.sock = sx->outblock.sock = -1;
#ifndef DISABLE_EVENT
(void) event_raise(sx->tblock->event_action, US"tcp:close", NULL);
if (sx->peer_offered & PEER_OFFERED_SIZE)
{
+/*XXX problem here under spool_files_wireformat?
+Or just forget about lines? Or inflate by a fixed proportion? */
+
sprintf(CS p, " SIZE=%d", message_size+message_linecount+sx->ob->size_addition);
while (*p) p++;
}
}
+
+/*
+Return:
+ 0 good, rcpt results in addr->transport_return (PENDING_OK, DEFER, FAIL)
+ -1 MAIL response error
+ -2 any non-MAIL read i/o error
+ -3 non-MAIL response timeout
+ -4 internal error; channel still usable
+ -5 transmit failed
+ */
+
+int
+smtp_write_mail_and_rcpt_cmds(smtp_context * sx, int * yield)
+{
+address_item * addr;
+int address_count;
+int rc;
+
+if (build_mailcmd_options(sx, sx->first_addr) != OK)
+ {
+ *yield = ERROR;
+ return -4;
+ }
+
+/* From here until we send the DATA command, we can make use of PIPELINING
+if the server host supports it. The code has to be able to check the responses
+at any point, for when the buffer fills up, so we write it totally generally.
+When PIPELINING is off, each command written reports that it has flushed the
+buffer. */
+
+sx->pending_MAIL = TRUE; /* The block starts with MAIL */
+
+ {
+ uschar * s = sx->from_addr;
+#ifdef SUPPORT_I18N
+ uschar * errstr = NULL;
+
+ /* If we must downconvert, do the from-address here. Remember we had to
+ for the to-addresses (done below), and also (ugly) for re-doing when building
+ the delivery log line. */
+
+ if ( sx->addrlist->prop.utf8_msg
+ && (sx->addrlist->prop.utf8_downcvt || !(sx->peer_offered & PEER_OFFERED_UTF8))
+ )
+ {
+ if (s = string_address_utf8_to_alabel(s, &errstr), errstr)
+ {
+ set_errno_nohost(sx->addrlist, ERRNO_EXPANDFAIL, errstr, DEFER, FALSE);
+ *yield = ERROR;
+ return -4;
+ }
+ setflag(sx->addrlist, af_utf8_downcvt);
+ }
+#endif
+
+ rc = smtp_write_command(&sx->outblock, pipelining_active ? SCMD_BUFFER : SCMD_FLUSH,
+ "MAIL FROM:<%s>%s\r\n", s, sx->buffer);
+ }
+
+mail_command = string_copy(big_buffer); /* Save for later error message */
+
+switch(rc)
+ {
+ case -1: /* Transmission error */
+ return -5;
+
+ case +1: /* Cmd was sent */
+ if (!smtp_read_response(&sx->inblock, sx->buffer, sizeof(sx->buffer), '2',
+ sx->ob->command_timeout))
+ {
+ if (errno == 0 && sx->buffer[0] == '4')
+ {
+ errno = ERRNO_MAIL4XX;
+ sx->addrlist->more_errno |= ((sx->buffer[1] - '0')*10 + sx->buffer[2] - '0') << 8;
+ }
+ return -1;
+ }
+ sx->pending_MAIL = FALSE;
+ break;
+
+ /* otherwise zero: command queued for pipeline */
+ }
+
+/* Pass over all the relevant recipient addresses for this host, which are the
+ones that have status PENDING_DEFER. If we are using PIPELINING, we can send
+several before we have to read the responses for those seen so far. This
+checking is done by a subroutine because it also needs to be done at the end.
+Send only up to max_rcpt addresses at a time, leaving next_addr pointing to
+the next one if not all are sent.
+
+In the MUA wrapper situation, we want to flush the PIPELINING buffer for the
+last address because we want to abort if any recipients have any kind of
+problem, temporary or permanent. We know that all recipient addresses will have
+the PENDING_DEFER status, because only one attempt is ever made, and we know
+that max_rcpt will be large, so all addresses will be done at once.
+
+For verify we flush the pipeline after any (the only) rcpt address. */
+
+for (addr = sx->first_addr, address_count = 0;
+ addr && address_count < sx->max_rcpt;
+ addr = addr->next) if (addr->transport_return == PENDING_DEFER)
+ {
+ int count;
+ BOOL no_flush;
+ uschar * rcpt_addr;
+
+ addr->dsn_aware = sx->peer_offered & PEER_OFFERED_DSN
+ ? dsn_support_yes : dsn_support_no;
+
+ address_count++;
+ no_flush = pipelining_active && !sx->verify
+ && (!mua_wrapper || addr->next && address_count < sx->max_rcpt);
+
+ build_rcptcmd_options(sx, addr);
+
+ /* Now send the RCPT command, and process outstanding responses when
+ necessary. After a timeout on RCPT, we just end the function, leaving the
+ yield as OK, because this error can often mean that there is a problem with
+ just one address, so we don't want to delay the host. */
+
+ rcpt_addr = transport_rcpt_address(addr, sx->tblock->rcpt_include_affixes);
+
+#ifdef SUPPORT_I18N
+ if ( testflag(sx->addrlist, af_utf8_downcvt)
+ && !(rcpt_addr = string_address_utf8_to_alabel(rcpt_addr, NULL))
+ )
+ {
+ /*XXX could we use a per-address errstr here? Not fail the whole send? */
+ errno = ERRNO_EXPANDFAIL;
+ return -5; /*XXX too harsh? */
+ }
+#endif
+
+ count = smtp_write_command(&sx->outblock, no_flush ? SCMD_BUFFER : SCMD_FLUSH,
+ "RCPT TO:<%s>%s%s\r\n", rcpt_addr, sx->igquotstr, sx->buffer);
+
+ if (count < 0) return -5;
+ if (count > 0)
+ {
+ switch(sync_responses(sx, count, 0))
+ {
+ case 3: sx->ok = TRUE; /* 2xx & 5xx => OK & progress made */
+ case 2: sx->completed_addr = TRUE; /* 5xx (only) => progress made */
+ break;
+
+ case 1: sx->ok = TRUE; /* 2xx (only) => OK, but if LMTP, */
+ if (!sx->lmtp) /* can't tell about progress yet */
+ sx->completed_addr = TRUE;
+ case 0: /* No 2xx or 5xx, but no probs */
+ break;
+
+ case -1: return -3; /* Timeout on RCPT */
+ case -2: return -2; /* non-MAIL read i/o error */
+ default: return -1; /* any MAIL error */
+ }
+ sx->pending_MAIL = FALSE; /* Dealt with MAIL */
+ }
+ } /* Loop for next address */
+
+sx->next_addr = addr;
+return 0;
+}
+
+
+#ifdef SUPPORT_TLS
+/*****************************************************
+* 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.
+
+Arguments:
+ buf space to use for buffering
+ bufsiz size of buffer
+ proxy_fd comms to proxied process
+ timeout per-read timeout, seconds
+*/
+
+void
+smtp_proxy_tls(uschar * buf, size_t bsize, int proxy_fd, int timeout)
+{
+fd_set rfds, efds;
+int max_fd = MAX(proxy_fd, tls_out.active) + 1;
+int rc, i, fd_bits, nbytes;
+
+set_process_info("proxying TLS connection for continued transport");
+FD_ZERO(&rfds);
+FD_SET(tls_out.active, &rfds);
+FD_SET(proxy_fd, &rfds);
+
+for (fd_bits = 3; fd_bits; )
+ {
+ time_t time_left = timeout;
+ time_t time_start = time(NULL);
+
+ /* wait for data */
+ efds = rfds;
+ do
+ {
+ struct timeval tv = { time_left, 0 };
+
+ rc = select(max_fd,
+ (SELECT_ARG2_TYPE *)&rfds, NULL, (SELECT_ARG2_TYPE *)&efds, &tv);
+
+ if (rc < 0 && errno == EINTR)
+ if ((time_left -= time(NULL) - time_start) > 0) continue;
+
+ if (rc <= 0)
+ {
+ DEBUG(D_transport) if (rc == 0) debug_printf("%s: timed out\n", __FUNCTION__);
+ return;
+ }
+
+ if (FD_ISSET(tls_out.active, &efds) || FD_ISSET(proxy_fd, &efds))
+ {
+ DEBUG(D_transport) debug_printf("select: exceptional cond on %s fd\n",
+ FD_ISSET(proxy_fd, &efds) ? "proxy" : "tls");
+ return;
+ }
+ }
+ while (rc < 0 || !(FD_ISSET(tls_out.active, &rfds) || FD_ISSET(proxy_fd, &rfds)));
+
+ /* handle inbound data */
+ if (FD_ISSET(tls_out.active, &rfds))
+ if ((rc = tls_read(FALSE, buf, bsize)) <= 0)
+ {
+ fd_bits &= ~1;
+ FD_CLR(tls_out.active, &rfds);
+ shutdown(proxy_fd, SHUT_WR);
+ timeout = 5;
+ }
+ else
+ {
+ for (nbytes = 0; rc - nbytes > 0; nbytes += i)
+ if ((i = write(proxy_fd, buf + nbytes, rc - nbytes)) < 0) return;
+ }
+ 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)
+ {
+ fd_bits = 0;
+ tls_close(FALSE, TRUE);
+ }
+ else
+ {
+ for (nbytes = 0; rc - nbytes > 0; nbytes += i)
+ if ((i = tls_write(FALSE, buf + nbytes, rc - nbytes, FALSE)) < 0)
+ return;
+ }
+ else if (fd_bits & 2)
+ FD_SET(proxy_fd, &rfds);
+ }
+}
+#endif
+
+
/*************************************************
* Deliver address list to given host *
*************************************************/
failed by one of them.
host host to deliver to
host_af AF_INET or AF_INET6
- port default TCP/IP port to use, in host byte order
+ defport default TCP/IP port to use if host does not specify, in host
+ byte order
interface interface to bind to, or NULL
tblock transport instance block
message_defer set TRUE if yield is OK, but all addresses were deferred
*/
static int
-smtp_deliver(address_item *addrlist, host_item *host, int host_af, int port,
+smtp_deliver(address_item *addrlist, host_item *host, int host_af, int defport,
uschar *interface, transport_instance *tblock,
BOOL *message_defer, BOOL suppress_tls)
{
address_item *addr;
-address_item *sync_addr;
-address_item *first_addr = addrlist;
int yield = OK;
-int address_count;
int save_errno;
int rc;
time_t start_delivery_time = time(NULL);
-BOOL completed_address = FALSE;
-
-
BOOL pass_message = FALSE;
uschar *message = NULL;
uschar new_message_id[MESSAGE_ID_LENGTH + 1];
smtp_context sx;
suppress_tls = suppress_tls; /* stop compiler warning when no TLS support */
+*message_defer = FALSE;
sx.addrlist = addrlist;
sx.host = host;
sx.host_af = host_af,
-sx.port = port;
+sx.port = defport;
sx.interface = interface;
+sx.helo_data = NULL;
sx.tblock = tblock;
+sx.verify = FALSE;
/* Get the channel set up ready for a message (MAIL FROM being the next
SMTP command to send */
-if ((rc = smtp_setup_conn(&sx, message_defer, suppress_tls, FALSE)) != OK)
+if ((rc = smtp_setup_conn(&sx, suppress_tls)) != OK)
return rc;
/* If there is a filter command specified for this transport, we can now
set it up. This cannot be done until the identify of the host is known. */
-if (tblock->filter_command != NULL)
+if (tblock->filter_command)
{
- BOOL rc;
- uschar fbuf[64];
- sprintf(CS fbuf, "%.50s transport", tblock->name);
- rc = transport_set_up_command(&transport_filter_argv, tblock->filter_command,
- TRUE, DEFER, addrlist, fbuf, NULL);
transport_filter_timeout = tblock->filter_timeout;
/* On failure, copy the error to all addresses, abandon the SMTP call, and
yield ERROR. */
- if (!rc)
+ if (!transport_set_up_command(&transport_filter_argv,
+ tblock->filter_command, TRUE, DEFER, addrlist,
+ string_sprintf("%.50s transport", tblock->name), NULL))
{
set_errno_nohost(addrlist->next, addrlist->basic_errno, addrlist->message, DEFER,
FALSE);
}
}
+sx.first_addr = addrlist;
/* For messages that have more than the maximum number of envelope recipients,
we want to send several transactions down the same SMTP connection. (See
transaction to handle. */
SEND_MESSAGE:
-sync_addr = first_addr;
+sx.from_addr = return_path;
+sx.sync_addr = sx.first_addr;
sx.ok = FALSE;
sx.send_rset = TRUE;
-completed_address = FALSE;
+sx.completed_addr = FALSE;
-/* Initiate a message transfer. */
+/* If we are a continued-connection-after-verify the MAIL and RCPT
+commands were already sent; do not re-send but do mark the addrs as
+having been accepted up to RCPT stage. A traditional cont-conn
+always has a sequence number greater than one. */
-if (build_mailcmd_options(&sx, first_addr) != OK)
+if (continue_hostname && continue_sequence == 1)
{
- yield = ERROR;
- goto SEND_QUIT;
- }
-
-/* From here until we send the DATA command, we can make use of PIPELINING
-if the server host supports it. The code has to be able to check the responses
-at any point, for when the buffer fills up, so we write it totally generally.
-When PIPELINING is off, each command written reports that it has flushed the
-buffer. */
-
-sx.pending_MAIL = TRUE; /* The block starts with MAIL */
-
- {
- uschar * s = return_path;
-#ifdef SUPPORT_I18N
- uschar * errstr = NULL;
-
- /* If we must downconvert, do the from-address here. Remember we had to
- for the to-addresses (done below), and also (ugly) for re-doing when building
- the delivery log line. */
-
- if ( addrlist->prop.utf8_msg
- && (addrlist->prop.utf8_downcvt || !(sx.peer_offered & PEER_OFFERED_UTF8))
- )
- {
- if (s = string_address_utf8_to_alabel(return_path, &errstr), errstr)
- {
- set_errno_nohost(addrlist, ERRNO_EXPANDFAIL, errstr, DEFER, FALSE);
- yield = ERROR;
- goto SEND_QUIT;
- }
- setflag(addrlist, af_utf8_downcvt);
- }
-#endif
-
- rc = smtp_write_command(&sx.outblock, pipelining_active,
- "MAIL FROM:<%s>%s\r\n", s, sx.buffer);
- }
+ address_item * addr;
-mail_command = string_copy(big_buffer); /* Save for later error message */
-
-switch(rc)
- {
- case -1: /* Transmission error */
- goto SEND_FAILED;
+ sx.peer_offered = smtp_peer_options;
+ sx.pending_MAIL = FALSE;
+ sx.ok = TRUE;
+ sx.next_addr = NULL;
- case +1: /* Block was sent */
- if (!smtp_read_response(&sx.inblock, sx.buffer, sizeof(sx.buffer), '2',
- sx.ob->command_timeout))
- {
- if (errno == 0 && sx.buffer[0] == '4')
- {
- errno = ERRNO_MAIL4XX;
- addrlist->more_errno |= ((sx.buffer[1] - '0')*10 + sx.buffer[2] - '0') << 8;
- }
- goto RESPONSE_FAILED;
- }
- sx.pending_MAIL = FALSE;
- break;
+ for (addr = addrlist; addr; addr = addr->next)
+ addr->transport_return = PENDING_OK;
}
-
-/* Pass over all the relevant recipient addresses for this host, which are the
-ones that have status PENDING_DEFER. If we are using PIPELINING, we can send
-several before we have to read the responses for those seen so far. This
-checking is done by a subroutine because it also needs to be done at the end.
-Send only up to max_rcpt addresses at a time, leaving first_addr pointing to
-the next one if not all are sent.
-
-In the MUA wrapper situation, we want to flush the PIPELINING buffer for the
-last address because we want to abort if any recipients have any kind of
-problem, temporary or permanent. We know that all recipient addresses will have
-the PENDING_DEFER status, because only one attempt is ever made, and we know
-that max_rcpt will be large, so all addresses will be done at once. */
-
-for (addr = first_addr, address_count = 0;
- addr && address_count < sx.max_rcpt;
- addr = addr->next) if (addr->transport_return == PENDING_DEFER)
+else
{
- int count;
- BOOL no_flush;
- uschar * rcpt_addr;
+ /* Initiate a message transfer. */
- addr->dsn_aware = sx.peer_offered & PEER_OFFERED_DSN
- ? dsn_support_yes : dsn_support_no;
-
- address_count++;
- no_flush = pipelining_active && (!mua_wrapper || addr->next);
-
- build_rcptcmd_options(&sx, addr);
-
- /* Now send the RCPT command, and process outstanding responses when
- necessary. After a timeout on RCPT, we just end the function, leaving the
- yield as OK, because this error can often mean that there is a problem with
- just one address, so we don't want to delay the host. */
-
- rcpt_addr = transport_rcpt_address(addr, tblock->rcpt_include_affixes);
-
-#ifdef SUPPORT_I18N
- if ( testflag(addrlist, af_utf8_downcvt)
- && !(rcpt_addr = string_address_utf8_to_alabel(rcpt_addr, NULL))
- )
+ switch(smtp_write_mail_and_rcpt_cmds(&sx, &yield))
{
- /*XXX could we use a per-address errstr here? Not fail the whole send? */
- errno = ERRNO_EXPANDFAIL;
- goto SEND_FAILED;
+ case 0: break;
+ case -1: case -2: goto RESPONSE_FAILED;
+ case -3: goto END_OFF;
+ case -4: goto SEND_QUIT;
+ default: goto SEND_FAILED;
}
-#endif
- count = smtp_write_command(&sx.outblock, no_flush, "RCPT TO:<%s>%s%s\r\n",
- rcpt_addr, sx.igquotstr, sx.buffer);
+ /* If we are an MUA wrapper, abort if any RCPTs were rejected, either
+ permanently or temporarily. We should have flushed and synced after the last
+ RCPT. */
- if (count < 0) goto SEND_FAILED;
- if (count > 0)
+ if (mua_wrapper)
{
- switch(sync_responses(first_addr, tblock->rcpt_include_affixes,
- &sync_addr, host, count, sx.ob->address_retry_include_sender,
- sx.pending_MAIL, 0, &sx.inblock, sx.ob->command_timeout, sx.buffer,
- sizeof(sx.buffer)))
- {
- case 3: sx.ok = TRUE; /* 2xx & 5xx => OK & progress made */
- case 2: completed_address = TRUE; /* 5xx (only) => progress made */
- break;
+ address_item * a;
+ unsigned cnt;
- case 1: sx.ok = TRUE; /* 2xx (only) => OK, but if LMTP, */
- if (!sx.lmtp) completed_address = TRUE; /* can't tell about progress yet */
- case 0: /* No 2xx or 5xx, but no probs */
- break;
-
- case -1: goto END_OFF; /* Timeout on RCPT */
- default: goto RESPONSE_FAILED; /* I/O error, or any MAIL error */
- }
- sx.pending_MAIL = FALSE; /* Dealt with MAIL */
+ for (a = sx.first_addr, cnt = 0; a && cnt < sx.max_rcpt; a = a->next, cnt++)
+ if (a->transport_return != PENDING_OK)
+ {
+ /*XXX could we find a better errno than 0 here? */
+ set_errno_nohost(addrlist, 0, a->message, FAIL,
+ testflag(a, af_pass_message));
+ sx.ok = FALSE;
+ break;
+ }
}
- } /* Loop for next address */
-
-/*XXX potential break point for verify-callouts here. The MAIL and
-RCPT handling is relevant there */
-
-/* If we are an MUA wrapper, abort if any RCPTs were rejected, either
-permanently or temporarily. We should have flushed and synced after the last
-RCPT. */
-
-if (mua_wrapper)
- {
- address_item *badaddr;
- for (badaddr = first_addr; badaddr; badaddr = badaddr->next)
- if (badaddr->transport_return != PENDING_OK)
- {
- /*XXX could we find a better errno than 0 here? */
- set_errno_nohost(addrlist, 0, badaddr->message, FAIL,
- testflag(badaddr, af_pass_message));
- sx.ok = FALSE;
- break;
- }
}
/* If ok is TRUE, we know we have got at least one good recipient, and must now
if ( !(sx.peer_offered & PEER_OFFERED_CHUNKING)
&& (sx.ok || (pipelining_active && !mua_wrapper)))
{
- int count = smtp_write_command(&sx.outblock, FALSE, "DATA\r\n");
+ int count = smtp_write_command(&sx.outblock, SCMD_FLUSH, "DATA\r\n");
if (count < 0) goto SEND_FAILED;
- switch(sync_responses(first_addr, tblock->rcpt_include_affixes, &sync_addr,
- host, count, sx.ob->address_retry_include_sender, sx.pending_MAIL,
- sx.ok ? +1 : -1, &sx.inblock, sx.ob->command_timeout, sx.buffer, sizeof(sx.buffer)))
+ switch(sync_responses(&sx, count, sx.ok ? +1 : -1))
{
case 3: sx.ok = TRUE; /* 2xx & 5xx => OK & progress made */
- case 2: completed_address = TRUE; /* 5xx (only) => progress made */
+ case 2: sx.completed_addr = TRUE; /* 5xx (only) => progress made */
break;
case 1: sx.ok = TRUE; /* 2xx (only) => OK, but if LMTP, */
- if (!sx.lmtp) completed_address = TRUE; /* can't tell about progress yet */
+ if (!sx.lmtp) sx.completed_addr = TRUE; /* can't tell about progress yet */
case 0: break; /* No 2xx or 5xx, but no probs */
case -1: goto END_OFF; /* Timeout on RCPT */
if (!(sx.peer_offered & PEER_OFFERED_CHUNKING) && !sx.ok)
{
/* Save the first address of the next batch. */
- first_addr = addr;
+ sx.first_addr = sx.next_addr;
sx.ok = TRUE;
}
else
{
transport_ctx tctx = {
+ sx.inblock.sock,
tblock,
addrlist,
US".", US"..", /* Escaping strings */
tctx.check_string = tctx.escape_string = NULL;
tctx.options |= topt_use_bdat;
tctx.chunk_cb = smtp_chunk_cmd_callback;
- tctx.inblock = &sx.inblock;
- tctx.outblock = &sx.outblock;
- tctx.host = host;
- tctx.first_addr = first_addr;
- tctx.sync_addr = &sync_addr;
- tctx.pending_MAIL = sx.pending_MAIL;
- tctx.pending_BDAT = FALSE;
- tctx.good_RCPT = sx.ok;
- tctx.completed_address = &completed_address;
- tctx.cmd_count = 0;
- tctx.buffer = sx.buffer;
+ sx.pending_BDAT = FALSE;
+ sx.good_RCPT = sx.ok;
+ sx.cmd_count = 0;
+ tctx.smtp_context = &sx;
}
else
tctx.options |= topt_end_dot;
/* Save the first address of the next batch. */
- first_addr = addr;
+ sx.first_addr = sx.next_addr;
/* Responses from CHUNKING commands go in buffer. Otherwise,
there has not been a response. */
transport_count = 0;
#ifndef DISABLE_DKIM
- sx.ok = dkim_transport_write_message(sx.inblock.sock, &tctx, &sx.ob->dkim);
+ sx.ok = dkim_transport_write_message(&tctx, &sx.ob->dkim, CUSS &message);
#else
- sx.ok = transport_write_message(sx.inblock.sock, &tctx, 0);
+ sx.ok = transport_write_message(&tctx, 0);
#endif
/* transport_write_message() uses write() because it is called from other
Or, when CHUNKING, it can be a protocol-detected failure. */
if (!sx.ok)
- goto RESPONSE_FAILED;
+ if (message) goto SEND_FAILED;
+ else goto RESPONSE_FAILED;
/* We used to send the terminating "." explicitly here, but because of
buffering effects at both ends of TCP/IP connections, you don't gain
smtp_command = US"end of data";
- if (sx.peer_offered & PEER_OFFERED_CHUNKING && tctx.cmd_count > 1)
+ if (sx.peer_offered & PEER_OFFERED_CHUNKING && sx.cmd_count > 1)
{
/* Reap any outstanding MAIL & RCPT commands, but not a DATA-go-ahead */
- switch(sync_responses(first_addr, tblock->rcpt_include_affixes, &sync_addr,
- host, tctx.cmd_count-1, sx.ob->address_retry_include_sender,
- sx.pending_MAIL, 0,
- &sx.inblock, sx.ob->command_timeout, sx.buffer, sizeof(sx.buffer)))
+ switch(sync_responses(&sx, sx.cmd_count-1, 0))
{
case 3: sx.ok = TRUE; /* 2xx & 5xx => OK & progress made */
- case 2: completed_address = TRUE; /* 5xx (only) => progress made */
+ case 2: sx.completed_addr = TRUE; /* 5xx (only) => progress made */
break;
case 1: sx.ok = TRUE; /* 2xx (only) => OK, but if LMTP, */
- if (!sx.lmtp) completed_address = TRUE; /* can't tell about progress yet */
+ if (!sx.lmtp) sx.completed_addr = TRUE; /* can't tell about progress yet */
case 0: break; /* No 2xx or 5xx, but no probs */
case -1: goto END_OFF; /* Timeout on RCPT */
/* Process all transported addresses - for LMTP or PRDR, read a status for
each one. */
- for (addr = addrlist; addr != first_addr; addr = addr->next)
+ for (addr = addrlist; addr != sx.first_addr; addr = addr->next)
{
if (addr->transport_return != PENDING_OK) continue;
}
continue;
}
- completed_address = TRUE; /* NOW we can set this flag */
+ sx.completed_addr = TRUE; /* NOW we can set this flag */
if (LOGGING(smtp_confirmation))
{
const uschar *s = string_printing(sx.buffer);
else
sprintf(CS sx.buffer, "%.500s\n", addr->unique);
- DEBUG(D_deliver) debug_printf("journalling %s\n", 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 "
errno = ERRNO_DATA4XX;
addrlist->more_errno |= ((sx.buffer[1] - '0')*10 + sx.buffer[2] - '0') << 8;
}
- for (addr = addrlist; addr != first_addr; addr = addr->next)
+ for (addr = addrlist; addr != sx.first_addr; addr = addr->next)
if (sx.buffer[0] == '5' || addr->transport_return == OK)
addr->transport_return = PENDING_OK; /* allow set_errno action */
goto RESPONSE_FAILED;
}
/* Update the journal, or setup retry. */
- for (addr = addrlist; addr != first_addr; addr = addr->next)
+ for (addr = addrlist; addr != sx.first_addr; addr = addr->next)
if (addr->transport_return == OK)
{
if (testflag(addr, af_homonym))
{
save_errno = errno;
code = '4';
- message = US string_sprintf("send() to %s [%s] failed: %s",
- host->name, host->address, strerror(save_errno));
+ message = string_sprintf("send() to %s [%s] failed: %s",
+ host->name, host->address, message ? message : US strerror(save_errno));
sx.send_quit = FALSE;
goto FAILED;
}
DEBUG(D_transport)
debug_printf("ok=%d send_quit=%d send_rset=%d continue_more=%d "
"yield=%d first_address is %sNULL\n", sx.ok, sx.send_quit,
- sx.send_rset, continue_more, yield, first_addr ? "not " : "");
+ sx.send_rset, continue_more, yield, sx.first_addr ? "not " : "");
-if (completed_address && sx.ok && sx.send_quit)
+if (sx.completed_addr && sx.ok && sx.send_quit)
{
BOOL more;
smtp_compare_t t_compare;
t_compare.tblock = tblock;
t_compare.current_sender_address = sender_address;
- if ( first_addr != NULL
+ if ( sx.first_addr != NULL
|| continue_more
- || ( ( tls_out.active < 0
+ || (
+#ifdef SUPPORT_TLS
+ ( tls_out.active < 0 && !continue_proxy_cipher
|| verify_check_given_host(&sx.ob->hosts_nopass_tls, host) != OK
)
&&
+#endif
transport_check_waiting(tblock->name, host->name,
tblock->connection_max_messages, new_message_id, &more,
(oicf)smtp_are_same_identities, (void*)&t_compare)
BOOL pass_message;
if (sx.send_rset)
- if (! (sx.ok = smtp_write_command(&sx.outblock, FALSE, "RSET\r\n") >= 0))
+ if (! (sx.ok = smtp_write_command(&sx.outblock, SCMD_FLUSH, "RSET\r\n") >= 0))
{
msg = US string_sprintf("send() to %s [%s] failed: %s", host->name,
- host->address, strerror(save_errno));
+ host->address, strerror(errno));
sx.send_quit = FALSE;
}
else if (! (sx.ok = smtp_read_response(&sx.inblock, sx.buffer,
if (sx.ok)
{
- if (first_addr != NULL) /* More addresses still to be sent */
+ int pfd[2];
+ int socket_fd = sx.inblock.sock;
+
+
+ if (sx.first_addr != NULL) /* More addresses still to be sent */
{ /* in this run of the transport */
continue_sequence++; /* Causes * in logging */
goto SEND_MESSAGE;
}
- if (continue_more) return yield; /* More addresses for another run */
- /* Pass the socket to a new Exim process. Before doing so, we must shut
- down TLS. Not all MTAs allow for the continuation of the SMTP session
- when TLS is shut down. We test for this by sending a new EHLO. If we
- don't get a good response, we don't attempt to pass the socket on. */
+ /* Unless caller said it already has more messages listed for this host,
+ pass the connection on to a new Exim process (below, the call to
+ transport_pass_socket). If the caller has more ready, just return with
+ the connection still open. */
#ifdef SUPPORT_TLS
if (tls_out.active >= 0)
- {
- tls_close(FALSE, TRUE);
- smtp_peer_options = smtp_peer_options_wrap;
- sx.ok = !sx.smtps
- && smtp_write_command(&sx.outblock, FALSE,
- "EHLO %s\r\n", sx.helo_data) >= 0
- && smtp_read_response(&sx.inblock, sx.buffer, sizeof(sx.buffer),
- '2', sx.ob->command_timeout);
- }
+ if ( continue_more
+ || verify_check_given_host(&sx.ob->hosts_noproxy_tls, host) == OK)
+ {
+ /* Before passing the socket on, or returning to caller with it still
+ open, we must shut down TLS. Not all MTAs allow for the continuation
+ of the SMTP session when TLS is shut down. We test for this by sending
+ a new EHLO. If we don't get a good response, we don't attempt to pass
+ the socket on. */
+
+ tls_close(FALSE, TRUE);
+ smtp_peer_options = smtp_peer_options_wrap;
+ sx.ok = !sx.smtps
+ && smtp_write_command(&sx.outblock, SCMD_FLUSH,
+ "EHLO %s\r\n", sx.helo_data) >= 0
+ && smtp_read_response(&sx.inblock, sx.buffer, sizeof(sx.buffer),
+ '2', sx.ob->command_timeout);
+
+ if (sx.ok && continue_more)
+ return yield; /* More addresses for another run */
+ }
+ else
+ {
+ /* Set up a pipe for proxying TLS for the new transport process */
+
+ smtp_peer_options |= PEER_OFFERED_TLS;
+ if (sx.ok = (socketpair(AF_UNIX, SOCK_STREAM, 0, pfd) == 0))
+ socket_fd = pfd[1];
+ else
+ set_errno(sx.first_addr, errno, US"internal allocation problem",
+ DEFER, FALSE, host
+# ifdef EXPERIMENTAL_DSN_INFO
+ , sx.smtp_greeting, sx.helo_response
+# endif
+ );
+ }
+ else
#endif
+ if (continue_more)
+ return yield; /* More addresses for another run */
/* If the socket is successfully passed, we mustn't send QUIT (or
indeed anything!) from here. */
propagate it from the initial
*/
if (sx.ok && transport_pass_socket(tblock->name, host->name,
- host->address, new_message_id, sx.inblock.sock))
+ host->address, new_message_id, socket_fd))
+ {
sx.send_quit = FALSE;
+
+ /* 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. */
+#ifdef SUPPORT_TLS
+ if (tls_out.active >= 0)
+ {
+ int pid = fork();
+ if (pid > 0) /* parent */
+ {
+ DEBUG(D_transport) debug_printf("proxy-proc inter-pid %d\n", pid);
+ close(pfd[0]);
+ waitpid(pid, NULL, 0);
+ tls_close(FALSE, FALSE);
+ (void)close(sx.inblock.sock);
+ continue_transport = NULL;
+ 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 ? EXIT_FAILURE : EXIT_SUCCESS);
+ }
+ smtp_proxy_tls(sx.buffer, sizeof(sx.buffer), pfd[0], sx.ob->command_timeout);
+ exim_exit(0);
+ }
+ }
+#endif
+ }
}
/* If RSET failed and there are addresses left, they get deferred. */
-
- else set_errno(first_addr, errno, msg, DEFER, FALSE, host
+ else
+ set_errno(sx.first_addr, errno, msg, DEFER, FALSE, host
#ifdef EXPERIMENTAL_DSN_INFO
, sx.smtp_greeting, sx.helo_response
#endif
operation, the old commented-out code was removed on 17-Sep-99. */
SEND_QUIT:
-if (sx.send_quit) (void)smtp_write_command(&sx.outblock, FALSE, "QUIT\r\n");
+if (sx.send_quit) (void)smtp_write_command(&sx.outblock, SCMD_FLUSH, "QUIT\r\n");
END_OFF:
specified in the transports, and therefore not visible at top level, in which
case continue_more won't get set. */
-HDEBUG(D_transport|D_acl|D_v) debug_printf(" SMTP(close)>>\n");
+HDEBUG(D_transport|D_acl|D_v) debug_printf_indent(" SMTP(close)>>\n");
if (sx.send_quit)
{
shutdown(sx.outblock.sock, SHUT_WR);
smtp_transport_closedown(transport_instance *tblock)
{
smtp_transport_options_block *ob =
- (smtp_transport_options_block *)(tblock->options_block);
+ (smtp_transport_options_block *)tblock->options_block;
smtp_inblock inblock;
smtp_outblock outblock;
uschar buffer[256];
outblock.cmd_count = 0;
outblock.authenticating = FALSE;
-(void)smtp_write_command(&outblock, FALSE, "QUIT\r\n");
+(void)smtp_write_command(&outblock, SCMD_FLUSH, "QUIT\r\n");
(void)smtp_read_response(&inblock, buffer, sizeof(buffer), '2',
ob->command_timeout);
(void)close(inblock.sock);
address_item *addrlist) /* addresses we are working on */
{
int cutoff_retry;
-int port;
+int defport;
int hosts_defer = 0;
int hosts_fail = 0;
int hosts_looked_up = 0;
for (host = hostlist; host; host = host->next)
debug_printf(" %s:%d\n", host->name, host->port);
}
- if (continue_hostname) debug_printf("already connected to %s [%s]\n",
- continue_hostname, continue_host_address);
+ if (continue_hostname)
+ debug_printf("already connected to %s [%s] (on fd %d)\n",
+ continue_hostname, continue_host_address,
+ cutthrough.fd >= 0 ? cutthrough.fd : 0);
}
/* Set the flag requesting that these hosts be added to the waiting
/* Sort out the default port. */
-if (!smtp_get_port(ob->port, addrlist, &port, tid)) return FALSE;
+if (!smtp_get_port(ob->port, addrlist, &defport, tid)) return FALSE;
/* For each host-plus-IP-address on the list:
commonly points to a configuration error, but the best action is still
to carry on for the next host. */
- if (rc == HOST_FIND_AGAIN || rc == HOST_FIND_FAILED)
+ if (rc == HOST_FIND_AGAIN || rc == HOST_FIND_SECURITY || rc == HOST_FIND_FAILED)
{
retry_add_item(addrlist, string_sprintf("R:%s", host->name), 0);
expired = FALSE;
{
if (addr->transport_return != DEFER) continue;
addr->basic_errno = ERRNO_UNKNOWNHOST;
- addr->message =
- string_sprintf("failed to lookup IP address for %s", host->name);
+ addr->message = string_sprintf(
+ rc == HOST_FIND_SECURITY
+ ? "lookup of IP address for %s was insecure"
+ : "failed to lookup IP address for %s",
+ host->name);
}
continue;
}
the default. */
pistring = string_sprintf(":%d", host->port == PORT_NONE
- ? port : host->port);
+ ? defport : host->port);
if (Ustrcmp(pistring, ":25") == 0) pistring = US"";
/* Select IPv4 or IPv6, and choose an outgoing interface. If the interface
host_is_expired = retry_check_address(addrlist->domain, host, pistring,
incl_ip, &retry_host_key, &retry_message_key);
- DEBUG(D_transport) debug_printf("%s [%s]%s status = %s\n", host->name,
+ DEBUG(D_transport) debug_printf("%s [%s]%s retry-status = %s\n", host->name,
(host->address == NULL)? US"" : host->address, pistring,
(host->status == hstatus_usable)? "usable" :
(host->status == hstatus_unusable)? "unusable" :
{
case hwhy_retry: hosts_retry++; break;
case hwhy_failed: hosts_fail++; break;
+ case hwhy_insecure:
case hwhy_deferred: hosts_defer++; break;
}
/* Attempt the delivery. */
total_hosts_tried++;
- rc = smtp_deliver(addrlist, thost, host_af, port, interface, tblock,
+ rc = smtp_deliver(addrlist, thost, host_af, defport, interface, tblock,
&message_defer, FALSE);
/* Yield is one of:
&& verify_check_given_host(&ob->hosts_require_tls, host) != OK
)
{
- log_write(0, LOG_MAIN, "TLS session failure: delivering unencrypted "
- "to %s [%s] (not in hosts_require_tls)", host->name, host->address);
+ log_write(0, LOG_MAIN,
+ "%s: delivering unencrypted to H=%s [%s] (not in hosts_require_tls)",
+ first_addr->message, host->name, host->address);
first_addr = prepare_addresses(addrlist, host);
- rc = smtp_deliver(addrlist, thost, host_af, port, interface, tblock,
+ 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);
return TRUE; /* Each address has its status */
}
+#endif /*!MACRO_PREDEF*/
/* vi: aw ai sw=2
*/
/* End of transport/smtp.c */