*************************************************/
/* Copyright (c) University of Cambridge 1995 - 2018 */
+/* Copyright (c) The Exim Maintainers 2020 */
/* See the file NOTICE for conditions of use and distribution. */
#include "../exim.h"
before the lower case letters). Some live in the transport_instance block so as
to be publicly visible; these are flagged with opt_public. */
+#define LOFF(field) OPT_OFF(smtp_transport_options_block, field)
+
optionlist smtp_transport_options[] = {
{ "*expand_multi_domain", opt_stringptr | opt_hidden | opt_public,
- (void *)offsetof(transport_instance, expand_multi_domain) },
+ OPT_OFF(transport_instance, expand_multi_domain) },
{ "*expand_retry_include_ip_address", opt_stringptr | opt_hidden,
- (void *)(offsetof(smtp_transport_options_block, expand_retry_include_ip_address)) },
+ LOFF(expand_retry_include_ip_address) },
{ "address_retry_include_sender", opt_bool,
- (void *)offsetof(smtp_transport_options_block, address_retry_include_sender) },
- { "allow_localhost", opt_bool,
- (void *)offsetof(smtp_transport_options_block, allow_localhost) },
+ LOFF(address_retry_include_sender) },
+ { "allow_localhost", opt_bool, LOFF(allow_localhost) },
#ifdef EXPERIMENTAL_ARC
- { "arc_sign", opt_stringptr,
- (void *)offsetof(smtp_transport_options_block, arc_sign) },
-#endif
- { "authenticated_sender", opt_stringptr,
- (void *)offsetof(smtp_transport_options_block, authenticated_sender) },
- { "authenticated_sender_force", opt_bool,
- (void *)offsetof(smtp_transport_options_block, authenticated_sender_force) },
- { "command_timeout", opt_time,
- (void *)offsetof(smtp_transport_options_block, command_timeout) },
- { "connect_timeout", opt_time,
- (void *)offsetof(smtp_transport_options_block, connect_timeout) },
+ { "arc_sign", opt_stringptr, LOFF(arc_sign) },
+#endif
+ { "authenticated_sender", opt_stringptr, LOFF(authenticated_sender) },
+ { "authenticated_sender_force", opt_bool, LOFF(authenticated_sender_force) },
+ { "command_timeout", opt_time, LOFF(command_timeout) },
+ { "connect_timeout", opt_time, LOFF(connect_timeout) },
{ "connection_max_messages", opt_int | opt_public,
- (void *)offsetof(transport_instance, connection_max_messages) },
+ OPT_OFF(transport_instance, connection_max_messages) },
# ifdef SUPPORT_DANE
- { "dane_require_tls_ciphers", opt_stringptr,
- (void *)offsetof(smtp_transport_options_block, dane_require_tls_ciphers) },
+ { "dane_require_tls_ciphers", opt_stringptr, LOFF(dane_require_tls_ciphers) },
# endif
- { "data_timeout", opt_time,
- (void *)offsetof(smtp_transport_options_block, data_timeout) },
- { "delay_after_cutoff", opt_bool,
- (void *)offsetof(smtp_transport_options_block, delay_after_cutoff) },
+ { "data_timeout", opt_time, LOFF(data_timeout) },
+ { "delay_after_cutoff", opt_bool, LOFF(delay_after_cutoff) },
#ifndef DISABLE_DKIM
- { "dkim_canon", opt_stringptr,
- (void *)offsetof(smtp_transport_options_block, dkim.dkim_canon) },
- { "dkim_domain", opt_stringptr,
- (void *)offsetof(smtp_transport_options_block, dkim.dkim_domain) },
- { "dkim_hash", opt_stringptr,
- (void *)offsetof(smtp_transport_options_block, dkim.dkim_hash) },
- { "dkim_identity", opt_stringptr,
- (void *)offsetof(smtp_transport_options_block, dkim.dkim_identity) },
- { "dkim_private_key", opt_stringptr,
- (void *)offsetof(smtp_transport_options_block, dkim.dkim_private_key) },
- { "dkim_selector", opt_stringptr,
- (void *)offsetof(smtp_transport_options_block, dkim.dkim_selector) },
- { "dkim_sign_headers", opt_stringptr,
- (void *)offsetof(smtp_transport_options_block, dkim.dkim_sign_headers) },
- { "dkim_strict", opt_stringptr,
- (void *)offsetof(smtp_transport_options_block, dkim.dkim_strict) },
- { "dkim_timestamps", opt_stringptr,
- (void *)offsetof(smtp_transport_options_block, dkim.dkim_timestamps) },
-#endif
- { "dns_qualify_single", opt_bool,
- (void *)offsetof(smtp_transport_options_block, dns_qualify_single) },
- { "dns_search_parents", opt_bool,
- (void *)offsetof(smtp_transport_options_block, dns_search_parents) },
- { "dnssec_request_domains", opt_stringptr,
- (void *)offsetof(smtp_transport_options_block, dnssec.request) },
- { "dnssec_require_domains", opt_stringptr,
- (void *)offsetof(smtp_transport_options_block, dnssec.require) },
- { "dscp", opt_stringptr,
- (void *)offsetof(smtp_transport_options_block, dscp) },
- { "fallback_hosts", opt_stringptr,
- (void *)offsetof(smtp_transport_options_block, fallback_hosts) },
- { "final_timeout", opt_time,
- (void *)offsetof(smtp_transport_options_block, final_timeout) },
- { "gethostbyname", opt_bool,
- (void *)offsetof(smtp_transport_options_block, gethostbyname) },
- { "helo_data", opt_stringptr,
- (void *)offsetof(smtp_transport_options_block, helo_data) },
- { "hosts", opt_stringptr,
- (void *)offsetof(smtp_transport_options_block, hosts) },
- { "hosts_avoid_esmtp", opt_stringptr,
- (void *)offsetof(smtp_transport_options_block, hosts_avoid_esmtp) },
- { "hosts_avoid_pipelining", opt_stringptr,
- (void *)offsetof(smtp_transport_options_block, hosts_avoid_pipelining) },
+ { "dkim_canon", opt_stringptr, LOFF(dkim.dkim_canon) },
+ { "dkim_domain", opt_stringptr, LOFF(dkim.dkim_domain) },
+ { "dkim_hash", opt_stringptr, LOFF(dkim.dkim_hash) },
+ { "dkim_identity", opt_stringptr, LOFF(dkim.dkim_identity) },
+ { "dkim_private_key", opt_stringptr, LOFF(dkim.dkim_private_key) },
+ { "dkim_selector", opt_stringptr, LOFF(dkim.dkim_selector) },
+ { "dkim_sign_headers", opt_stringptr, LOFF(dkim.dkim_sign_headers) },
+ { "dkim_strict", opt_stringptr, LOFF(dkim.dkim_strict) },
+ { "dkim_timestamps", opt_stringptr, LOFF(dkim.dkim_timestamps) },
+#endif
+ { "dns_qualify_single", opt_bool, LOFF(dns_qualify_single) },
+ { "dns_search_parents", opt_bool, LOFF(dns_search_parents) },
+ { "dnssec_request_domains", opt_stringptr, LOFF(dnssec.request) },
+ { "dnssec_require_domains", opt_stringptr, LOFF(dnssec.require) },
+ { "dscp", opt_stringptr, LOFF(dscp) },
+ { "fallback_hosts", opt_stringptr, LOFF(fallback_hosts) },
+ { "final_timeout", opt_time, LOFF(final_timeout) },
+ { "gethostbyname", opt_bool, LOFF(gethostbyname) },
+ { "helo_data", opt_stringptr, LOFF(helo_data) },
+ { "hosts", opt_stringptr, LOFF(hosts) },
+ { "hosts_avoid_esmtp", opt_stringptr, LOFF(hosts_avoid_esmtp) },
+ { "hosts_avoid_pipelining", opt_stringptr, LOFF(hosts_avoid_pipelining) },
#ifndef DISABLE_TLS
- { "hosts_avoid_tls", opt_stringptr,
- (void *)offsetof(smtp_transport_options_block, hosts_avoid_tls) },
+ { "hosts_avoid_tls", opt_stringptr, LOFF(hosts_avoid_tls) },
#endif
- { "hosts_max_try", opt_int,
- (void *)offsetof(smtp_transport_options_block, hosts_max_try) },
- { "hosts_max_try_hardlimit", opt_int,
- (void *)offsetof(smtp_transport_options_block, hosts_max_try_hardlimit) },
+ { "hosts_max_try", opt_int, LOFF(hosts_max_try) },
+ { "hosts_max_try_hardlimit", opt_int, LOFF(hosts_max_try_hardlimit) },
#ifndef DISABLE_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) },
+ { "hosts_nopass_tls", opt_stringptr, LOFF(hosts_nopass_tls) },
+ { "hosts_noproxy_tls", opt_stringptr, LOFF(hosts_noproxy_tls) },
#endif
- { "hosts_override", opt_bool,
- (void *)offsetof(smtp_transport_options_block, hosts_override) },
+ { "hosts_override", opt_bool, LOFF(hosts_override) },
#ifndef DISABLE_PIPE_CONNECT
- { "hosts_pipe_connect", opt_stringptr,
- (void *)offsetof(smtp_transport_options_block, hosts_pipe_connect) },
+ { "hosts_pipe_connect", opt_stringptr, LOFF(hosts_pipe_connect) },
#endif
- { "hosts_randomize", opt_bool,
- (void *)offsetof(smtp_transport_options_block, hosts_randomize) },
+ { "hosts_randomize", opt_bool, LOFF(hosts_randomize) },
#if !defined(DISABLE_TLS) && !defined(DISABLE_OCSP)
- { "hosts_request_ocsp", opt_stringptr,
- (void *)offsetof(smtp_transport_options_block, hosts_request_ocsp) },
+ { "hosts_request_ocsp", opt_stringptr, LOFF(hosts_request_ocsp) },
#endif
- { "hosts_require_auth", opt_stringptr,
- (void *)offsetof(smtp_transport_options_block, hosts_require_auth) },
+ { "hosts_require_auth", opt_stringptr, LOFF(hosts_require_auth) },
#ifndef DISABLE_TLS
# ifdef SUPPORT_DANE
- { "hosts_require_dane", opt_stringptr,
- (void *)offsetof(smtp_transport_options_block, hosts_require_dane) },
+ { "hosts_require_dane", opt_stringptr, LOFF(hosts_require_dane) },
# endif
# ifndef DISABLE_OCSP
- { "hosts_require_ocsp", opt_stringptr,
- (void *)offsetof(smtp_transport_options_block, hosts_require_ocsp) },
+ { "hosts_require_ocsp", opt_stringptr, LOFF(hosts_require_ocsp) },
# endif
- { "hosts_require_tls", opt_stringptr,
- (void *)offsetof(smtp_transport_options_block, hosts_require_tls) },
+ { "hosts_require_tls", opt_stringptr, LOFF(hosts_require_tls) },
#endif
- { "hosts_try_auth", opt_stringptr,
- (void *)offsetof(smtp_transport_options_block, hosts_try_auth) },
- { "hosts_try_chunking", opt_stringptr,
- (void *)offsetof(smtp_transport_options_block, hosts_try_chunking) },
+ { "hosts_try_auth", opt_stringptr, LOFF(hosts_try_auth) },
+ { "hosts_try_chunking", opt_stringptr, LOFF(hosts_try_chunking) },
#ifdef SUPPORT_DANE
- { "hosts_try_dane", opt_stringptr,
- (void *)offsetof(smtp_transport_options_block, hosts_try_dane) },
+ { "hosts_try_dane", opt_stringptr, LOFF(hosts_try_dane) },
#endif
- { "hosts_try_fastopen", opt_stringptr,
- (void *)offsetof(smtp_transport_options_block, hosts_try_fastopen) },
+ { "hosts_try_fastopen", opt_stringptr, LOFF(hosts_try_fastopen) },
#ifndef DISABLE_PRDR
- { "hosts_try_prdr", opt_stringptr,
- (void *)offsetof(smtp_transport_options_block, hosts_try_prdr) },
+ { "hosts_try_prdr", opt_stringptr, LOFF(hosts_try_prdr) },
#endif
#ifndef DISABLE_TLS
- { "hosts_verify_avoid_tls", opt_stringptr,
- (void *)offsetof(smtp_transport_options_block, hosts_verify_avoid_tls) },
-#endif
- { "interface", opt_stringptr,
- (void *)offsetof(smtp_transport_options_block, interface) },
- { "keepalive", opt_bool,
- (void *)offsetof(smtp_transport_options_block, keepalive) },
- { "lmtp_ignore_quota", opt_bool,
- (void *)offsetof(smtp_transport_options_block, lmtp_ignore_quota) },
+ { "hosts_verify_avoid_tls", opt_stringptr, LOFF(hosts_verify_avoid_tls) },
+#endif
+ { "interface", opt_stringptr, LOFF(interface) },
+ { "keepalive", opt_bool, LOFF(keepalive) },
+ { "lmtp_ignore_quota", opt_bool, LOFF(lmtp_ignore_quota) },
{ "max_rcpt", opt_int | opt_public,
- (void *)offsetof(transport_instance, max_addresses) },
+ OPT_OFF(transport_instance, max_addresses) },
+ { "message_linelength_limit", opt_int, LOFF(message_linelength_limit) },
{ "multi_domain", opt_expand_bool | opt_public,
- (void *)offsetof(transport_instance, multi_domain) },
- { "port", opt_stringptr,
- (void *)offsetof(smtp_transport_options_block, port) },
- { "protocol", opt_stringptr,
- (void *)offsetof(smtp_transport_options_block, protocol) },
- { "retry_include_ip_address", opt_expand_bool,
- (void *)offsetof(smtp_transport_options_block, retry_include_ip_address) },
- { "serialize_hosts", opt_stringptr,
- (void *)offsetof(smtp_transport_options_block, serialize_hosts) },
- { "size_addition", opt_int,
- (void *)offsetof(smtp_transport_options_block, size_addition) },
+ OPT_OFF(transport_instance, multi_domain) },
+ { "port", opt_stringptr, LOFF(port) },
+ { "protocol", opt_stringptr, LOFF(protocol) },
+ { "retry_include_ip_address", opt_expand_bool, LOFF(retry_include_ip_address) },
+ { "serialize_hosts", opt_stringptr, LOFF(serialize_hosts) },
+ { "size_addition", opt_int, LOFF(size_addition) },
#ifdef SUPPORT_SOCKS
- { "socks_proxy", opt_stringptr,
- (void *)offsetof(smtp_transport_options_block, socks_proxy) },
+ { "socks_proxy", opt_stringptr, LOFF(socks_proxy) },
#endif
#ifndef DISABLE_TLS
- { "tls_certificate", opt_stringptr,
- (void *)offsetof(smtp_transport_options_block, tls_certificate) },
- { "tls_crl", opt_stringptr,
- (void *)offsetof(smtp_transport_options_block, tls_crl) },
- { "tls_dh_min_bits", opt_int,
- (void *)offsetof(smtp_transport_options_block, tls_dh_min_bits) },
- { "tls_privatekey", opt_stringptr,
- (void *)offsetof(smtp_transport_options_block, tls_privatekey) },
- { "tls_require_ciphers", opt_stringptr,
- (void *)offsetof(smtp_transport_options_block, tls_require_ciphers) },
-# ifdef EXPERIMENTAL_TLS_RESUME
- { "tls_resumption_hosts", opt_stringptr,
- (void *)offsetof(smtp_transport_options_block, tls_resumption_hosts) },
+ { "tls_certificate", opt_stringptr, LOFF(tls_certificate) },
+ { "tls_crl", opt_stringptr, LOFF(tls_crl) },
+ { "tls_dh_min_bits", opt_int, LOFF(tls_dh_min_bits) },
+ { "tls_privatekey", opt_stringptr, LOFF(tls_privatekey) },
+ { "tls_require_ciphers", opt_stringptr, LOFF(tls_require_ciphers) },
+# ifndef DISABLE_TLS_RESUME
+ { "tls_resumption_hosts", opt_stringptr, LOFF(tls_resumption_hosts) },
# endif
- { "tls_sni", opt_stringptr,
- (void *)offsetof(smtp_transport_options_block, tls_sni) },
- { "tls_tempfail_tryclear", opt_bool,
- (void *)offsetof(smtp_transport_options_block, tls_tempfail_tryclear) },
- { "tls_try_verify_hosts", opt_stringptr,
- (void *)offsetof(smtp_transport_options_block, tls_try_verify_hosts) },
- { "tls_verify_cert_hostnames", opt_stringptr,
- (void *)offsetof(smtp_transport_options_block,tls_verify_cert_hostnames)},
- { "tls_verify_certificates", opt_stringptr,
- (void *)offsetof(smtp_transport_options_block, tls_verify_certificates) },
- { "tls_verify_hosts", opt_stringptr,
- (void *)offsetof(smtp_transport_options_block, tls_verify_hosts) },
+ { "tls_sni", opt_stringptr, LOFF(tls_sni) },
+ { "tls_tempfail_tryclear", opt_bool, LOFF(tls_tempfail_tryclear) },
+ { "tls_try_verify_hosts", opt_stringptr, LOFF(tls_try_verify_hosts) },
+ { "tls_verify_cert_hostnames", opt_stringptr, LOFF(tls_verify_cert_hostnames)},
+ { "tls_verify_certificates", opt_stringptr, LOFF(tls_verify_certificates) },
+ { "tls_verify_hosts", opt_stringptr, LOFF(tls_verify_hosts) },
#endif
#ifdef SUPPORT_I18N
- { "utf8_downconvert", opt_stringptr,
- (void *)offsetof(smtp_transport_options_block, utf8_downconvert) },
+ { "utf8_downconvert", opt_stringptr, LOFF(utf8_downconvert) },
#endif
};
/* Default private options block for the smtp transport. */
smtp_transport_options_block smtp_transport_option_defaults = {
- .hosts = NULL,
- .fallback_hosts = NULL,
- .hostlist = NULL,
- .fallback_hostlist = NULL,
+ /* All non-mentioned elements 0/NULL/FALSE */
.helo_data = US"$primary_hostname",
- .interface = NULL,
- .port = NULL,
.protocol = US"smtp",
- .dscp = NULL,
- .serialize_hosts = NULL,
- .hosts_try_auth = NULL,
- .hosts_require_auth = NULL,
.hosts_try_chunking = US"*",
#ifdef SUPPORT_DANE
.hosts_try_dane = US"*",
- .hosts_require_dane = NULL,
- .dane_require_tls_ciphers = NULL,
#endif
.hosts_try_fastopen = US"*",
#ifndef DISABLE_PRDR
#endif
#ifndef DISABLE_OCSP
.hosts_request_ocsp = US"*", /* hosts_request_ocsp (except under DANE; tls_client_start()) */
- .hosts_require_ocsp = NULL,
-#endif
- .hosts_require_tls = NULL,
- .hosts_avoid_tls = NULL,
- .hosts_verify_avoid_tls = NULL,
- .hosts_avoid_pipelining = NULL,
-#ifndef DISABLE_PIPE_CONNECT
- .hosts_pipe_connect = NULL,
-#endif
- .hosts_avoid_esmtp = NULL,
-#ifndef DISABLE_TLS
- .hosts_nopass_tls = NULL,
- .hosts_noproxy_tls = NULL,
#endif
.command_timeout = 5*60,
.connect_timeout = 5*60,
.size_addition = 1024,
.hosts_max_try = 5,
.hosts_max_try_hardlimit = 50,
+ .message_linelength_limit = 998,
.address_retry_include_sender = TRUE,
- .allow_localhost = FALSE,
- .authenticated_sender_force = FALSE,
- .gethostbyname = FALSE,
.dns_qualify_single = TRUE,
- .dns_search_parents = FALSE,
.dnssec = { .request= US"*", .require=NULL },
.delay_after_cutoff = TRUE,
- .hosts_override = FALSE,
- .hosts_randomize = FALSE,
.keepalive = TRUE,
- .lmtp_ignore_quota = FALSE,
- .expand_retry_include_ip_address = NULL,
.retry_include_ip_address = TRUE,
-#ifdef SUPPORT_SOCKS
- .socks_proxy = NULL,
-#endif
#ifndef DISABLE_TLS
- .tls_certificate = NULL,
- .tls_crl = NULL,
- .tls_privatekey = NULL,
- .tls_require_ciphers = NULL,
- .tls_sni = NULL,
+# if defined(SUPPORT_SYSDEFAULT_CABUNDLE) || !defined(USE_GNUTLS)
.tls_verify_certificates = US"system",
+# endif
.tls_dh_min_bits = EXIM_CLIENT_DH_DEFAULT_MIN_BITS,
.tls_tempfail_tryclear = TRUE,
-# ifdef EXPERIMENTAL_TLS_RESUME
- .tls_resumption_hosts = NULL,
-# endif
- .tls_verify_hosts = NULL,
.tls_try_verify_hosts = US"*",
.tls_verify_cert_hostnames = US"*",
#endif
#ifdef SUPPORT_I18N
- .utf8_downconvert = NULL,
+ .utf8_downconvert = US"-1",
#endif
#ifndef DISABLE_DKIM
.dkim =
- {.dkim_domain = NULL,
- .dkim_identity = NULL,
- .dkim_private_key = NULL,
- .dkim_selector = NULL,
- .dkim_canon = NULL,
- .dkim_sign_headers = NULL,
- .dkim_strict = NULL,
- .dkim_hash = US"sha256",
- .dkim_timestamps = NULL,
- .dot_stuffed = FALSE,
- .force_bodyhash = FALSE,
-# ifdef EXPERIMENTAL_ARC
- .arc_signspec = NULL,
-# endif
- },
-# ifdef EXPERIMENTAL_ARC
- .arc_sign = NULL,
-# endif
+ { .dkim_hash = US"sha256", },
#endif
};
{
smtp_transport_options_block *ob = SOB tblock->options_block;
-errmsg = errmsg; /* Keep picky compilers happy */
-uid = uid;
-gid = gid;
-
/* Pass back options if required. This interface is getting very messy. */
if (tf)
/* If hosts_override is set and there are local hosts, set the global
flag that stops verify from showing router hosts. */
-if (ob->hosts_override && ob->hosts != NULL) tblock->overrides_hosts = TRUE;
+if (ob->hosts_override && ob->hosts) tblock->overrides_hosts = TRUE;
/* If there are any fallback hosts listed, build a chain of host items
for them, but do not do any lookups at this time. */
static BOOL
smtp_reap_banner(smtp_context * sx)
{
-BOOL good_response = smtp_read_response(sx, sx->buffer, sizeof(sx->buffer),
+BOOL good_response;
+#if defined(__linux__) && defined(TCP_QUICKACK)
+ { /* Hack to get QUICKACK disabled; has to be right after 3whs, and has to on->off */
+ int sock = sx->cctx.sock;
+ struct pollfd p = {.fd = sock, .events = POLLOUT};
+ int rc = poll(&p, 1, 1000);
+ (void) setsockopt(sock, IPPROTO_TCP, TCP_QUICKACK, US &on, sizeof(on));
+ (void) setsockopt(sock, IPPROTO_TCP, TCP_QUICKACK, US &off, sizeof(off));
+ }
+#endif
+good_response = smtp_read_response(sx, sx->buffer, sizeof(sx->buffer),
'2', (SOB sx->conn_args.ob)->command_timeout);
#ifdef EXPERIMENTAL_DSN_INFO
sx->smtp_greeting = string_copy(sx->buffer);
uschar * ehlo_resp_key = ehlo_cache_key(sx);
dbdata_ehlo_resp * er;
- if (!(er = dbfn_read(dbm_file, ehlo_resp_key)))
+ if (!(er = dbfn_read_enforce_length(dbm_file, ehlo_resp_key, sizeof(dbdata_ehlo_resp))))
{ DEBUG(D_transport) debug_printf("no ehlo-resp record\n"); }
else if (time(NULL) - er->time_stamp > retry_data_expire)
{
for (au = auths, authnum = 0; au; au = au->next, authnum++) if (au->client)
{
const uschar * list = names;
- int sep = ' ';
- uschar name[32];
-
- while (string_nextinlist(&list, &sep, name, sizeof(name)))
- if (strcmpic(au->public_name, name) == 0)
+ uschar * s;
+ for (int sep = ' '; s = string_nextinlist(&list, &sep, NULL, 0); )
+ if (strcmpic(au->public_name, s) == 0)
{ authbits |= BIT(authnum); break; }
}
(void) smtp_discard_responses(sx, sx->conn_args.ob, *countp);
return rc;
}
-#endif
+#endif /*!DISABLE_PIPE_CONNECT*/
/*************************************************
{
DEBUG(D_transport) debug_printf("%s expect mail\n", __FUNCTION__);
count--;
- sx->pending_MAIL = FALSE;
+ sx->pending_MAIL = sx->RCPT_452 = FALSE;
if (!smtp_read_response(sx, sx->buffer, sizeof(sx->buffer),
'2', ob->command_timeout))
{
/* The address was accepted */
addr->host_used = sx->conn_args.host;
- DEBUG(D_transport) debug_printf("%s expect rcpt\n", __FUNCTION__);
+ DEBUG(D_transport) debug_printf("%s expect rcpt for %s\n", __FUNCTION__, addr->address);
if (smtp_read_response(sx, sx->buffer, sizeof(sx->buffer),
'2', ob->command_timeout))
{
if (addr->more_errno >> 8 == 52 && yield & 3)
{
- if (!sx->RCPT_452)
+ if (!sx->RCPT_452) /* initialised at MAIL-ack above */
{
DEBUG(D_transport)
debug_printf("%s: seen first 452 too-many-rcpts\n", __FUNCTION__);
}
}
}
+ if (count && !(addr = addr->next))
+ return -2;
} /* Loop for next RCPT response */
/* Update where to start at for the next block of responses, unless we
if (require_auth == OK && !f.smtp_authenticated)
{
+#ifndef DISABLE_PIPE_CONNECT
+ invalidate_ehlo_cache_entry(sx);
+#endif
set_errno_nohost(sx->addrlist, ERRNO_AUTHFAIL,
string_sprintf("authentication required but %s", fail_reason), DEFER,
FALSE, &sx->delivery_start);
typedef struct smtp_compare_s
{
- uschar *current_sender_address;
- struct transport_instance *tblock;
+ uschar * current_sender_address;
+ struct transport_instance * tblock;
} smtp_compare_t;
current_local_identity =
smtp_local_identity(s_compare->current_sender_address, s_compare->tblock);
-if (!(new_sender_address = deliver_get_sender_address(message_id)))
- return 0;
+if (!(new_sender_address = spool_sender_from_msgid(message_id)))
+ return FALSE;
+
message_local_identity =
smtp_local_identity(new_sender_address, s_compare->tblock);
tls_out.sni = NULL;
#endif
tls_out.ocsp = OCSP_NOT_REQ;
-#ifdef EXPERIMENTAL_TLS_RESUME
+#ifndef DISABLE_TLS_RESUME
tls_out.resumption = 0;
#endif
tls_out.ver = NULL;
DEFER, FALSE, &sx->delivery_start);
return ERROR;
}
-#endif
+#else
+
+/* If we have a proxied TLS connection, check usability for this message */
+
+if (continue_hostname && continue_proxy_cipher)
+ {
+ int rc;
+ const uschar * sni = US"";
+
+# ifdef SUPPORT_DANE
+ /* Check if the message will be DANE-verified; if so force its SNI */
+
+ tls_out.dane_verified = FALSE;
+ smtp_port_for_connect(sx->conn_args.host, sx->port);
+ if ( sx->conn_args.host->dnssec == DS_YES
+ && ( sx->dane_required
+ || verify_check_given_host(CUSS &ob->hosts_try_dane, sx->conn_args.host) == OK
+ ) )
+ switch (rc = tlsa_lookup(sx->conn_args.host, &sx->conn_args.tlsa_dnsa, sx->dane_required))
+ {
+ case OK: sx->conn_args.dane = TRUE;
+ ob->tls_tempfail_tryclear = FALSE; /* force TLS */
+ ob->tls_sni = sx->first_addr->domain; /* force SNI */
+ break;
+ case FAIL_FORCED: break;
+ default: set_errno_nohost(sx->addrlist, ERRNO_DNSDEFER,
+ string_sprintf("DANE error: tlsa lookup %s",
+ rc_to_string(rc)),
+ rc, FALSE, &sx->delivery_start);
+# ifndef DISABLE_EVENT
+ (void) event_raise(sx->conn_args.tblock->event_action,
+ US"dane:fail", sx->dane_required
+ ? US"dane-required" : US"dnssec-invalid");
+# endif
+ return rc;
+ }
+# endif
+
+ /* If the SNI or the DANE status required for the new message differs from the
+ existing conn drop the connection to force a new one. */
+
+ if (ob->tls_sni && !(sni = expand_cstring(ob->tls_sni)))
+ log_write(0, LOG_MAIN|LOG_PANIC,
+ "<%s>: failed to expand transport's tls_sni value: %s",
+ sx->addrlist->address, expand_string_message);
+
+# ifdef SUPPORT_DANE
+ if ( (continue_proxy_sni ? (Ustrcmp(continue_proxy_sni, sni) == 0) : !*sni)
+ && continue_proxy_dane == sx->conn_args.dane)
+ {
+ tls_out.sni = US sni;
+ if ((tls_out.dane_verified = continue_proxy_dane))
+ sx->conn_args.host->dnssec = DS_YES;
+ }
+# else
+ if ((continue_proxy_sni ? (Ustrcmp(continue_proxy_sni, sni) == 0) : !*sni))
+ tls_out.sni = US sni;
+# endif
+ else
+ {
+ DEBUG(D_transport)
+ debug_printf("Closing proxied-TLS connection due to SNI mismatch\n");
+
+ HDEBUG(D_transport|D_acl|D_v) debug_printf_indent(" SMTP>> QUIT\n");
+ write(0, "QUIT\r\n", 6);
+ close(0);
+ continue_hostname = continue_proxy_cipher = NULL;
+ f.continue_more = FALSE;
+ continue_sequence = 1; /* Unfortunately, this process cannot affect success log
+ which is done by delivery proc. Would have to pass this
+ back through reporting pipe. */
+ }
+ }
+#endif /*!DISABLE_TLS*/
/* Make a connection to the host if this isn't a continued delivery, and handle
the initial interaction and HELO/EHLO/LHLO. Connect timeout errors are handled
switch (rc = tlsa_lookup(sx->conn_args.host, &sx->conn_args.tlsa_dnsa, sx->dane_required))
{
case OK: sx->conn_args.dane = TRUE;
- ob->tls_tempfail_tryclear = FALSE;
+ ob->tls_tempfail_tryclear = FALSE; /* force TLS */
+ ob->tls_sni = sx->first_addr->domain; /* force SNI */
break;
case FAIL_FORCED: break;
default: set_errno_nohost(sx->addrlist, ERRNO_DNSDEFER,
else DEBUG(D_transport)
debug_printf("helo needs $sending_ip_address\n");
+PIPE_CONNECT_RETRY:
if (sx->early_pipe_active)
sx->outblock.conn_args = &sx->conn_args;
else
#endif
{
- if ((sx->cctx.sock = smtp_connect(&sx->conn_args, NULL)) < 0)
+ blob lazy_conn = {.data = NULL};
+ /* For TLS-connect, a TFO lazy-connect is useful since the Client Hello
+ can go on the TCP SYN. */
+
+ if ((sx->cctx.sock = smtp_connect(&sx->conn_args,
+ sx->smtps ? &lazy_conn : NULL)) < 0)
{
set_errno_nohost(sx->addrlist,
errno == ETIMEDOUT ? ERRNO_CONNECTTIMEOUT : errno,
sx->send_quit = FALSE;
return DEFER;
}
+#ifdef TCP_QUICKACK
+ (void) setsockopt(sx->cctx.sock, IPPROTO_TCP, TCP_QUICKACK, US &off,
+ sizeof(off));
+#endif
}
/* Expand the greeting message while waiting for the initial response. (Makes
sense if helo_data contains ${lookup dnsdb ...} stuff). The expansion is
else
#endif
{
-#ifdef TCP_QUICKACK
- (void) setsockopt(sx->cctx.sock, IPPROTO_TCP, TCP_QUICKACK, US &off,
- sizeof(off));
-#endif
if (!smtp_reap_banner(sx))
goto RESPONSE_FAILED;
}
}
sx->inblock.cctx = sx->outblock.cctx = &sx->cctx;
smtp_command = big_buffer;
+ sx->peer_offered = smtp_peer_options;
sx->helo_data = NULL; /* ensure we re-expand ob->helo_data */
/* For a continued connection with TLS being proxied for us, or a
&& cutthrough.is_tls)
)
{
- sx->peer_offered = smtp_peer_options;
sx->pipelining_used = pipelining_active = !!(smtp_peer_options & OPTION_PIPE);
HDEBUG(D_transport) debug_printf("continued connection, %s TLS\n",
continue_proxy_cipher ? "proxied" : "verify conn with");
{
HDEBUG(D_transport)
debug_printf("failed reaping pipelined cmd responses\n");
- goto RESPONSE_FAILED;
+ close(sx->cctx.sock);
+ sx->cctx.sock = -1;
+ sx->early_pipe_active = FALSE;
+ goto PIPE_CONNECT_RETRY;
}
#endif
/* 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
it for this host. */
-#ifdef USE_GNUTLS
- GNUTLS_CONN_FAILED:
-#endif
+ TLS_CONN_FAILED:
DEBUG(D_tls) debug_printf("TLS session fail: %s\n", tls_errstr);
# ifdef SUPPORT_DANE
sx->send_quit = FALSE;
goto TLS_FAILED;
}
+ sx->send_tlsclose = TRUE;
- /* TLS session is set up */
+ /* TLS session is set up. Check the inblock fill level. If there is
+ content then as we have not yet done a tls read it must have arrived before
+ the TLS handshake, in-clear. That violates the sync requirement of the
+ STARTTLS RFC, so fail. */
+
+ if (sx->inblock.ptr != sx->inblock.ptrend)
+ {
+ DEBUG(D_tls)
+ {
+ int i = sx->inblock.ptrend - sx->inblock.ptr;
+ debug_printf("unused data in input buffer after ack for STARTTLS:\n"
+ "'%.*s'%s\n",
+ i > 100 ? 100 : i, sx->inblock.ptr, i > 100 ? "..." : "");
+ }
+ tls_errstr = US"synch error before connect";
+ goto TLS_CONN_FAILED;
+ }
smtp_peer_options_wrap = smtp_peer_options;
for (address_item * addr = sx->addrlist; addr; addr = addr->next)
Can it do that, with all the flexibility we need? */
tls_errstr = US"error on first read";
- goto GNUTLS_CONN_FAILED;
+ goto TLS_CONN_FAILED;
}
#else
goto RESPONSE_FAILED;
continued session down a previously-used socket, we haven't just done EHLO, so
we skip this. */
-if (continue_hostname == NULL
+if ( !continue_hostname
#ifndef DISABLE_TLS
|| tls_out.active.sock >= 0
#endif
#ifndef DISABLE_TLS
if (sx->cctx.tls_ctx)
{
- tls_close(sx->cctx.tls_ctx, TLS_SHUTDOWN_NOWAIT);
+ if (sx->send_tlsclose)
+ {
+ tls_close(sx->cctx.tls_ctx, TLS_SHUTDOWN_NOWAIT);
+ sx->send_tlsclose = FALSE;
+ }
sx->cctx.tls_ctx = NULL;
}
#endif
fd_set rfds, efds;
int max_fd = MAX(pfd[0], tls_out.active.sock) + 1;
int rc, i;
+BOOL send_tls_shutdown = TRUE;
close(pfd[1]);
-if ((rc = fork()))
- {
- DEBUG(D_transport) debug_printf("proxy-proc final-pid %d\n", rc);
+if ((rc = exim_fork(US"tls-proxy")))
_exit(rc < 0 ? EXIT_FAILURE : EXIT_SUCCESS);
- }
-testharness_pause_ms(100); /* let parent debug out */
set_process_info("proxying TLS connection for continued transport");
FD_ZERO(&rfds);
FD_SET(tls_out.active.sock, &rfds);
goto done;
}
+ /* For errors where not readable, bomb out */
+
if (FD_ISSET(tls_out.active.sock, &efds) || FD_ISSET(pfd[0], &efds))
{
DEBUG(D_transport) debug_printf("select: exceptional cond on %s fd\n",
FD_ISSET(pfd[0], &efds) ? "proxy" : "tls");
- goto done;
+ if (!(FD_ISSET(tls_out.active.sock, &rfds) || FD_ISSET(pfd[0], &rfds)))
+ goto done;
+ DEBUG(D_transport) debug_printf("- but also readable; no exit yet\n");
}
}
while (rc < 0 || !(FD_ISSET(tls_out.active.sock, &rfds) || FD_ISSET(pfd[0], &rfds)));
/* handle inbound data */
if (FD_ISSET(tls_out.active.sock, &rfds))
- if ((rc = tls_read(ct_ctx, buf, bsize)) <= 0)
- {
+ if ((rc = tls_read(ct_ctx, buf, bsize)) <= 0) /* Expect -1 for EOF; */
+ { /* that reaps the TLS Close Notify record */
fd_bits &= ~1;
FD_CLR(tls_out.active.sock, &rfds);
shutdown(pfd[0], SHUT_WR);
for (int nbytes = 0; rc - nbytes > 0; nbytes += i)
if ((i = write(pfd[0], buf + nbytes, rc - nbytes)) < 0) goto done;
}
- else if (fd_bits & 1)
- FD_SET(tls_out.active.sock, &rfds);
- /* handle outbound data */
+ /* Handle outbound data. We cannot combine payload and the TLS-close
+ due to the limitations of the (pipe) channel feeding us. */
if (FD_ISSET(pfd[0], &rfds))
if ((rc = read(pfd[0], buf, bsize)) <= 0)
{
- fd_bits = 0;
- tls_close(ct_ctx, TLS_SHUTDOWN_NOWAIT);
- ct_ctx = NULL;
+ fd_bits &= ~2;
+ FD_CLR(pfd[0], &rfds);
+
+# ifdef EXIM_TCP_CORK /* Use _CORK to get TLS Close Notify in FIN segment */
+ (void) setsockopt(tls_out.active.sock, IPPROTO_TCP, EXIM_TCP_CORK, US &on, sizeof(on));
+# endif
+ tls_shutdown_wr(ct_ctx);
+ send_tls_shutdown = FALSE;
+ shutdown(tls_out.active.sock, SHUT_WR);
}
else
{
if ((i = tls_write(ct_ctx, buf + nbytes, rc - nbytes, FALSE)) < 0)
goto done;
}
- else if (fd_bits & 2)
- FD_SET(pfd[0], &rfds);
+
+ if (fd_bits & 1) FD_SET(tls_out.active.sock, &rfds);
+ if (fd_bits & 2) FD_SET(pfd[0], &rfds);
}
done:
+ if (send_tls_shutdown) tls_close(ct_ctx, TLS_SHUTDOWN_NOWAIT);
+ ct_ctx = NULL;
testharness_pause_ms(100); /* let logging complete */
- exim_exit(0, US"TLS proxy");
+ exim_exit(EXIT_SUCCESS);
}
#endif
/* If continue_hostname is not null, we get here only when continuing to
deliver down an existing channel. The channel was passed as the standard
-input. TLS is never active on a passed channel; the previous process always
-closes it down before passing the connection on.
+input. TLS is never active on a passed channel; the previous process either
+closes it down before passing the connection on, or inserts a TLS-proxy
+process and passes on a cleartext conection.
Otherwise, we have to make a connection to the remote host, and do the
initial protocol exchange.
uschar *message = NULL;
uschar new_message_id[MESSAGE_ID_LENGTH + 1];
smtp_context * sx = store_get(sizeof(*sx), TRUE); /* tainted, for the data buffers */
+#ifdef SUPPORT_DANE
+BOOL dane_held;
+#endif
+BOOL tcw_done = FALSE, tcw = FALSE;
-suppress_tls = suppress_tls; /* stop compiler warning when no TLS support */
*message_defer = FALSE;
memset(sx, 0, sizeof(*sx));
gettimeofday(&sx->delivery_start, NULL);
sx->sync_addr = sx->first_addr = addrlist;
-/* Get the channel set up ready for a message (MAIL FROM being the next
-SMTP command to send */
+#ifdef SUPPORT_DANE
+DANE_DOMAINS:
+dane_held = FALSE;
+#endif
+
+/* 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)) != OK)
{
timesince(&addrlist->delivery_time, &sx->delivery_start);
- return rc;
+ yield = rc;
+ goto TIDYUP;
}
+#ifdef SUPPORT_DANE
+/* If the connection used DANE, ignore for now any addresses with incompatible
+domains. The SNI has to be the domain. Arrange a whole new TCP conn later,
+just in case only TLS isn't enough. */
+
+if (sx->conn_args.dane)
+ {
+ const uschar * dane_domain = sx->first_addr->domain;
+
+ for (address_item * a = sx->first_addr->next; a; a = a->next)
+ if ( a->transport_return == PENDING_DEFER
+ && Ustrcmp(dane_domain, a->domain) != 0)
+ {
+ DEBUG(D_transport) debug_printf("DANE: holding %s for later\n", a->domain);
+ dane_held = TRUE;
+ a->transport_return = DANE;
+ }
+ }
+#endif
+
/* 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. */
+set it up. This cannot be done until the identity of the host is known. */
if (tblock->filter_command)
{
if (continue_hostname && continue_sequence == 1)
{
- sx->peer_offered = smtp_peer_options;
/* sx->pending_MAIL = FALSE; */
sx->ok = TRUE;
/* sx->next_addr = NULL; */
report_time_since(&t0, US"dkim_exim_sign_init (delta)");
# endif
}
+#endif
+
+ /* See if we can pipeline QUIT. Reasons not to are
+ - pipelining not active
+ - not ok to send quit
+ - errors in amtp transation responses
+ - more addrs to send for this message or this host
+ - this message was being retried
+ - more messages for this host
+ If we can, we want the message-write to not flush (the tail end of) its data out. */
+
+ if ( sx->pipelining_used
+ && (sx->ok && sx->completed_addr || sx->peer_offered & OPTION_CHUNKING)
+ && sx->send_quit
+ && !(sx->first_addr || f.continue_more)
+ && f.deliver_firsttime
+ )
+ {
+ smtp_compare_t t_compare =
+ {.tblock = tblock, .current_sender_address = sender_address};
+
+ tcw_done = TRUE;
+ tcw =
+#ifndef DISABLE_TLS
+ ( tls_out.active.sock < 0 && !continue_proxy_cipher
+ || verify_check_given_host(CUSS &ob->hosts_nopass_tls, host) != OK
+ )
+ &&
+#endif
+ transport_check_waiting(tblock->name, host->name,
+ tblock->connection_max_messages, new_message_id,
+ (oicf)smtp_are_same_identities, (void*)&t_compare);
+ if (!tcw)
+ {
+ HDEBUG(D_transport) debug_printf("will pipeline QUIT\n");
+ tctx.options |= topt_no_flush;
+ }
+ }
+
+#ifndef DISABLE_DKIM
sx->ok = dkim_transport_write_message(&tctx, &ob->dkim, CUSS &message);
#else
sx->ok = transport_write_message(&tctx, 0);
smtp_command = US"end of data";
+ /* If we can pipeline a QUIT with the data them send it now. If a new message
+ for this host appeared in the queue while data was being sent, we will not see
+ it and it will have to wait for a queue run. If there was one but another
+ thread took it, we might attempt to send it - but locking of spoolfiles will
+ detect that. Use _MORE to get QUIT in FIN segment. */
+
+ if (tcw_done && !tcw)
+ {
+ /*XXX jgh 2021/03/10 google et. al screwup. G, at least, sends TCP FIN in response to TLS
+ close-notify. Under TLS 1.3, violating RFC.
+ However, TLS 1.2 does not have half-close semantics. */
+
+ if ( sx->cctx.tls_ctx
+#if 0 && !defined(DISABLE_TLS)
+ && Ustrcmp(tls_out.ver, "TLS1.3") != 0
+#endif
+ || !f.deliver_firsttime
+ )
+ { /* Send QUIT now and not later */
+ (void)smtp_write_command(sx, SCMD_FLUSH, "QUIT\r\n");
+ sx->send_quit = FALSE;
+ }
+ else
+ { /* add QUIT to the output buffer */
+ (void)smtp_write_command(sx, SCMD_MORE, "QUIT\r\n");
+ sx->send_quit = FALSE; /* avoid sending it later */
+
+#ifndef DISABLE_TLS
+ if (sx->cctx.tls_ctx) /* need to send TLS Cloe Notify */
+ {
+# ifdef EXIM_TCP_CORK /* Use _CORK to get Close Notify in FIN segment */
+ (void) setsockopt(sx->cctx.sock, IPPROTO_TCP, EXIM_TCP_CORK, US &on, sizeof(on));
+# endif
+ tls_shutdown_wr(sx->cctx.tls_ctx);
+ sx->send_tlsclose = FALSE; /* avoid later repeat */
+ }
+#endif
+ HDEBUG(D_transport|D_acl|D_v) debug_printf_indent(" SMTP(shutdown)>>\n");
+ shutdown(sx->cctx.sock, SHUT_WR); /* flush output buffer, with TCP FIN */
+ }
+ }
+
if (sx->peer_offered & OPTION_CHUNKING && sx->cmd_count > 1)
{
/* Reap any outstanding MAIL & RCPT commands, but not a DATA-go-ahead */
!sx->lmtp
)
{
- const uschar *s = string_printing(sx->buffer);
+ const uschar * s = string_printing(sx->buffer);
/* deconst cast ok here as string_printing was checked to have alloc'n'copied */
- conf = (s == sx->buffer)? US string_copy(s) : US s;
+ conf = s == sx->buffer ? US string_copy(s) : US s;
}
/* Process all transported addresses - for LMTP or PRDR, read a status for
- each one. */
+ each one. We used to drop out at first_addr, until someone returned a 452
+ followed by a 250... and we screwed up the accepted addresses. */
- for (address_item * addr = addrlist; addr != sx->first_addr; addr = addr->next)
+ for (address_item * addr = addrlist; addr; addr = addr->next)
{
if (addr->transport_return != PENDING_OK) continue;
{
save_errno = errno;
message = NULL;
- sx->send_quit = check_response(host, &save_errno, addrlist->more_errno,
+ /* Clear send_quit flag if needed. Do not set. */
+ sx->send_quit &= check_response(host, &save_errno, addrlist->more_errno,
sx->buffer, &code, &message, &pass_message);
goto FAILED;
}
*message_defer = TRUE;
}
+#ifdef TIOCOUTQ
+ DEBUG(D_transport) if (sx->cctx.sock >= 0)
+ {
+ int n;
+ if (ioctl(sx->cctx.sock, TIOCOUTQ, &n) == 0)
+ debug_printf("%d bytes remain in socket output buffer\n", n);
+ }
+#endif
}
-
/* Otherwise, we have an I/O error or a timeout other than after MAIL or
".", or some other transportation error. We defer all addresses and yield
DEFER, except for the case of failed add_headers expansion, or a transport
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;
+ smtp_compare_t t_compare =
+ {.tblock = tblock, .current_sender_address = sender_address};
- if ( sx->first_addr != NULL
- || f.continue_more
+ if ( sx->first_addr /* more addrs for this message */
+ || f.continue_more /* more addrs for continued-host */
+ || tcw_done && tcw /* more messages for host */
|| (
#ifndef DISABLE_TLS
( tls_out.active.sock < 0 && !continue_proxy_cipher
&&
#endif
transport_check_waiting(tblock->name, host->name,
- tblock->connection_max_messages, new_message_id, &more,
+ tblock->connection_max_messages, new_message_id,
(oicf)smtp_are_same_identities, (void*)&t_compare)
) )
{
#endif
int socket_fd = sx->cctx.sock;
-
- if (sx->first_addr != NULL) /* More addresses still to be sent */
- { /* in this run of the transport */
+ if (sx->first_addr) /* More addresses still to be sent */
+ { /* for this message */
continue_sequence++; /* Causes * in logging */
pipelining_active = sx->pipelining_used; /* was cleared at DATA */
goto SEND_MESSAGE;
the socket on. */
tls_close(sx->cctx.tls_ctx, TLS_SHUTDOWN_WAIT);
+ sx->send_tlsclose = FALSE;
sx->cctx.tls_ctx = NULL;
+ tls_out.active.sock = -1;
smtp_peer_options = smtp_peer_options_wrap;
sx->ok = !sx->smtps
&& smtp_write_command(sx, SCMD_FLUSH, "EHLO %s\r\n", sx->helo_data)
'2', ob->command_timeout);
if (sx->ok && f.continue_more)
- return yield; /* More addresses for another run */
+ goto TIDYUP; /* More addresses for another run */
}
else
{
else
#endif
if (f.continue_more)
- return yield; /* More addresses for another run */
+ goto TIDYUP; /* More addresses for another run */
/* If the socket is successfully passed, we mustn't send QUIT (or
indeed anything!) from here. */
#ifndef DISABLE_TLS
if (tls_out.active.sock >= 0)
{
- int pid = fork();
+ int pid = exim_fork(US"tls-proxy-interproc");
if (pid == 0) /* child; fork again to disconnect totally */
{
- testharness_pause_ms(100); /* let parent debug out */
/* does not return */
smtp_proxy_tls(sx->cctx.tls_ctx, sx->buffer, sizeof(sx->buffer), pfd,
ob->command_timeout);
if (pid > 0) /* parent */
{
- DEBUG(D_transport) debug_printf("proxy-proc inter-pid %d\n", pid);
close(pfd[0]);
/* tidy the inter-proc to disconn the proxy proc */
waitpid(pid, NULL, 0);
sx->cctx.sock = -1;
continue_transport = NULL;
continue_hostname = NULL;
- return yield;
+ goto TIDYUP;
}
log_write(0, LOG_PANIC_DIE, "fork failed");
}
}
/* End off tidily with QUIT unless the connection has died or the socket has
-been passed to another process. There has been discussion on the net about what
-to do after sending QUIT. The wording of the RFC suggests that it is necessary
-to wait for a response, but on the other hand, there isn't anything one can do
-with an error response, other than log it. Exim used to do that. However,
-further discussion suggested that it is positively advantageous not to wait for
-the response, but to close the session immediately. This is supposed to move
-the TCP/IP TIME_WAIT state from the server to the client, thereby removing some
-load from the server. (Hosts that are both servers and clients may not see much
-difference, of course.) Further discussion indicated that this was safe to do
-on Unix systems which have decent implementations of TCP/IP that leave the
-connection around for a while (TIME_WAIT) after the application has gone away.
-This enables the response sent by the server to be properly ACKed rather than
-timed out, as can happen on broken TCP/IP implementations on other OS.
-
-This change is being made on 31-Jul-98. After over a year of trouble-free
-operation, the old commented-out code was removed on 17-Sep-99. */
+been passed to another process. */
SEND_QUIT:
-#ifdef TCP_CORK
-(void) setsockopt(sx->cctx.sock, IPPROTO_TCP, TCP_CORK, US &on, sizeof(on));
+if (sx->send_quit)
+ { /* Use _MORE to get QUIT in FIN segment */
+ (void)smtp_write_command(sx, SCMD_MORE, "QUIT\r\n");
+#ifndef DISABLE_TLS
+ if (sx->cctx.tls_ctx)
+ {
+# ifdef EXIM_TCP_CORK /* Use _CORK to get TLS Close Notify in FIN segment */
+ (void) setsockopt(sx->cctx.sock, IPPROTO_TCP, EXIM_TCP_CORK, US &on, sizeof(on));
+# endif
+ tls_shutdown_wr(sx->cctx.tls_ctx);
+ sx->send_tlsclose = FALSE;
+ }
#endif
-if (sx->send_quit) (void)smtp_write_command(sx, SCMD_FLUSH, "QUIT\r\n");
+ }
END_OFF:
-#ifndef DISABLE_TLS
-tls_close(sx->cctx.tls_ctx, TLS_SHUTDOWN_NOWAIT);
-sx->cctx.tls_ctx = NULL;
-#endif
-
/* 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,
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_indent(" SMTP(close)>>\n");
if (sx->send_quit)
{
+ /* This flushes data queued in the socket, being the QUIT and any TLS Close,
+ sending them along with the client FIN flag. Us (we hope) sending FIN first
+ means we (client) take the TIME_WAIT state, so the server (which likely has a
+ higher connection rate) does not have to. */
+
+ HDEBUG(D_transport|D_acl|D_v) debug_printf_indent(" SMTP(shutdown)>>\n");
shutdown(sx->cctx.sock, SHUT_WR);
+ }
+
+if (sx->send_quit || tcw_done && !tcw)
+ {
+ /* Wait for (we hope) ack of our QUIT, and a server FIN. Discard any data
+ received, then discard the socket. Any packet received after then, or receive
+ data still in the socket, will get a RST - hence the pause/drain. */
+
+ /* Reap the response to QUIT, timing out after one second */
+ (void) smtp_read_response(sx, sx->buffer, sizeof(sx->buffer), '2', 1);
+#ifndef DISABLE_TLS
+ if (sx->cctx.tls_ctx)
+ {
+ int n;
+
+ /* Reap the TLS Close Notify from the server, timing out after one second */
+ sigalrm_seen = FALSE;
+ ALARM(1);
+ do
+ n = tls_read(sx->cctx.tls_ctx, sx->inbuffer, sizeof(sx->inbuffer));
+ while (!sigalrm_seen && n > 0);
+ ALARM_CLR(0);
+
+# ifdef EXIM_TCP_CORK
+ (void) setsockopt(sx->cctx.sock, IPPROTO_TCP, EXIM_TCP_CORK, US &on, sizeof(on));
+# endif
+ tls_close(sx->cctx.tls_ctx, TLS_SHUTDOWN_WAIT);
+ sx->cctx.tls_ctx = NULL;
+ }
+#endif
millisleep(20);
- testharness_pause_ms(200);
if (fcntl(sx->cctx.sock, F_SETFL, O_NONBLOCK) == 0)
- for (int i = 16; read(sx->cctx.sock, sx->inbuffer, sizeof(sx->inbuffer)) > 0 && i > 0;)
- i--; /* drain socket */
+ for (int i = 16, n; /* drain socket */
+ (n = read(sx->cctx.sock, sx->inbuffer, sizeof(sx->inbuffer))) > 0 && i > 0;
+ i--) HDEBUG(D_transport|D_acl|D_v)
+ {
+ int m = MIN(n, 64);
+ debug_printf_indent(" SMTP(drain %d bytes)<< %.*s\n", n, m, sx->inbuffer);
+ for (m = 0; m < n; m++)
+ debug_printf("0x%02x\n", sx->inbuffer[m]);
+ }
}
+HDEBUG(D_transport|D_acl|D_v) debug_printf_indent(" SMTP(close)>>\n");
(void)close(sx->cctx.sock);
#ifndef DISABLE_EVENT
(void) event_raise(tblock->event_action, US"tcp:close", NULL);
#endif
+#ifdef SUPPORT_DANE
+if (dane_held)
+ {
+ sx->first_addr = NULL;
+ for (address_item * a = sx->addrlist->next; a; a = a->next)
+ if (a->transport_return == DANE)
+ {
+ a->transport_return = PENDING_DEFER;
+ if (!sx->first_addr)
+ {
+ /* Remember the new start-point in the addrlist, for smtp_setup_conn()
+ to get the domain string for SNI */
+
+ sx->first_addr = a;
+ DEBUG(D_transport) debug_printf("DANE: go-around for %s\n", a->domain);
+ }
+ }
+ goto DANE_DOMAINS;
+ }
+#endif
+
continue_transport = NULL;
continue_hostname = NULL;
return yield;
+
+TIDYUP:
+#ifdef SUPPORT_DANE
+if (dane_held) for (address_item * a = sx->addrlist->next; a; a = a->next)
+ if (a->transport_return == DANE)
+ a->transport_return = PENDING_DEFER;
+#endif
+return yield;
}
cutthrough.cctx.sock >= 0 ? cutthrough.cctx.sock : 0);
}
+/* Check the restrictions on line length */
+
+if (max_received_linelength > ob->message_linelength_limit)
+ {
+ struct timeval now;
+ gettimeofday(&now, NULL);
+
+ for (address_item * addr = addrlist; addr; addr = addr->next)
+ if (addr->transport_return == DEFER)
+ addr->transport_return = PENDING_DEFER;
+
+ set_errno_nohost(addrlist, ERRNO_SMTPFORMAT,
+ US"message has lines too long for transport", FAIL, TRUE, &now);
+ goto END_TRANSPORT;
+ }
+
/* Set the flag requesting that these hosts be added to the waiting
database if the delivery fails temporarily or if we are running with
queue_smtp or a 2-stage queue run. This gets unset for certain
{
uschar *s = ob->hosts;
- if (Ustrchr(s, '$') != NULL)
+ if (Ustrchr(s, '$'))
{
if (!(expanded_hosts = expand_string(s)))
{
were not in it. We don't want to hold up all SMTP deliveries! Except when
doing a two-stage queue run, don't do this if forcing. */
- if ((!f.deliver_force || f.queue_2stage) && (f.queue_smtp ||
- match_isinlist(addrlist->domain,
- (const uschar **)&queue_smtp_domains, 0,
- &domainlist_anchor, NULL, MCL_DOMAIN, TRUE, NULL) == OK))
+ if ( (!f.deliver_force || f.queue_2stage)
+ && ( f.queue_smtp
+ || match_isinlist(addrlist->domain,
+ CUSS &queue_smtp_domains, 0,
+ &domainlist_anchor, NULL, MCL_DOMAIN, TRUE, NULL) == OK)
+ )
{
+ DEBUG(D_transport) debug_printf("first-pass routing only\n");
expired = FALSE;
for (address_item * addr = addrlist; addr; addr = addr->next)
if (addr->transport_return == DEFER)
- addr->message = US"domain matches queue_smtp_domains, or -odqs set";
+ addr->message = US"first-pass only routing due to -odqs, "
+ "queue_smtp_domains or control=queue";
continue; /* With next host */
}
because connections to the same host from a different interface should be
treated separately. */
- host_af = Ustrchr(host->address, ':') == NULL ? AF_INET : AF_INET6;
+ host_af = Ustrchr(host->address, ':') ? AF_INET6 : AF_INET;
{
uschar * s = ob->interface;
if (s && *s)
#ifndef DISABLE_EVENT
/* If the last host gave a defer raise a per-message event */
- if (!nexthost && (message_defer || rc == DEFER))
+ if ( !( nexthost
+ && unexpired_hosts_tried < ob->hosts_max_try
+ && total_hosts_tried < ob->hosts_max_try_hardlimit
+ )
+ && (message_defer || rc == DEFER)
+ )
deferred_event_raise(first_addr, host, US"msg:defer");
#endif
}