-/*
-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
+/* Send MAIL FROM and RCPT TO commands.
+See sw_mrc_t definition for return codes.
*/
-int
+sw_mrc_t
smtp_write_mail_and_rcpt_cmds(smtp_context * sx, int * yield)
{
address_item * addr;
if (build_mailcmd_options(sx, sx->first_addr) != OK)
{
*yield = ERROR;
- return -4;
+ return sw_mrc_bad_internal;
}
/* From here until we send the DATA command, we can make use of PIPELINING
{
set_errno_nohost(sx->addrlist, ERRNO_EXPANDFAIL, errstr, DEFER, FALSE, &sx->delivery_start);
*yield = ERROR;
- return -4;
+ return sw_mrc_bad_internal;
}
setflag(sx->addrlist, af_utf8_downcvt);
}
switch(rc)
{
case -1: /* Transmission error */
- return -5;
+ return sw_mrc_bad_mail;
case +1: /* Cmd was sent */
if (!smtp_read_response(sx, sx->buffer, sizeof(sx->buffer), '2',
errno = ERRNO_MAIL4XX;
sx->addrlist->more_errno |= ((sx->buffer[1] - '0')*10 + sx->buffer[2] - '0') << 8;
}
- return -1;
+ return sw_mrc_bad_mail;
}
sx->pending_MAIL = FALSE;
break;
{
/*XXX could we use a per-address errstr here? Not fail the whole send? */
errno = ERRNO_EXPANDFAIL;
- return -5; /*XXX too harsh? */
+ return sw_mrc_tx_fail; /*XXX too harsh? */
}
#endif
cmds_sent = smtp_write_command(sx, no_flush ? SCMD_BUFFER : SCMD_FLUSH,
"RCPT TO:<%s>%s%s\r\n", rcpt_addr, sx->igquotstr, sx->buffer);
- if (cmds_sent < 0) return -5;
+ if (cmds_sent < 0) return sw_mrc_tx_fail;
if (cmds_sent > 0)
{
switch(sync_responses(sx, cmds_sent, 0))
DEBUG(D_transport) debug_printf("seen 452 too-many-rcpts\n");
sx->RCPT_452 = FALSE;
/* sx->next_addr has been reset for fast_retry */
- return 0;
+ return sw_mrc_ok;
}
break;
- case RESP_RCPT_TIMEO: return -3; /* Timeout on RCPT */
- case RESP_RCPT_ERROR: return -2; /* non-MAIL read i/o error */
- default: return -1; /* any MAIL error */
+ case RESP_RCPT_TIMEO: return sw_mrc_nonmail_read_timeo;
+ case RESP_RCPT_ERROR: return sw_mrc_bad_read;
+ default: return sw_mrc_bad_mail; /* any MAIL error */
#ifndef DISABLE_PIPE_CONNECT
- case RESP_EPIPE_EHLO_ERR: return -1; /* non-2xx for pipelined banner or EHLO */
- case RESP_EHLO_ERR_TLS: return -1; /* TLS first-read error */
+ case RESP_EPIPE_EHLO_ERR: return sw_mrc_bad_mail; /* non-2xx for pipelined banner or EHLO */
+ case RESP_EHLO_ERR_TLS: return sw_mrc_bad_mail; /* TLS first-read error */
#endif
}
}
#else
sx->next_addr = addr;
#endif
-return 0;
+return sw_mrc_ok;
}
switch(smtp_write_mail_and_rcpt_cmds(sx, &yield))
{
- case 0: break;
- case -1: case -2: goto RESPONSE_FAILED;
- case -3: goto END_OFF;
- case -4: goto SEND_QUIT;
- default: goto SEND_FAILED;
+ case sw_mrc_ok: break;
+ case sw_mrc_bad_mail: goto RESPONSE_FAILED;
+ case sw_mrc_bad_read: goto RESPONSE_FAILED;
+ case sw_mrc_nonmail_read_timeo: goto END_OFF;
+ case sw_mrc_bad_internal: goto SEND_QUIT;
+ default: goto SEND_FAILED;
}
/* If we are an MUA wrapper, abort if any RCPTs were rejected, either
break;
case ERRNO_SMTPCLOSED:
- message_error = Ustrncmp(smtp_command,"end ",4) == 0;
+ /* If the peer closed the TCP connection after end-of-data, but before
+ we could send QUIT, do TLS close, etc - call it a message error.
+ Otherwise, if all the recipients have been dealt with, call a close no
+ error at all; each address_item should have a suitable result already
+ (2xx: PENDING_OK, 4xx: DEFER, 5xx: FAIL) */
+
+ if (!(message_error = Ustrncmp(smtp_command,"end ",4) == 0))
+ {
+ address_item * addr;
+ for (addr = sx->addrlist; addr; addr = addr->next)
+ if (addr->transport_return == PENDING_DEFER)
+ break;
+ if (!addr) /* all rcpts fates determined */
+ {
+ log_write(0, LOG_MAIN, "peer close after all rcpt responses;"
+ " converting i/o-error to no-error");
+ sx->ok = TRUE;
+ goto happy;
+ }
+ }
break;
#ifndef DISABLE_DKIM
break;
}
- /* Handle the cases that are treated as message errors. These are:
+ /* Handle the cases that are treated as message errors (as opposed to
+ host-errors). These are:
(a) negative response or timeout after MAIL
(b) negative response after DATA
can), so we do not pass such a connection on if the host matches
hosts_nopass_tls. */
+happy:
+
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,
*/
static address_item *
-prepare_addresses(address_item *addrlist, host_item *host)
+prepare_addresses(address_item * addrlist, host_item * host)
{
-address_item *first_addr = NULL;
+address_item * first_addr = NULL;
for (address_item * addr = addrlist; addr; addr = addr->next)
if (addr->transport_return == DEFER)
{
if (!first_addr) first_addr = addr;
addr->transport_return = PENDING_DEFER;
addr->basic_errno = 0;
- addr->more_errno = (host->mx >= 0)? 'M' : 'A';
+ addr->more_errno = host->mx >= 0 ? 'M' : 'A';
addr->message = NULL;
#ifndef DISABLE_TLS
addr->cipher = NULL;
BOOL
smtp_transport_entry(
- transport_instance *tblock, /* data for this instantiation */
- address_item *addrlist) /* addresses we are working on */
+ transport_instance * tblock, /* data for this instantiation */
+ address_item * addrlist) /* addresses we are working on */
{
int defport;
-int hosts_defer = 0;
-int hosts_fail = 0;
-int hosts_looked_up = 0;
-int hosts_retry = 0;
-int hosts_serial = 0;
-int hosts_total = 0;
-int total_hosts_tried = 0;
+int hosts_defer = 0, hosts_fail = 0, hosts_looked_up = 0;
+int hosts_retry = 0, hosts_serial = 0, hosts_total = 0, total_hosts_tried = 0;
BOOL expired = TRUE;
-uschar *expanded_hosts = NULL;
-uschar *pistring;
-uschar *tid = string_sprintf("%s transport", tblock->name);
-smtp_transport_options_block *ob = SOB tblock->options_block;
-host_item *hostlist = addrlist->host_list;
-host_item *host = NULL;
+uschar * expanded_hosts = NULL, * pistring;
+uschar * tid = string_sprintf("%s transport", tblock->name);
+smtp_transport_options_block * ob = SOB tblock->options_block;
+host_item * hostlist = addrlist->host_list, * host = NULL;
DEBUG(D_transport)
{
queue_smtp or a 2-stage queue run. This gets unset for certain
kinds of error, typically those that are specific to the message. */
-update_waiting = TRUE;
+update_waiting = TRUE;
/* If a host list is not defined for the addresses - they must all have the
same one in order to be passed to a single transport - or if the transport has
&& total_hosts_tried < ob->hosts_max_try_hardlimit;
host = nexthost)
{
- int rc;
- int host_af;
- BOOL host_is_expired = FALSE;
- BOOL message_defer = FALSE;
- BOOL some_deferred = FALSE;
- address_item *first_addr = NULL;
- uschar *interface = NULL;
- uschar *retry_host_key = NULL;
- uschar *retry_message_key = NULL;
- uschar *serialize_key = NULL;
+ int rc, host_af;
+ BOOL host_is_expired = FALSE, message_defer = FALSE, some_deferred = FALSE;
+ address_item * first_addr = NULL;
+ uschar * interface = NULL;
+ uschar * retry_host_key = NULL, * retry_message_key = NULL;
+ uschar * serialize_key = NULL;
/* Deal slightly better with a possible Linux kernel bug that results
in intermittent TFO-conn fails deep into the TCP flow. Bug 2907 tracks.
out the result of previous attempts, and finding the first address that
is still to be delivered. */
- first_addr = prepare_addresses(addrlist, host);
+ if (!(first_addr = prepare_addresses(addrlist, host)))
+ {
+ /* Obscure situation; at least one case (bug 3059, fixed) where
+ a previous host try returned DEFER, but having moved all
+ recipients away from DEFER (the waiting-to-be-done state). */
+ DEBUG(D_transport) debug_printf("no pending recipients\n");
+ goto END_TRANSPORT;
+ }
DEBUG(D_transport) debug_printf("delivering %s to %s [%s] (%s%s)\n",
message_id, host->name, host->address, addrlist->address,
{
host_item * thost;
/* Make a copy of the host if it is local to this invocation
- of the transport. */
+ of the transport. */
if (expanded_hosts)
{
if (rc == OK)
for (address_item * addr = addrlist; addr; addr = addr->next)
if (addr->transport_return == DEFER)
- {
- some_deferred = TRUE;
- break;
- }
+ { some_deferred = TRUE; break; }
/* If no addresses deferred or the result was ERROR, return. We do this for
ERROR because a failing filter set-up or add_headers expansion is likely to
fail for any host we try. */
if (rc == ERROR || (rc == OK && !some_deferred))
- {
- DEBUG(D_transport) debug_printf("Leaving %s transport\n", tblock->name);
- return TRUE; /* Each address has its status */
- }
+ goto END_TRANSPORT;
/* If the result was DEFER or some individual addresses deferred, let
the loop run to try other hosts with the deferred addresses, except for the
if ((rc == DEFER || some_deferred) && nexthost)
{
BOOL timedout;
- retry_config *retry = retry_find_config(host->name, NULL, 0, 0);
+ retry_config * retry = retry_find_config(host->name, NULL, 0, 0);
if (retry && retry->rules)
{