X-Git-Url: https://git.exim.org/exim.git/blobdiff_plain/02b41d7106c67bbe862d6a44373034684a03c00e..4c2471caf34b901ee481cd1e742b3620e734b16b:/src/src/transports/smtp.c diff --git a/src/src/transports/smtp.c b/src/src/transports/smtp.c index 1ce0cbe11..997281901 100644 --- a/src/src/transports/smtp.c +++ b/src/src/transports/smtp.c @@ -2,7 +2,7 @@ * 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" @@ -87,6 +87,8 @@ optionlist smtp_transport_options[] = { #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) }, @@ -218,7 +220,10 @@ 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 */ @@ -283,6 +288,8 @@ static uschar *smtp_command; /* Points to last cmd for error messages */ 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 */ @@ -510,82 +517,62 @@ check_response(host_item *host, int *errno_value, int more_errno, 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; @@ -603,7 +590,8 @@ if (*errno_value == 0 || *errno_value == ECONNRESET) *message = US string_sprintf("Remote host closed connection " "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; } @@ -735,21 +723,11 @@ subsequent general error, it will get reset accordingly. If not, it will get 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 @@ -761,39 +739,38 @@ Returns: 3 if at least one address had 2xx and one had 5xx */ 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; } @@ -804,7 +781,7 @@ if (pending_MAIL) 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; @@ -819,12 +796,15 @@ with an address by scanning for the next address whose status is PENDING_DEFER. 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; @@ -847,8 +827,8 @@ while (count-- > 0) 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; @@ -859,10 +839,10 @@ while (count-- > 0) 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; } @@ -872,14 +852,15 @@ while (count-- > 0) { 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; @@ -891,40 +872,42 @@ while (count-- > 0) { 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 */ @@ -932,27 +915,28 @@ while (count-- > 0) /* 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); } @@ -1310,9 +1294,11 @@ return Ustrcmp(current_local_identity, message_local_identity) == 0; -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) @@ -1363,28 +1349,34 @@ return checks; 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 */ if (chunk_size > 0) { - if((cmd_count = smtp_write_command(tctx->outblock, FALSE, "BDAT %u%s\r\n", + if((cmd_count = smtp_write_command(&sx->outblock, FALSE, "BDAT %u%s\r\n", chunk_size, flags & tc_chunk_last ? " LAST" : "") ) < 0) return ERROR; @@ -1392,13 +1384,13 @@ if (chunk_size > 0) 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) @@ -1406,53 +1398,47 @@ 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; } @@ -1467,8 +1453,6 @@ Arguments: ctx connection context 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. @@ -1480,15 +1464,13 @@ Returns: OK - the connection was made and the delivery attempted; to expand */ int -smtp_setup_conn(smtp_context * sx, 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; @@ -1572,7 +1554,7 @@ specially so they can be identified for retries. */ if (continue_hostname == NULL) { - if (verify) + if (sx->verify) HDEBUG(D_verify) debug_printf("interface=%s port=%d\n", sx->interface, sx->port); /* This puts port into host->port */ @@ -1583,15 +1565,14 @@ if (continue_hostname == NULL) if (sx->inblock.sock < 0) { uschar * msg = NULL; - int save_errno = errno; - if (verify) + if (sx->verify) { - msg = strerror(errno); + msg = US strerror(errno); HDEBUG(D_verify) debug_printf("connect: %s\n", msg); } set_errno_nohost(sx->addrlist, - save_errno == ETIMEDOUT ? ERRNO_CONNECTTIMEOUT : save_errno, - verify ? string_sprintf("could not connect: %s", msg) + errno == ETIMEDOUT ? ERRNO_CONNECTTIMEOUT : errno, + sx->verify ? string_sprintf("could not connect: %s", msg) : NULL, DEFER, FALSE); sx->send_quit = FALSE; @@ -1638,7 +1619,7 @@ if (continue_hostname == NULL) if (sx->helo_data) if (!(sx->helo_data = expand_string(sx->helo_data))) - if (verify) + 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); @@ -1650,7 +1631,7 @@ if (continue_hostname == NULL) if ((sx->helo_data = string_domain_utf8_to_alabel(sx->helo_data, &expand_string_message)), expand_string_message) - if (verify) + 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); @@ -1807,12 +1788,8 @@ goto SEND_QUIT; 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; @@ -1823,7 +1800,7 @@ goto SEND_QUIT; 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 */ ); @@ -1835,9 +1812,11 @@ goto SEND_QUIT; } } -/* 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. */ @@ -1849,10 +1828,33 @@ separate - we could match up by host ip+port as a bodge. */ 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 */ + sx->host->port = 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 @@ -1867,7 +1869,7 @@ for error analysis. */ if ( smtp_peer_options & PEER_OFFERED_TLS && !suppress_tls && verify_check_given_host(&sx->ob->hosts_avoid_tls, sx->host) != OK - && ( !verify + && ( !sx->verify || verify_check_given_host(&sx->ob->hosts_verify_avoid_tls, sx->host) != OK ) ) { @@ -1891,6 +1893,7 @@ if ( smtp_peer_options & PEER_OFFERED_TLS ) { Ustrncpy(sx->buffer, buffer2, sizeof(sx->buffer)); + sx->buffer[sizeof(sx->buffer)-1] = '\0'; goto RESPONSE_FAILED; } } @@ -1901,11 +1904,12 @@ if ( smtp_peer_options & PEER_OFFERED_TLS 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 @@ -1915,12 +1919,12 @@ if ( smtp_peer_options & PEER_OFFERED_TLS { # 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; } @@ -2006,7 +2010,7 @@ else if ( sx->smtps || 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"); @@ -2027,7 +2031,7 @@ if (continue_hostname == NULL { 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 @@ -2129,35 +2133,36 @@ return OK; { 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 @@ -2167,9 +2172,8 @@ return OK; whatever), defer all addresses, and yield DEFER, so that the host is not tried again for a while. */ - FAILED: +FAILED: sx->ok = FALSE; /* For when reached by GOTO */ - set_message = message; yield = code == '5' #ifdef SUPPORT_I18N @@ -2177,7 +2181,7 @@ return OK; #endif ? FAIL : DEFER; - set_errno(sx->addrlist, save_errno, set_message, yield, pass_message, sx->host + set_errno(sx->addrlist, errno, message, yield, pass_message, sx->host #ifdef EXPERIMENTAL_DSN_INFO , sx->smtp_greeting, sx->helo_response #endif @@ -2204,7 +2208,7 @@ 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); @@ -2363,6 +2367,258 @@ if (sx->peer_offered & PEER_OFFERED_DSN && !(addr->dsn_flags & rf_dsnlasthop)) } + +/* +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, + "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, "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 fds; +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(&fds); +FD_SET(tls_out.active, &fds); +FD_SET(proxy_fd, &fds); + +for (fd_bits = 3; fd_bits; ) + { + time_t time_left = timeout; + time_t time_start = time(NULL); + + /* wait for data */ + do + { + struct timeval tv = { time_left, 0 }; + + rc = select(max_fd, (SELECT_ARG2_TYPE *)&fds, NULL, NULL, &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; + } + } + while (rc < 0 || !(FD_ISSET(tls_out.active, &fds) || FD_ISSET(proxy_fd, &fds))); + + /* handle inbound data */ + if (FD_ISSET(tls_out.active, &fds)) + if ((rc = tls_read(FALSE, buf, bsize)) <= 0) + { + fd_bits &= ~1; + FD_CLR(tls_out.active, &fds); + shutdown(proxy_fd, SHUT_WR); + } + 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, &fds); + + /* handle outbound data */ + if (FD_ISSET(proxy_fd, &fds)) + if ((rc = read(proxy_fd, buf, bsize)) <= 0) + { + fd_bits &= ~2; + FD_CLR(proxy_fd, &fds); + shutdown(tls_out.active, SHUT_WR); + } + else + { + for (nbytes = 0; rc - nbytes > 0; nbytes += i) + if ((i = tls_write(FALSE, buf + nbytes, rc - nbytes)) < 0) return; + } + else if (fd_bits & 2) + FD_SET(proxy_fd, &fds); + } +} +#endif + + /************************************************* * Deliver address list to given host * *************************************************/ @@ -2413,17 +2669,11 @@ smtp_deliver(address_item *addrlist, host_item *host, int host_af, int port, 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]; @@ -2441,29 +2691,27 @@ sx.port = port; 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, 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); @@ -2482,6 +2730,7 @@ if (tblock->filter_command != NULL) } } +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 @@ -2492,170 +2741,62 @@ code was to use a goto to jump back to this point when there is another 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; - } + address_item * addr; -/* 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); - } - -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; - - 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); + /* Initiate a message transfer. */ - 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; - - 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; + address_item * a; + unsigned cnt; - 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 @@ -2672,16 +2813,14 @@ if ( !(sx.peer_offered & PEER_OFFERED_CHUNKING) int count = smtp_write_command(&sx.outblock, FALSE, "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 */ @@ -2701,7 +2840,7 @@ well as body. Set the appropriate timeout value to be used for each chunk. 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; } @@ -2729,23 +2868,16 @@ else 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. */ @@ -2763,7 +2895,8 @@ else 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(sx.inblock.sock, &tctx, &sx.ob->dkim, + CUSS &message); #else sx.ok = transport_write_message(sx.inblock.sock, &tctx, 0); #endif @@ -2780,7 +2913,8 @@ else 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 @@ -2790,20 +2924,17 @@ else 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 */ @@ -2887,7 +3018,7 @@ else /* 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; @@ -2928,7 +3059,7 @@ else } 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); @@ -2965,7 +3096,7 @@ else 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 " @@ -2987,14 +3118,14 @@ else 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)) @@ -3048,8 +3179,8 @@ if (!sx.ok) { 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; } @@ -3178,9 +3309,9 @@ hosts_nopass_tls. */ 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; @@ -3188,12 +3319,15 @@ if (completed_address && sx.ok && sx.send_quit) 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) @@ -3206,7 +3340,7 @@ if (completed_address && sx.ok && sx.send_quit) if (! (sx.ok = smtp_write_command(&sx.outblock, FALSE, "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, @@ -3226,29 +3360,51 @@ if (completed_address && sx.ok && sx.send_quit) 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. */ - + /* Pass the connection on to a new Exim process. */ #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 (verify_check_given_host(&sx.ob->hosts_noproxy_tls, host) == OK) + { + /* Pass the socket, for direct use, 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. */ + + 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); + } + 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 + ); + } #endif /* If the socket is successfully passed, we mustn't send QUIT (or @@ -3258,13 +3414,42 @@ if (completed_address && sx.ok && sx.send_quit) 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 */ + { + 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 */ + { + if ((pid = fork())) + _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 @@ -3309,7 +3494,7 @@ 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); @@ -3473,8 +3658,10 @@ DEBUG(D_transport) 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 @@ -3739,7 +3926,7 @@ for (cutoff_retry = 0; 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; @@ -3752,8 +3939,11 @@ for (cutoff_retry = 0; { 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; } @@ -3871,7 +4061,7 @@ for (cutoff_retry = 0; 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" : @@ -3892,6 +4082,7 @@ for (cutoff_retry = 0; { case hwhy_retry: hosts_retry++; break; case hwhy_failed: hosts_fail++; break; + case hwhy_insecure: case hwhy_deferred: hosts_defer++; break; } @@ -4075,8 +4266,9 @@ for (cutoff_retry = 0; && 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, &message_defer, TRUE);