summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJeremy Harris <jgh146exb@wizmail.org>2018-11-03 23:13:34 +0000
committerJeremy Harris <jgh146exb@wizmail.org>2018-11-05 16:55:33 +0000
commitee8b809061baea861fc87c41bcb72a62d76b0047 (patch)
tree015dadad49f70deb9a2bc0887452ced9824b9f8b
parentb536a578fbabdc9d39da53d54a8d7700ba537431 (diff)
Squashed commit of PIPE_CONNECT
-rw-r--r--doc/doc-docbook/spec.xfpt1
-rw-r--r--doc/doc-txt/NewStuff3
-rw-r--r--doc/doc-txt/OptionLists.txt4
-rw-r--r--doc/doc-txt/experimental-spec.txt80
-rw-r--r--src/src/config.h.defaults1
-rw-r--r--src/src/dbstuff.h18
-rw-r--r--src/src/deliver.c42
-rw-r--r--src/src/exim.c9
-rw-r--r--src/src/functions.h3
-rw-r--r--src/src/globals.c12
-rw-r--r--src/src/globals.h14
-rw-r--r--src/src/ip.c6
-rw-r--r--src/src/macro_predef.c3
-rw-r--r--src/src/macros.h25
-rw-r--r--src/src/queue.c2
-rw-r--r--src/src/readconf.c13
-rw-r--r--src/src/receive.c14
-rw-r--r--src/src/smtp_in.c124
-rw-r--r--src/src/smtp_out.c192
-rw-r--r--src/src/structs.h21
-rw-r--r--src/src/tls-openssl.c12
-rw-r--r--src/src/transports/smtp.c1145
-rw-r--r--src/src/transports/smtp.h24
-rw-r--r--src/src/verify.c10
-rw-r--r--test/aux-var-src/tls_conf_prefix3
-rw-r--r--test/confs/00023
-rw-r--r--test/confs/05643
-rw-r--r--test/confs/09003
-rw-r--r--test/confs/09063
-rw-r--r--test/confs/405063
l---------test/confs/40511
l---------test/confs/40521
-rw-r--r--test/confs/405347
-rw-r--r--test/confs/405662
-rw-r--r--test/confs/405850
-rw-r--r--test/confs/406058
-rw-r--r--test/confs/406159
l---------test/confs/40621
l---------test/confs/40631
-rw-r--r--test/confs/406472
-rw-r--r--test/confs/406573
l---------test/confs/40661
l---------test/confs/40671
-rw-r--r--test/confs/59103
-rw-r--r--test/log/40274
-rw-r--r--test/log/40506
-rw-r--r--test/log/405144
-rw-r--r--test/log/405252
-rw-r--r--test/log/405323
-rw-r--r--test/log/405623
-rw-r--r--test/log/405844
-rw-r--r--test/log/406054
-rw-r--r--test/log/406133
-rw-r--r--test/log/406254
-rw-r--r--test/log/406333
-rw-r--r--test/log/406448
-rw-r--r--test/log/406523
-rw-r--r--test/log/406648
-rw-r--r--test/log/406723
-rwxr-xr-xtest/runtest5
-rw-r--r--test/scripts/4050-pipe-conn/4050103
-rw-r--r--test/scripts/4050-pipe-conn/405159
-rw-r--r--test/scripts/4050-pipe-conn/4052243
-rw-r--r--test/scripts/4050-pipe-conn/405331
-rw-r--r--test/scripts/4050-pipe-conn/REQUIRES1
-rw-r--r--test/scripts/4056-pipe-conn-auth/405631
-rw-r--r--test/scripts/4056-pipe-conn-auth/REQUIRES2
-rw-r--r--test/scripts/4058-pipe-conn-tfo/405878
-rw-r--r--test/scripts/4058-pipe-conn-tfo/REQUIRES2
-rw-r--r--test/scripts/4060-pipe-conn-gnutls/406071
-rw-r--r--test/scripts/4060-pipe-conn-gnutls/406143
-rw-r--r--test/scripts/4060-pipe-conn-gnutls/REQUIRES2
-rw-r--r--test/scripts/4062-pipe-conn-openssl/406272
-rw-r--r--test/scripts/4062-pipe-conn-openssl/406343
-rw-r--r--test/scripts/4062-pipe-conn-openssl/REQUIRES2
-rw-r--r--test/scripts/4064-pipe-conn-gnutls-auth/406460
-rw-r--r--test/scripts/4064-pipe-conn-gnutls-auth/406532
-rw-r--r--test/scripts/4064-pipe-conn-gnutls-auth/REQUIRES3
-rw-r--r--test/scripts/4066-pipe-conn-openssl-auth/406661
-rw-r--r--test/scripts/4066-pipe-conn-openssl-auth/406732
-rw-r--r--test/scripts/4066-pipe-conn-openssl-auth/REQUIRES3
-rw-r--r--test/stderr/20134
-rw-r--r--test/stderr/20352
-rw-r--r--test/stderr/21134
-rw-r--r--test/stderr/21352
-rw-r--r--test/stderr/405267
-rw-r--r--test/stdout/4050133
-rw-r--r--test/stdout/4052201
88 files changed, 3749 insertions, 441 deletions
diff --git a/doc/doc-docbook/spec.xfpt b/doc/doc-docbook/spec.xfpt
index c84c9b4d1..4e99e6c0d 100644
--- a/doc/doc-docbook/spec.xfpt
+++ b/doc/doc-docbook/spec.xfpt
@@ -36965,6 +36965,7 @@ immediately after the time and date.
&%pipelining%&: A field is added to delivery and accept
log lines when the ESMTP PIPELINING extension was used.
The field is a single "L".
+
On accept lines, where PIPELINING was offered but not used by the client,
the field has a minus appended.
.next
diff --git a/doc/doc-txt/NewStuff b/doc/doc-txt/NewStuff
index cc9721ada..fb336b8af 100644
--- a/doc/doc-txt/NewStuff
+++ b/doc/doc-txt/NewStuff
@@ -17,7 +17,8 @@ Version 4.92
2. The ${readsocket } expansion item now takes a "tls" option, doing the
obvious thing.
- 3. EXPERIMENTAL_REQUIRETLS. See the experimental.spec file.
+ 3. EXPERIMENTAL_REQUIRETLS and EXPERIMENTAL_PIPE_CONNECT optional build
+ features. See the experimental.spec file.
4. If built with SUPPORT_I18N a "utf8_downconvert" option on the smtp transport.
diff --git a/doc/doc-txt/OptionLists.txt b/doc/doc-txt/OptionLists.txt
index 8d20f6fe2..fec47946a 100644
--- a/doc/doc-txt/OptionLists.txt
+++ b/doc/doc-txt/OptionLists.txt
@@ -54,7 +54,7 @@ acl_not_smtp_mime string* unset main
acl_smtp_auth string* unset main 4.00
acl_smtp_connect string* unset main 4.11
acl_smtp_data string* unset main 4.00
-acl_smtp_data_prdr string* unset main 4.82 with experimental_prdr
+acl_smtp_data_prdr string* unset main 4.82 with experimental_prdr, 4.83 unless disable_prdr
acl_smtp_dkim string* unset main 4.70 unless disable_dkim
acl_smtp_etrn string* unset main 4.00
acl_smtp_expn string* unset main 4.00
@@ -300,6 +300,7 @@ hosts_max_try_hardlimit integer 50 smtp
hosts_nopass_tls host list unset smtp 4.00
hosts_noproxy_tls host list "*" smtp 4.90
hosts_override boolean false smtp 2.11
+hosts_pipe_connect host_list unset smtp 4.93 if experimental_pipe_connect
hosts_randomize boolean false manualroute 4.00
false smtp 3.14
hosts_require_auth host list unset smtp 4.00
@@ -412,6 +413,7 @@ pid_file_path string ++ main
pipe_as_creator boolean false pipe
pipe_transport string* unset redirect 4.00
pipelining_advertise_hosts host list "*" main 4.14
+pipelining__connect_advertise_hosts host list "*" main 4.92 if experimental_pipe_connect
port integer 0 iplookup 4.00
string "smtp" smtp
preserve_message_logs boolean false main
diff --git a/doc/doc-txt/experimental-spec.txt b/doc/doc-txt/experimental-spec.txt
index 49935fb40..d5a75f5b3 100644
--- a/doc/doc-txt/experimental-spec.txt
+++ b/doc/doc-txt/experimental-spec.txt
@@ -904,6 +904,86 @@ Note that REQUIRETLS is only advertised once a TLS connection is achieved
like "swaks -s 127.0.0.1 -tls -q HELO".
+
+
+Early pipelining support
+------------------------
+Ref: https://datatracker.ietf.org/doc/draft-harris-early-pipe/
+
+If compiled with EXPERIMENTAL_PIPE_CONNECT support is included for this feature.
+The server advertises the feature in its EHLO response, currently using the name
+"X_PIPE_CONNECT" (this will change, some time in the future).
+A client may cache this information, along with the rest of the EHLO response,
+and use it for later connections. Those later ones can send esmtp commands before
+a banner is received.
+
+Up to 1.5 roundtrip times can be taken out of cleartext connections, 2.5 on
+STARTTLS connections.
+
+In combination with the traditional PIPELINING feature the following example
+sequences are possible (among others):
+
+(client) (server)
+
+EHLO,MAIL,RCPT,DATA ->
+ <- banner,EHLO-resp,MAIL-ack,RCPT-ack,DATA-goahead
+message-data ->
+------
+
+EHLO,MAIL,RCPT,BDAT ->
+ <- banner,EHLO-resp,MAIL-ack,RCPT-ack
+message-data ->
+------
+
+EHLO,STARTTLS ->
+ <- banner,EHLO-resp,TLS-goahead
+TLS1.2-client-hello ->
+ <- TLS-server-hello,cert,hello-done
+client-Kex,change-cipher,finished ->
+ <- change-cipher,finshed
+EHLO,MAIL,RCPT,DATA ->
+ <- EHLO-resp,MAIL-ack,RCPT-ack,DATA-goahead
+
+------
+(tls-on-connect)
+TLS1.2-client-hello ->
+ <- TLS-server-hello,cert,hello-done
+client-Kex,change-cipher,finished ->
+ <- change-cipher,finshed
+ <- banner
+EHLO,MAIL,RCPT,DATA ->
+ <- EHLO-resp,MAIL-ack,RCPT-ack,DATA-goahead
+
+Where the initial client packet is SMTP, it can combine with the TCP Fast Open
+feature and be sent in the TCP SYN.
+
+
+A main-section option "pipelining_connect_advertise_hosts" (default: *)
+and an smtp transport option "hosts_pipe_connect" (default: unset)
+control the feature.
+
+If the "pipelining" log_selector is enabled, the "L" field in server <=
+log lines has a period appended if the feature was advertised but not used;
+or has an asterisk appended if the feature was used. In client => lines
+the "L" field has an asterisk appended if the feature was used.
+
+The "retry_data_expire" option controls cache invalidation.
+Entries are also rewritten (or cleared) if the adverised features
+change.
+
+
+NOTE: since the EHLO command must be constructed before the connection is
+made it cannot depend on the interface IP address that will be used.
+Transport configurations should be checked for this. An example avoidance:
+
+ helo_data = ${if def:sending_ip_address \
+ {${lookup dnsdb{>! ptr=$sending_ip_address} \
+ {${sg{$value} {^([^!]*).*\$} {\$1}}} fail}} \
+ {$primary_hostname}}
+
+
+
+
--------------------------------------------------------------
End of file
--------------------------------------------------------------
diff --git a/src/src/config.h.defaults b/src/src/config.h.defaults
index 4fb30dd5f..7c2e534f3 100644
--- a/src/src/config.h.defaults
+++ b/src/src/config.h.defaults
@@ -198,6 +198,7 @@ Do not put spaces between # and the 'define'.
#define EXPERIMENTAL_DMARC
#define DMARC_TLD_FILE "/etc/exim/opendmarc.tlds"
#define EXPERIMENTAL_LMDB
+#define EXPERIMENTAL_PIPE_CONNECT
#define EXPERIMENTAL_REQUIRETLS
#define EXPERIMENTAL_QUEUEFILE
#define EXPERIMENTAL_SRS
diff --git a/src/src/dbstuff.h b/src/src/dbstuff.h
index afb84f912..02cfa1446 100644
--- a/src/src/dbstuff.h
+++ b/src/src/dbstuff.h
@@ -786,5 +786,23 @@ typedef struct {
uschar bloom[40]; /* Bloom filter which may be larger than this */
} dbdata_ratelimit_unique;
+#ifdef EXPERIMENTAL_PIPE_CONNECT
+/* This structure records the EHLO responses, cleartext and crypted,
+for an IP, as bitmasks (cf. OPTION_TLS) */
+
+typedef struct {
+ unsigned short cleartext_features;
+ unsigned short crypted_features;
+ unsigned short cleartext_auths;
+ unsigned short crypted_auths;
+} ehlo_resp_precis;
+
+typedef struct {
+ time_t time_stamp;
+ /*************/
+ ehlo_resp_precis data;
+} dbdata_ehlo_resp;
+#endif
+
/* End of dbstuff.h */
diff --git a/src/src/deliver.c b/src/src/deliver.c
index 4624719f8..9f292fc53 100644
--- a/src/src/deliver.c
+++ b/src/src/deliver.c
@@ -798,7 +798,7 @@ if (LOGGING(proxy) && proxy_local_address)
g = d_log_interface(g);
if (testflag(addr, af_tcp_fastopen))
- g = string_catn(g, US" TFO", 4);
+ g = string_catn(g, US" TFO*", testflag(addr, af_tcp_fastopen_data) ? 5 : 4);
return g;
}
@@ -1247,8 +1247,15 @@ else
}
}
- if (LOGGING(pipelining) && testflag(addr, af_pipelining))
- g = string_catn(g, US" L", 2);
+ if (LOGGING(pipelining))
+ {
+ if (testflag(addr, af_pipelining))
+ g = string_catn(g, US" L", 2);
+#ifdef EXPERIMENTAL_PIPE_CONNECT
+ if (testflag(addr, af_early_pipe))
+ g = string_catn(g, US"*", 1);
+#endif
+ }
#ifndef DISABLE_PRDR
if (testflag(addr, af_prdr_used))
@@ -3576,6 +3583,9 @@ while (!done)
case 'L':
switch (*subid)
{
+#ifdef EXPERIMENTAL_PIPE_CONNECT
+ case 2: setflag(addr, af_early_pipe); /*FALLTHROUGH*/
+#endif
case 1: setflag(addr, af_pipelining); break;
}
break;
@@ -3587,6 +3597,7 @@ while (!done)
case 'T':
setflag(addr, af_tcp_fastopen_conn);
if (*subid > '0') setflag(addr, af_tcp_fastopen);
+ if (*subid > '1') setflag(addr, af_tcp_fastopen_data);
break;
case 'D':
@@ -4884,6 +4895,11 @@ all pipes, so I do not see a reason to use non-blocking IO here
#endif
if (testflag(addr, af_pipelining))
+#ifdef EXPERIMENTAL_PIPE_CONNECT
+ if (testflag(addr, af_early_pipe))
+ rmt_dlv_checked_write(fd, 'L', '2', NULL, 0);
+ else
+#endif
rmt_dlv_checked_write(fd, 'L', '1', NULL, 0);
if (testflag(addr, af_chunking_used))
@@ -4891,7 +4907,9 @@ all pipes, so I do not see a reason to use non-blocking IO here
if (testflag(addr, af_tcp_fastopen_conn))
rmt_dlv_checked_write(fd, 'T',
- testflag(addr, af_tcp_fastopen) ? '1' : '0', NULL, 0);
+ testflag(addr, af_tcp_fastopen) ? testflag(addr, af_tcp_fastopen_data)
+ ? '2' : '1' : '0',
+ NULL, 0);
memcpy(big_buffer, &addr->dsn_aware, sizeof(addr->dsn_aware));
rmt_dlv_checked_write(fd, 'D', '0', big_buffer, sizeof(addr->dsn_aware));
@@ -7305,7 +7323,7 @@ if (addr_senddsn)
}
else /* Creation of child succeeded */
{
- FILE *f = fdopen(fd, "wb");
+ FILE * f = fdopen(fd, "wb");
/* header only as required by RFC. only failure DSN needs to honor RET=FULL */
uschar * bound;
transport_ctx tctx = {{0}};
@@ -7386,8 +7404,10 @@ if (addr_senddsn)
/* Write the original email out */
- tctx.u.fd = fileno(f);
+ tctx.u.fd = fd;
tctx.options = topt_add_return_path | topt_no_body;
+ /*XXX hmm, retval ignored.
+ Could error for any number of reasons, and they are not handled. */
transport_write_message(&tctx, 0);
fflush(f);
@@ -7855,6 +7875,7 @@ wording. */
tctx.options = topt;
tb.add_headers = dsnnotifyhdr;
+ /*XXX no checking for failure! buggy! */
transport_write_message(&tctx, 0);
}
fflush(fp);
@@ -8321,6 +8342,7 @@ else if (addr_defer != (address_item *)(+1))
return_path = sender_address; /* In case not previously set */
/* Write the original email out */
+ /*XXX no checking for failure! buggy! */
transport_write_message(&tctx, 0);
fflush(f);
@@ -8484,8 +8506,7 @@ if (!regex_SIZE) regex_SIZE =
regex_must_compile(US"\\n250[\\s\\-]SIZE(\\s|\\n|$)", FALSE, TRUE);
if (!regex_AUTH) regex_AUTH =
- regex_must_compile(US"\\n250[\\s\\-]AUTH\\s+([\\-\\w\\s]+)(?:\\n|$)",
- FALSE, TRUE);
+ regex_must_compile(AUTHS_REGEX, FALSE, TRUE);
#ifdef SUPPORT_TLS
if (!regex_STARTTLS) regex_STARTTLS =
@@ -8515,6 +8536,11 @@ if (!regex_DSN) regex_DSN =
if (!regex_IGNOREQUOTA) regex_IGNOREQUOTA =
regex_must_compile(US"\\n250[\\s\\-]IGNOREQUOTA(\\s|\\n|$)", FALSE, TRUE);
+
+#ifdef EXPERIMENTAL_PIPE_CONNECT
+if (!regex_EARLY_PIPE) regex_EARLY_PIPE =
+ regex_must_compile(US"\\n250[\\s\\-]" EARLY_PIPE_FEATURE_NAME "(\\s|\\n|$)", FALSE, TRUE);
+#endif
}
diff --git a/src/src/exim.c b/src/src/exim.c
index b6d926b50..a3d1b9e60 100644
--- a/src/src/exim.c
+++ b/src/src/exim.c
@@ -140,14 +140,14 @@ regex_match_and_setup(const pcre *re, const uschar *subject, int options, int se
int ovector[3*(EXPAND_MAXN+1)];
uschar * s = string_copy(subject); /* de-constifying */
int n = pcre_exec(re, NULL, CS s, Ustrlen(s), 0,
- PCRE_EOPT | options, ovector, sizeof(ovector)/sizeof(int));
+ PCRE_EOPT | options, ovector, nelem(ovector));
BOOL yield = n >= 0;
if (n == 0) n = EXPAND_MAXN + 1;
if (yield)
{
int nn;
- expand_nmax = (setup < 0)? 0 : setup + 1;
- for (nn = (setup < 0)? 0 : 2; nn < n*2; nn += 2)
+ expand_nmax = setup < 0 ? 0 : setup + 1;
+ for (nn = setup < 0 ? 0 : 2; nn < n*2; nn += 2)
{
expand_nstring[expand_nmax] = s + ovector[nn];
expand_nlength[expand_nmax++] = ovector[nn+1] - ovector[nn];
@@ -906,6 +906,9 @@ fprintf(fp, "Support for:");
#ifdef EXPERIMENTAL_REQUIRETLS
fprintf(fp, " Experimental_REQUIRETLS");
#endif
+#ifdef EXPERIMENTAL_PIPE_CONNECT
+ fprintf(fp, " Experimental_PIPE_CONNECT");
+#endif
fprintf(fp, "\n");
fprintf(fp, "Lookups (built-in):");
diff --git a/src/src/functions.h b/src/src/functions.h
index 38df27aea..fbf2cbe5b 100644
--- a/src/src/functions.h
+++ b/src/src/functions.h
@@ -435,8 +435,7 @@ extern void smtp_command_sigterm_exit(void);
extern void smtp_data_timeout_exit(void);
extern void smtp_data_sigint_exit(void);
extern uschar *smtp_cmd_hist(void);
-extern int smtp_connect(host_item *, int, uschar *, int,
- transport_instance *);
+extern int smtp_connect(smtp_connect_args *, const blob *);
extern int smtp_sock_connect(host_item *, int, int, uschar *,
transport_instance * tb, int, const blob *);
extern int smtp_feof(void);
diff --git a/src/src/globals.c b/src/src/globals.c
index 8dd8191ef..731d25c0a 100644
--- a/src/src/globals.c
+++ b/src/src/globals.c
@@ -325,6 +325,11 @@ struct global_flags f =
.sender_name_forced = FALSE,
.sender_set_untrusted = FALSE,
.smtp_authenticated = FALSE,
+#ifdef EXPERIMENTAL_PIPE_CONNECT
+ .smtp_in_early_pipe_advertised = FALSE,
+ .smtp_in_early_pipe_no_auth = FALSE,
+ .smtp_in_early_pipe_used = FALSE,
+#endif
.smtp_in_pipelining_advertised = FALSE,
.smtp_in_pipelining_used = FALSE,
.spool_file_wireformat = FALSE,
@@ -336,6 +341,7 @@ struct global_flags f =
.tcp_fastopen_ok = FALSE,
.tcp_in_fastopen = FALSE,
+ .tcp_in_fastopen_data = FALSE,
.tcp_in_fastopen_logged = FALSE,
.tcp_out_fastopen_logged= FALSE,
.timestamps_utc = FALSE,
@@ -1180,6 +1186,9 @@ uschar *override_pid_file_path = NULL;
uschar *percent_hack_domains = NULL;
uschar *pid_file_path = US PID_FILE_PATH
"\0<--------------Space to patch pid_file_path->";
+#ifdef EXPERIMENTAL_PIPE_CONNECT
+uschar *pipe_connect_advertise_hosts = US"*";
+#endif
uschar *pipelining_advertise_hosts = US"*";
uschar *primary_hostname = NULL;
uschar process_info[PROCESS_INFO_SIZE];
@@ -1267,6 +1276,9 @@ const pcre *regex_From = NULL;
const pcre *regex_IGNOREQUOTA = NULL;
const pcre *regex_PIPELINING = NULL;
const pcre *regex_SIZE = NULL;
+#ifdef EXPERIMENTAL_PIPE_CONNECT
+const pcre *regex_EARLY_PIPE = NULL;
+#endif
const pcre *regex_ismsgid = NULL;
const pcre *regex_smtp_code = NULL;
uschar *regex_vars[REGEX_VARS];
diff --git a/src/src/globals.h b/src/src/globals.h
index 465266e1a..c90783e3c 100644
--- a/src/src/globals.h
+++ b/src/src/globals.h
@@ -251,6 +251,11 @@ extern struct global_flags {
BOOL sender_name_forced :1; /* Set by -F */
BOOL sender_set_untrusted :1; /* Sender set by untrusted caller */
BOOL smtp_authenticated :1; /* Sending client has authenticated */
+#ifdef EXPERIMENTAL_PIPE_CONNECT
+ BOOL smtp_in_early_pipe_advertised :1; /* server advertised PIPE_CONNECT */
+ BOOL smtp_in_early_pipe_no_auth :1; /* too many authenticator names */
+ BOOL smtp_in_early_pipe_used :1; /* client did send early data */
+#endif
BOOL smtp_in_pipelining_advertised :1; /* server advertised PIPELINING */
BOOL smtp_in_pipelining_used :1; /* server noted client using PIPELINING */
BOOL spool_file_wireformat :1; /* current -D file has CRLF rather than NL */
@@ -262,6 +267,7 @@ extern struct global_flags {
BOOL tcp_fastopen_ok :1; /* appears to be supported by kernel */
BOOL tcp_in_fastopen :1; /* conn usefully used fastopen */
+ BOOL tcp_in_fastopen_data :1; /* fastopen carried data */
BOOL tcp_in_fastopen_logged :1; /* one-time logging */
BOOL tcp_out_fastopen_logged :1; /* one-time logging */
BOOL timestamps_utc :1; /* Use UTC for all times */
@@ -734,6 +740,9 @@ extern uschar *override_pid_file_path; /* Value of -oP argument */
extern uschar *percent_hack_domains; /* Local domains for which '% operates */
extern uschar *pid_file_path; /* For writing daemon pids */
+#ifdef EXPERIMENTAL_PIPE_CONNECT
+extern uschar *pipe_connect_advertise_hosts; /* for banner/EHLO pipelining */
+#endif
extern uschar *pipelining_advertise_hosts; /* As it says */
#ifndef DISABLE_PRDR
extern BOOL prdr_enable; /* As it says */
@@ -813,6 +822,9 @@ extern const pcre *regex_CHUNKING; /* For recognizing CHUNKING (RFC 3030) */
extern const pcre *regex_IGNOREQUOTA; /* For recognizing IGNOREQUOTA (LMTP) */
extern const pcre *regex_PIPELINING; /* For recognizing PIPELINING */
extern const pcre *regex_SIZE; /* For recognizing SIZE settings */
+#ifdef EXPERIMENTAL_PIPE_CONNECT
+extern const pcre *regex_EARLY_PIPE; /* For recognizing PIPE_CONNCT */
+#endif
extern const pcre *regex_ismsgid; /* Compiled r.e. for message it */
extern const pcre *regex_smtp_code; /* For recognizing SMTP codes */
extern uschar *regex_vars[]; /* $regexN variables */
@@ -989,7 +1001,7 @@ extern BOOL system_filter_uid_set; /* TRUE if uid set */
extern blob tcp_fastopen_nodata; /* for zero-data TFO connect requests */
extern BOOL tcp_nodelay; /* Controls TCP_NODELAY on daemon */
-extern tfo_state_t tcp_out_fastopen; /* 0: no 1: conn used 2: useful */
+extern tfo_state_t tcp_out_fastopen; /* TCP fast open */
#ifdef USE_TCP_WRAPPERS
extern uschar *tcp_wrappers_daemon_name; /* tcpwrappers daemon lookup name */
#endif
diff --git a/src/src/ip.c b/src/src/ip.c
index d601b46b3..22f459db9 100644
--- a/src/src/ip.c
+++ b/src/src/ip.c
@@ -264,10 +264,10 @@ if (fastopen_blob && f.tcp_fastopen_ok)
/* seen for with-data, proper TFO opt, with-cookie case */
{
DEBUG(D_transport|D_v)
- debug_printf("non-TFO mode connection attempt to %s, %lu data\n",
+ debug_printf("TFO mode connection attempt to %s, %lu data\n",
address, (unsigned long)fastopen_blob->len);
/*XXX also seen on successful TFO, sigh */
- tcp_out_fastopen = fastopen_blob->len > 0 ? TFO_USED : TFO_ATTEMPTED;
+ tcp_out_fastopen = fastopen_blob->len > 0 ? TFO_ATTEMPTED_DATA : TFO_ATTEMPTED_NODATA;
}
else if (errno == EINPROGRESS) /* expected if we had no cookie for peer */
/* seen for no-data, proper TFO option, both cookie-request and with-cookie cases */
@@ -280,7 +280,7 @@ if (fastopen_blob && f.tcp_fastopen_ok)
fastopen_blob->len > 0 ? "with" : "no");
if (!fastopen_blob->data)
{
- tcp_out_fastopen = TFO_ATTEMPTED; /* we tried; unknown if useful yet */
+ tcp_out_fastopen = TFO_ATTEMPTED_NODATA; /* we tried; unknown if useful yet */
rc = 0;
}
else
diff --git a/src/src/macro_predef.c b/src/src/macro_predef.c
index 9f5700ebf..fbc923ce6 100644
--- a/src/src/macro_predef.c
+++ b/src/src/macro_predef.c
@@ -201,6 +201,9 @@ due to conflicts with other common macros. */
#ifdef EXPERIMENTAL_REQUIRETLS
builtin_macro_create(US"_HAVE_REQTLS");
#endif
+#ifdef EXPERIMENTAL_PIPE_CONNECT
+ builtin_macro_create(US"_HAVE_PIPE_CONNECT");
+#endif
#ifdef LOOKUP_LSEARCH
builtin_macro_create(US"_HAVE_LOOKUP_LSEARCH");
diff --git a/src/src/macros.h b/src/src/macros.h
index 1eff348d9..28d3d318e 100644
--- a/src/src/macros.h
+++ b/src/src/macros.h
@@ -1014,15 +1014,16 @@ enum { FILTER_UNSET, FILTER_FORWARD, FILTER_EXIM, FILTER_SIEVE };
/* Codes for ESMTP facilities offered by peer */
-#define OPTION_TLS BIT(0)
-#define OPTION_IGNQ BIT(1)
-#define OPTION_PRDR BIT(2)
-#define OPTION_UTF8 BIT(3)
-#define OPTION_DSN BIT(4)
-#define OPTION_PIPE BIT(5)
-#define OPTION_SIZE BIT(6)
-#define OPTION_CHUNKING BIT(7)
-#define OPTION_REQUIRETLS BIT(8)
+#define OPTION_TLS BIT(0)
+#define OPTION_IGNQ BIT(1)
+#define OPTION_PRDR BIT(2)
+#define OPTION_UTF8 BIT(3)
+#define OPTION_DSN BIT(4)
+#define OPTION_PIPE BIT(5)
+#define OPTION_SIZE BIT(6)
+#define OPTION_CHUNKING BIT(7)
+#define OPTION_REQUIRETLS BIT(8)
+#define OPTION_EARLY_PIPE BIT(9)
/* Codes for tls_requiretls requests (usually by sender) */
@@ -1063,4 +1064,10 @@ should not be one active. */
? (sigalarm_setter = NULL, alarm(seconds)) : alarm(seconds);
#endif
+#define AUTHS_REGEX US"\\n250[\\s\\-]AUTH\\s+([\\-\\w \\t]+)(?:\\n|$)"
+
+#define EARLY_PIPE_FEATURE_NAME "X_PIPE_CONNECT"
+#define EARLY_PIPE_FEATURE_LEN 14
+
+
/* End of macros.h */
diff --git a/src/src/queue.c b/src/src/queue.c
index ac1bb7604..92109ef92 100644
--- a/src/src/queue.c
+++ b/src/src/queue.c
@@ -1135,7 +1135,7 @@ switch(action)
deliver_in_buffer = store_malloc(DELIVER_IN_BUFFER_SIZE);
deliver_out_buffer = store_malloc(DELIVER_OUT_BUFFER_SIZE);
tctx.u.fd = 1;
- transport_write_message(&tctx, 0);
+ (void) transport_write_message(&tctx, 0);
break;
}
diff --git a/src/src/readconf.c b/src/src/readconf.c
index 9b3eef367..f21ce4d04 100644
--- a/src/src/readconf.c
+++ b/src/src/readconf.c
@@ -240,6 +240,10 @@ static optionlist optionlist_config[] = {
#endif
{ "pid_file_path", opt_stringptr, &pid_file_path },
{ "pipelining_advertise_hosts", opt_stringptr, &pipelining_advertise_hosts },
+#ifdef EXPERIMENTAL_PIPE_CONNECT
+ { "pipelining_connect_advertise_hosts", opt_stringptr,
+ &pipe_connect_advertise_hosts },
+#endif
#ifndef DISABLE_PRDR
{ "prdr_enable", opt_bool, &prdr_enable },
#endif
@@ -4208,6 +4212,9 @@ static void
auths_init(void)
{
auth_instance *au, *bu;
+#ifdef EXPERIMENTAL_PIPE_CONNECT
+int nauths = 0;
+#endif
readconf_driver_init(US"authenticator",
(driver_instance **)(&auths), /* chain anchor */
@@ -4231,7 +4238,13 @@ for (au = auths; au; au = au->next)
"(%s and %s) have the same public name (%s)",
au->client ? US"client" : US"server", au->name, bu->name,
au->public_name);
+#ifdef EXPERIMENTAL_PIPE_CONNECT
+ nauths++;
+#endif
}
+#ifdef EXPERIMENTAL_PIPE_CONNECT
+f.smtp_in_early_pipe_no_auth = nauths > 16;
+#endif
}
diff --git a/src/src/receive.c b/src/src/receive.c
index ab27cc373..a5646811f 100644
--- a/src/src/receive.c
+++ b/src/src/receive.c
@@ -1314,7 +1314,7 @@ if (sender_fullhost)
}
if (f.tcp_in_fastopen && !f.tcp_in_fastopen_logged)
{
- g = string_catn(g, US" TFO", 4);
+ g = string_catn(g, US" TFO*", f.tcp_in_fastopen_data ? 5 : 4);
f.tcp_in_fastopen_logged = TRUE;
}
if (sender_ident)
@@ -1322,7 +1322,17 @@ if (sender_ident)
if (received_protocol)
g = string_append(g, 2, US" P=", received_protocol);
if (LOGGING(pipelining) && f.smtp_in_pipelining_advertised)
- g = string_catn(g, US" L-", f.smtp_in_pipelining_used ? 2 : 3);
+ {
+ g = string_catn(g, US" L", 2);
+#ifdef EXPERIMENTAL_PIPE_CONNECT
+ if (f.smtp_in_early_pipe_used)
+ g = string_catn(g, US"*", 1);
+ else if (f.smtp_in_early_pipe_advertised)
+ g = string_catn(g, US".", 1);
+#endif
+ if (!f.smtp_in_pipelining_used)
+ g = string_catn(g, US"-", 1);
+ }
return g;
}
diff --git a/src/src/smtp_in.c b/src/src/smtp_in.c
index 2a83392ac..a579b8faf 100644
--- a/src/src/smtp_in.c
+++ b/src/src/smtp_in.c
@@ -145,6 +145,9 @@ static struct {
BOOL helo_verify :1;
BOOL helo_seen :1;
BOOL helo_accept_junk :1;
+#ifdef EXPERIMENTAL_PIPE_CONNECT
+ BOOL pipe_connect_acceptable :1;
+#endif
BOOL rcpt_smtp_response_same :1;
BOOL rcpt_in_progress :1;
BOOL smtp_exit_function_called :1;
@@ -404,6 +407,18 @@ return TRUE;
}
+#ifdef EXPERIMENTAL_PIPE_CONNECT
+static BOOL
+pipeline_connect_sends(void)
+{
+if (!sender_host_address || f.sender_host_notsocket || !fl.pipe_connect_acceptable)
+ return FALSE;
+
+if (wouldblock_reading()) return FALSE;
+f.smtp_in_early_pipe_used = TRUE;
+return TRUE;
+}
+#endif
/*************************************************
* Log incomplete transactions *
@@ -1842,7 +1857,7 @@ for (i = 0; i < smtp_ch_index; i++)
if (!(s = string_from_gstring(g))) s = US"";
log_write(0, LOG_MAIN, "no MAIL in %sSMTP connection from %s D=%s%s",
- f.tcp_in_fastopen ? US"TFO " : US"",
+ f.tcp_in_fastopen ? f.tcp_in_fastopen_data ? US"TFO* " : US"TFO " : US"",
host_and_ident(FALSE), string_timesince(&smtp_connection_start), s);
}
@@ -2392,13 +2407,17 @@ tfo_in_check(void)
struct tcp_info tinfo;
socklen_t len = sizeof(tinfo);
-if ( getsockopt(fileno(smtp_out), IPPROTO_TCP, TCP_INFO, &tinfo, &len) == 0
- && tinfo.tcpi_state == TCP_SYN_RECV
- )
- {
- DEBUG(D_receive) debug_printf("TCP_FASTOPEN mode connection (state TCP_SYN_RECV)\n");
- f.tcp_in_fastopen = TRUE;
- }
+if (getsockopt(fileno(smtp_out), IPPROTO_TCP, TCP_INFO, &tinfo, &len) == 0)
+ if (tinfo.tcpi_options & TCPI_OPT_SYN_DATA)
+ {
+ DEBUG(D_receive) debug_printf("TCP_FASTOPEN mode connection (ACKd data-on-SYN)\n");
+ f.tcp_in_fastopen_data = f.tcp_in_fastopen = TRUE;
+ }
+ else if (tinfo.tcpi_state == TCP_SYN_RECV)
+ {
+ DEBUG(D_receive) debug_printf("TCP_FASTOPEN mode connection (state TCP_SYN_RECV)\n");
+ f.tcp_in_fastopen = TRUE;
+ }
# endif
}
#endif
@@ -2987,22 +3006,39 @@ while (*p);
/* Before we write the banner, check that there is no input pending, unless
this synchronisation check is disabled. */
+#ifdef EXPERIMENTAL_PIPE_CONNECT
+fl.pipe_connect_acceptable =
+ sender_host_address && verify_check_host(&pipe_connect_advertise_hosts) == OK;
+
if (!check_sync())
- {
- unsigned n = smtp_inend - smtp_inptr;
- if (n > 32) n = 32;
-
- log_write(0, LOG_MAIN|LOG_REJECT, "SMTP protocol "
- "synchronization error (input sent without waiting for greeting): "
- "rejected connection from %s input=\"%s\"", host_and_ident(TRUE),
- string_printing(string_copyn(smtp_inptr, n)));
- smtp_printf("554 SMTP synchronization error\r\n", FALSE);
- return FALSE;
- }
+ if (fl.pipe_connect_acceptable)
+ f.smtp_in_early_pipe_used = TRUE;
+ else
+#else
+if (!check_sync())
+#endif
+ {
+ unsigned n = smtp_inend - smtp_inptr;
+ if (n > 32) n = 32;
+
+ log_write(0, LOG_MAIN|LOG_REJECT, "SMTP protocol "
+ "synchronization error (input sent without waiting for greeting): "
+ "rejected connection from %s input=\"%s\"", host_and_ident(TRUE),
+ string_printing(string_copyn(smtp_inptr, n)));
+ smtp_printf("554 SMTP synchronization error\r\n", FALSE);
+ return FALSE;
+ }
/* Now output the banner */
+/*XXX the ehlo-resp code does its own tls/nontls bit. Maybe subroutine that? */
-smtp_printf("%s", FALSE, string_from_gstring(ss));
+smtp_printf("%s",
+#ifdef EXPERIMENTAL_PIPE_CONNECT
+ fl.pipe_connect_acceptable && pipeline_connect_sends(),
+#else
+ FALSE,
+#endif
+ string_from_gstring(ss));
/* Attempt to see if we sent the banner before the last ACK of the 3-way
handshake arrived. If so we must have managed a TFO. */
@@ -3953,7 +3989,13 @@ while (done <= 0)
US &off, sizeof(off));
#endif
- switch(smtp_read_command(TRUE, GETC_BUFFER_UNLIMITED))
+ switch(smtp_read_command(
+#ifdef EXPERIMENTAL_PIPE_CONNECT
+ !fl.pipe_connect_acceptable,
+#else
+ TRUE,
+#endif
+ GETC_BUFFER_UNLIMITED))
{
/* The AUTH command is not permitted to occur inside a transaction, and may
occur successfully only once per connection. Actually, that isn't quite
@@ -4186,7 +4228,12 @@ while (done <= 0)
host_build_sender_fullhost(); /* Rebuild */
break;
}
- else if (!check_sync()) goto SYNC_FAILURE;
+#ifdef EXPERIMENTAL_PIPE_CONNECT
+ else if (!fl.pipe_connect_acceptable && !check_sync())
+#else
+ else if (!check_sync())
+#endif
+ goto SYNC_FAILURE;
/* Generate an OK reply. The default string includes the ident if present,
and also the IP address if present. Reflecting back the ident is intended
@@ -4315,13 +4362,22 @@ while (done <= 0)
/* Exim is quite happy with pipelining, so let the other end know that
it is safe to use it, unless advertising is disabled. */
- if (f.pipelining_enable &&
- verify_check_host(&pipelining_advertise_hosts) == OK)
+ if ( f.pipelining_enable
+ && verify_check_host(&pipelining_advertise_hosts) == OK)
{
g = string_catn(g, smtp_code, 3);
g = string_catn(g, US"-PIPELINING\r\n", 13);
sync_cmd_limit = NON_SYNC_CMD_PIPELINING;
f.smtp_in_pipelining_advertised = TRUE;
+
+#ifdef EXPERIMENTAL_PIPE_CONNECT
+ if (fl.pipe_connect_acceptable)
+ {
+ f.smtp_in_early_pipe_advertised = TRUE;
+ g = string_catn(g, smtp_code, 3);
+ g = string_catn(g, US"-" EARLY_PIPE_FEATURE_NAME "\r\n", EARLY_PIPE_FEATURE_LEN+3);
+ }
+#endif
}
@@ -4442,7 +4498,14 @@ while (done <= 0)
has been seen. */
#ifdef SUPPORT_TLS
- if (tls_in.active.sock >= 0) (void)tls_write(NULL, g->s, g->ptr, FALSE); else
+ if (tls_in.active.sock >= 0)
+ (void)tls_write(NULL, g->s, g->ptr,
+# ifdef EXPERIMENTAL_PIPE_CONNECT
+ fl.pipe_connect_acceptable && pipeline_connect_sends());
+# else
+ FALSE);
+# endif
+ else
#endif
{
@@ -5247,7 +5310,10 @@ while (done <= 0)
HAD(SCH_DATA);
f.dot_ends = TRUE;
- DATA_BDAT: /* Common code for DATA and BDAT */
+ DATA_BDAT: /* Common code for DATA and BDAT */
+#ifdef EXPERIMENTAL_PIPE_CONNECT
+ fl.pipe_connect_acceptable = FALSE;
+#endif
if (!discarded && recipients_count <= 0)
{
if (fl.rcpt_smtp_response_same && rcpt_smtp_response != NULL)
@@ -5619,7 +5685,11 @@ while (done <= 0)
log_write(L_lost_incoming_connection, LOG_MAIN,
"unexpected %s while reading SMTP command from %s%s%s D=%s",
f.sender_host_unknown ? "EOF" : "disconnection",
- f.tcp_in_fastopen && !f.tcp_in_fastopen_logged ? US"TFO " : US"",
+ f.tcp_in_fastopen_logged
+ ? US""
+ : f.tcp_in_fastopen
+ ? f.tcp_in_fastopen_data ? US"TFO* " : US"TFO "
+ : US"",
host_and_ident(FALSE), smtp_read_error,
string_timesince(&smtp_connection_start)
);
diff --git a/src/src/smtp_out.c b/src/src/smtp_out.c
index c5eafbc57..1209c7fd9 100644
--- a/src/src/smtp_out.c
+++ b/src/src/smtp_out.c
@@ -148,46 +148,50 @@ tfo_out_check(int sock)
struct tcp_info tinfo;
socklen_t len = sizeof(tinfo);
-if (getsockopt(sock, IPPROTO_TCP, TCP_INFO, &tinfo, &len) == 0)
- switch (tcp_out_fastopen)
- {
- /* This is a somewhat dubious detection method; totally undocumented so likely
- to fail in future kernels. There seems to be no documented way. What we really
- want to know is if the server sent smtp-banner data before our ACK of his SYN,ACK
- hit him. What this (possibly?) detects is whether we sent a TFO cookie with our
- SYN, as distinct from a TFO request. This gets a false-positive when the server
- key is rotated; we send the old one (which this test sees) but the server returns
- the new one and does not send its SMTP banner before we ACK his SYN,ACK.
- To force that rotation case:
- '# echo -n "00000000-00000000-00000000-0000000" >/proc/sys/net/ipv4/tcp_fastopen_key'
- The kernel seems to be counting unack'd packets. */
-
- case TFO_ATTEMPTED:
- if (tinfo.tcpi_unacked > 1)
- {
- DEBUG(D_transport|D_v)
- debug_printf("TCP_FASTOPEN tcpi_unacked %d\n", tinfo.tcpi_unacked);
- tcp_out_fastopen = TFO_USED;
- }
- break;
-
-# ifdef notdef /* This seems to always fire, meaning that we cannot tell
- whether the server accepted data we sent. For now assume
- that it did. */
+switch (tcp_out_fastopen)
+ {
+ /* This is a somewhat dubious detection method; totally undocumented so likely
+ to fail in future kernels. There seems to be no documented way. What we really
+ want to know is if the server sent smtp-banner data before our ACK of his SYN,ACK
+ hit him. What this (possibly?) detects is whether we sent a TFO cookie with our
+ SYN, as distinct from a TFO request. This gets a false-positive when the server
+ key is rotated; we send the old one (which this test sees) but the server returns
+ the new one and does not send its SMTP banner before we ACK his SYN,ACK.
+ To force that rotation case:
+ '# echo -n "00000000-00000000-00000000-0000000" >/proc/sys/net/ipv4/tcp_fastopen_key'
+ The kernel seems to be counting unack'd packets. */
+
+ case TFO_ATTEMPTED_NODATA:
+ if ( getsockopt(sock, IPPROTO_TCP, TCP_INFO, &tinfo, &len) == 0
+ && tinfo.tcpi_state == TCP_SYN_SENT
+ && tinfo.tcpi_unacked > 1
+ )
+ {
+ DEBUG(D_transport|D_v)
+ debug_printf("TCP_FASTOPEN tcpi_unacked %d\n", tinfo.tcpi_unacked);
+ tcp_out_fastopen = TFO_USED_NODATA;
+ }
+ break;
- /* If there was data-on-SYN but we had to retrasnmit it, declare no TFO */
+ /* When called after waiting for received data we should be able
+ to tell if data we sent was accepted. */
- case TFO_USED:
- if (!(tinfo.tcpi_options & TCPI_OPT_SYN_DATA))
+ case TFO_ATTEMPTED_DATA:
+ if ( getsockopt(sock, IPPROTO_TCP, TCP_INFO, &tinfo, &len) == 0
+ && tinfo.tcpi_state == TCP_ESTABLISHED
+ )
+ if (tinfo.tcpi_options & TCPI_OPT_SYN_DATA)
+ {
+ DEBUG(D_transport|D_v) debug_printf("TFO: data was acked\n");
+ tcp_out_fastopen = TFO_USED_DATA;
+ }
+ else
{
DEBUG(D_transport|D_v) debug_printf("TFO: had to retransmit\n");
tcp_out_fastopen = TFO_NOT_USED;
}
- break;
-
- default: break; /* compiler quietening */
-# endif
- }
+ break;
+ }
# endif
}
#endif
@@ -259,7 +263,8 @@ if (interface && ip_bind(sock, host_af, interface, 0) < 0)
/* Connect to the remote host, and add keepalive to the socket before returning
it, if requested. If the build supports TFO, request it - and if the caller
-requested some early-data then include that in the TFO request. */
+requested some early-data then include that in the TFO request. If there is
+early-data but no TFO support, send it after connecting. */
else
{
@@ -271,8 +276,16 @@ else
if (ip_connect(sock, host_af, host->address, port, timeout, fastopen_blob) < 0)
save_errno = errno;
else if (early_data && !fastopen_blob && early_data->data && early_data->len)
+ {
+ HDEBUG(D_transport|D_acl|D_v)
+ debug_printf("sending %ld nonTFO early-data\n", (long)early_data->len);
+
+#ifdef TCP_QUICKACK
+ (void) setsockopt(sock, IPPROTO_TCP, TCP_QUICKACK, US &off, sizeof(off));
+#endif
if (send(sock, early_data->data, early_data->len, 0) < 0)
save_errno = errno;
+ }
}
/* Either bind() or connect() failed */
@@ -291,12 +304,13 @@ if (save_errno != 0)
return -1;
}
-/* Both bind() and connect() succeeded */
+/* Both bind() and connect() succeeded, and any early-data */
else
{
union sockaddr_46 interface_sock;
EXIM_SOCKLEN_T size = sizeof(interface_sock);
+
HDEBUG(D_transport|D_acl|D_v) debug_printf_indent("connected\n");
if (getsockname(sock, (struct sockaddr *)(&interface_sock), &size) == 0)
sending_ip_address = host_ntoa(-1, &interface_sock, NULL, &sending_port);
@@ -307,9 +321,10 @@ else
close(sock);
return -1;
}
+
if (ob->keepalive) ip_keepalive(sock, host->address, TRUE);
#ifdef TCP_FASTOPEN
- if (fastopen_blob) tfo_out_check(sock);
+ tfo_out_check(sock);
#endif
return sock;
}
@@ -343,45 +358,62 @@ non-IPv6 systems, to enable the code to be less messy. However, on such systems
host->address will always be an IPv4 address.
Arguments:
- host host item containing name and address and port
- host_af AF_INET or AF_INET6
- interface outgoing interface address or NULL
- timeout timeout value or 0
- tb transport
+ sc details for making connection: host, af, interface, transport
+ early_data if non-NULL, data to be sent - preferably in the TCP SYN segment
Returns: connected socket number, or -1 with errno set
*/
int
-smtp_connect(host_item *host, int host_af, uschar *interface,
- int timeout, transport_instance * tb)
+smtp_connect(smtp_connect_args * sc, const blob * early_data)
{
-int port = host->port;
-#ifdef SUPPORT_SOCKS
-smtp_transport_options_block * ob =
- (smtp_transport_options_block *)tb->options_block;
-#endif
+int port = sc->host->port;
+smtp_transport_options_block * ob = sc->ob;
-callout_address = string_sprintf("[%s]:%d", host->address, port);
+callout_address = string_sprintf("[%s]:%d", sc->host->address, port);
HDEBUG(D_transport|D_acl|D_v)
{
uschar * s = US" ";
- if (interface) s = string_sprintf(" from %s ", interface);
+ if (sc->interface) s = string_sprintf(" from %s ", sc->interface);
#ifdef SUPPORT_SOCKS
if (ob->socks_proxy) s = string_sprintf("%svia proxy ", s);
#endif
- debug_printf_indent("Connecting to %s %s%s... ", host->name, callout_address, s);
+ debug_printf_indent("Connecting to %s %s%s... ", sc->host->name, callout_address, s);
}
/* Create and connect the socket */
#ifdef SUPPORT_SOCKS
if (ob->socks_proxy)
- return socks_sock_connect(host, host_af, port, interface, tb, timeout);
+ {
+ int sock = socks_sock_connect(sc->host, sc->host_af, port, sc->interface,
+ sc->tblock, ob->connect_timeout);
+
+ if (sock >= 0)
+ {
+ if (early_data && early_data->data && early_data->len)
+ if (send(sock, early_data->data, early_data->len, 0) < 0)
+ {
+ int save_errno = errno;
+ HDEBUG(D_transport|D_acl|D_v)
+ {
+ debug_printf_indent("failed: %s", CUstrerror(save_errno));
+ if (save_errno == ETIMEDOUT)
+ debug_printf(" (timeout=%s)", readconf_printtime(ob->connect_timeout));
+ debug_printf("\n");
+ }
+ (void)close(sock);
+ sock = -1;
+ errno = save_errno;
+ }
+ }
+ return sock;
+ }
#endif
-return smtp_sock_connect(host, host_af, port, interface, tb, timeout, NULL);
+return smtp_sock_connect(sc->host, sc->host_af, port, sc->interface,
+ sc->tblock, ob->connect_timeout, early_data);
}
@@ -415,13 +447,33 @@ if (outblock->cctx->tls_ctx)
rc = tls_write(outblock->cctx->tls_ctx, outblock->buffer, n, more);
else
#endif
- rc = send(outblock->cctx->sock, outblock->buffer, n,
+
+ {
+ if (outblock->conn_args)
+ {
+ blob early_data = { .data = outblock->buffer, .len = n };
+
+ /* We ignore the more-flag if we're doing a connect with early-data, which
+ means we won't get BDAT+data. A pity, but wise due to the idempotency
+ requirement: TFO with data can, in rare cases, replay the data to the
+ receiver. */
+
+ if ( (outblock->cctx->sock = smtp_connect(outblock->conn_args, &early_data))
+ < 0)
+ return FALSE;
+ outblock->conn_args = NULL;
+ rc = n;
+ }
+ else
+
+ rc = send(outblock->cctx->sock, outblock->buffer, n,
#ifdef MSG_MORE
- more ? MSG_MORE : 0
+ more ? MSG_MORE : 0
#else
- 0
+ 0
#endif
- );
+ );
+ }
if (rc <= 0)
{
@@ -633,27 +685,37 @@ Arguments:
Returns: TRUE if a valid, non-error response was received; else FALSE
*/
+/*XXX could move to smtp transport; no other users */
BOOL
-smtp_read_response(void * sx, uschar *buffer, int size, int okdigit,
+smtp_read_response(void * sx0, uschar *buffer, int size, int okdigit,
int timeout)
{
-smtp_inblock * inblock = &((smtp_context *)sx)->inblock;
+smtp_context * sx = sx0;
uschar *ptr = buffer;
-int count;
+int count = 0;
errno = 0; /* Ensure errno starts out zero */
+#ifdef EXPERIMENTAL_PIPE_CONNECT
+if (sx->pending_BANNER || sx->pending_EHLO)
+ if (smtp_reap_early_pipe(sx, &count) != OK)
+ {
+ DEBUG(D_transport) debug_printf("failed reaping pipelined cmd responsess\n");
+ return FALSE;
+ }
+#endif
+
/* This is a loop to read and concatenate the lines that make up a multi-line
response. */
for (;;)
{
- if ((count = read_response_line(inblock, ptr, size, timeout)) < 0)
+ if ((count = read_response_line(&sx->inblock, ptr, size, timeout)) < 0)
return FALSE;
HDEBUG(D_transport|D_acl|D_v)
- debug_printf_indent(" %s %s\n", (ptr == buffer)? "SMTP<<" : " ", ptr);
+ debug_printf_indent(" %s %s\n", ptr == buffer ? "SMTP<<" : " ", ptr);
/* Check the format of the response: it must start with three digits; if
these are followed by a space or end of line, the response is complete. If
@@ -687,6 +749,10 @@ for (;;)
size -= count + 1;
}
+#ifdef TCP_FASTOPEN
+ tfo_out_check(sx->cctx.sock);
+#endif
+
/* Return a value that depends on the SMTP return code. On some systems a
non-zero value of errno has been seen at this point, so ensure it is zero,
because the caller of this function looks at errno when FALSE is returned, to
diff --git a/src/src/structs.h b/src/src/structs.h
index 48f475a89..20db0e5f4 100644
--- a/src/src/structs.h
+++ b/src/src/structs.h
@@ -66,8 +66,10 @@ typedef enum { CHUNKING_NOT_OFFERED = -1,
CHUNKING_LAST} chunking_state_t;
typedef enum { TFO_NOT_USED = 0,
- TFO_ATTEMPTED,
- TFO_USED } tfo_state_t;
+ TFO_ATTEMPTED_NODATA,
+ TFO_ATTEMPTED_DATA,
+ TFO_USED_NODATA,
+ TFO_USED_DATA } tfo_state_t;
/* Structure for holding information about a host for use mainly by routers,
but also used when checking lists of hosts and when transporting. Looking up
@@ -618,7 +620,11 @@ typedef struct address_item {
BOOL af_bad_reply:1; /* filter could not generate autoreply */
BOOL af_tcp_fastopen_conn:1; /* delivery connection used TCP Fast Open */
BOOL af_tcp_fastopen:1; /* delivery usefully used TCP Fast Open */
+ BOOL af_tcp_fastopen_data:1; /* delivery sent SMTP commands on TCP Fast Open */
BOOL af_pipelining:1; /* delivery used (traditional) pipelining */
+#ifdef EXPERIMENTAL_PIPE_CONNECT
+ BOOL af_early_pipe:1; /* delivery used connect-time pipelining */
+#endif
#ifndef DISABLE_PRDR
BOOL af_prdr_used:1; /* delivery used SMTP PRDR */
#endif
@@ -787,6 +793,15 @@ typedef struct sha1 {
unsigned int length;
} sha1;
+/* Information for making an smtp connection */
+typedef struct {
+ transport_instance * tblock;
+ void * ob; /* smtp_transport_options_block * */
+ host_item * host;
+ int host_af;
+ uschar * interface;
+} smtp_connect_args;
+
/* A client-initiated connection. If TLS, the second element is non-NULL */
typedef struct {
int sock;
@@ -817,6 +832,8 @@ typedef struct smtp_outblock {
BOOL authenticating; /* TRUE when authenticating */
uschar *ptr; /* current position in the buffer */
uschar *buffer; /* the buffer itself */
+
+ smtp_connect_args * conn_args; /* to make connection, if not yet made */
} smtp_outblock;
/* Structure to hold information about the source of redirection information */
diff --git a/src/src/tls-openssl.c b/src/src/tls-openssl.c
index 982a5ba7e..f0351451c 100644
--- a/src/src/tls-openssl.c
+++ b/src/src/tls-openssl.c
@@ -2909,10 +2909,22 @@ DEBUG(D_tls) debug_printf("%s(%p, %lu%s)\n", __FUNCTION__,
"more" is notified. This hack is only ok if small amounts are involved AND only
one stream does it, in one context (i.e. no store reset). Currently it is used
for the responses to the received SMTP MAIL , RCPT, DATA sequence, only. */
+/*XXX + if PIPE_COMMAND, banner & ehlo-resp for smmtp-on-connect. Suspect there's
+a store reset there. */
if (!ct_ctx && (more || corked))
{
+#ifdef EXPERIMENTAL_PIPE_CONNECT
+ int save_pool = store_pool;
+ store_pool = POOL_PERM;
+#endif
+
corked = string_catn(corked, buff, len);
+
+#ifdef EXPERIMENTAL_PIPE_CONNECT
+ store_pool = save_pool;
+#endif
+
if (more)
return len;
buff = CUS corked->s;
diff --git a/src/src/transports/smtp.c b/src/src/transports/smtp.c
index fba5a7ce8..576b7c546 100644
--- a/src/src/transports/smtp.c
+++ b/src/src/transports/smtp.c
@@ -106,6 +106,10 @@ optionlist smtp_transport_options[] = {
#endif
{ "hosts_override", opt_bool,
(void *)offsetof(smtp_transport_options_block, hosts_override) },
+#ifdef EXPERIMENTAL_PIPE_CONNECT
+ { "hosts_pipe_connect", opt_stringptr,
+ (void *)offsetof(smtp_transport_options_block, hosts_pipe_connect) },
+#endif
{ "hosts_randomize", opt_bool,
(void *)offsetof(smtp_transport_options_block, hosts_randomize) },
#if defined(SUPPORT_TLS) && !defined(DISABLE_OCSP)
@@ -248,6 +252,9 @@ smtp_transport_options_block smtp_transport_option_defaults = {
.hosts_avoid_tls = NULL,
.hosts_verify_avoid_tls = NULL,
.hosts_avoid_pipelining = NULL,
+#ifdef EXPERIMENTAL_PIPE_CONNECT
+ .hosts_pipe_connect = NULL,
+#endif
.hosts_avoid_esmtp = NULL,
#ifdef SUPPORT_TLS
.hosts_nopass_tls = NULL,
@@ -336,6 +343,9 @@ static BOOL update_waiting; /* TRUE to update the "wait" database */
static BOOL pipelining_active; /* current transaction is in pipe mode */
+static unsigned ehlo_response(uschar * buf, unsigned checks);
+
+
/*************************************************
* Setup entry point *
*************************************************/
@@ -362,8 +372,7 @@ static int
smtp_transport_setup(transport_instance *tblock, address_item *addrlist,
transport_feedback *tf, uid_t uid, gid_t gid, uschar **errmsg)
{
-smtp_transport_options_block *ob =
- (smtp_transport_options_block *)(tblock->options_block);
+smtp_transport_options_block *ob = SOB tblock->options_block;
errmsg = errmsg; /* Keep picky compilers happy */
uid = uid;
@@ -413,8 +422,7 @@ Returns: nothing
void
smtp_transport_init(transport_instance *tblock)
{
-smtp_transport_options_block *ob =
- (smtp_transport_options_block *)(tblock->options_block);
+smtp_transport_options_block *ob = SOB tblock->options_block;
/* Retry_use_local_part defaults FALSE if unset */
@@ -423,9 +431,11 @@ if (tblock->retry_use_local_part == TRUE_UNSET)
/* Set the default port according to the protocol */
-if (ob->port == NULL)
- ob->port = (strcmpic(ob->protocol, US"lmtp") == 0)? US"lmtp" :
- (strcmpic(ob->protocol, US"smtps") == 0)? US"smtps" : US"smtp";
+if (!ob->port)
+ ob->port = strcmpic(ob->protocol, US"lmtp") == 0
+ ? US"lmtp"
+ : strcmpic(ob->protocol, US"smtps") == 0
+ ? US"smtps" : US"smtp";
/* Set up the setup entry point, to be called before subprocesses for this
transport. */
@@ -658,8 +668,6 @@ Returns: nothing
static void
write_logs(const host_item *host, const uschar *suffix, int basic_errno)
{
-
-
uschar *message = LOGGING(outgoing_port)
? string_sprintf("H=%s [%s]:%d", host->name, host->address,
host->port == PORT_NONE ? 25 : host->port)
@@ -741,6 +749,267 @@ router_name = transport_name = NULL;
#endif
/*************************************************
+* Reap SMTP specific responses *
+*************************************************/
+static int
+smtp_discard_responses(smtp_context * sx, smtp_transport_options_block * ob,
+ int count)
+{
+uschar flushbuffer[4096];
+
+while (count-- > 0)
+ {
+ if (!smtp_read_response(sx, flushbuffer, sizeof(flushbuffer),
+ '2', ob->command_timeout)
+ && (errno != 0 || flushbuffer[0] == 0))
+ break;
+ }
+return count;
+}
+
+
+/* Return boolean success */
+
+static BOOL
+smtp_reap_banner(smtp_context * sx)
+{
+BOOL 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);
+#endif
+return good_response;
+}
+
+static BOOL
+smtp_reap_ehlo(smtp_context * sx)
+{
+if (!smtp_read_response(sx, sx->buffer, sizeof(sx->buffer), '2',
+ (SOB sx->conn_args.ob)->command_timeout))
+ {
+ if (errno != 0 || sx->buffer[0] == 0 || sx->lmtp)
+ {
+#ifdef EXPERIMENTAL_DSN_INFO
+ sx->helo_response = string_copy(sx->buffer);
+#endif
+ return FALSE;
+ }
+ sx->esmtp = FALSE;
+ }
+#ifdef EXPERIMENTAL_DSN_INFO
+sx->helo_response = string_copy(sx->buffer);
+#endif
+return TRUE;
+}
+
+
+
+#ifdef EXPERIMENTAL_PIPE_CONNECT
+static uschar *
+ehlo_cache_key(const smtp_context * sx)
+{
+host_item * host = sx->conn_args.host;
+return Ustrchr(host->address, ':')
+ ? string_sprintf("[%s]:%d.EHLO", host->address,
+ host->port == PORT_NONE ? sx->port : host->port)
+ : string_sprintf("%s:%d.EHLO", host->address,
+ host->port == PORT_NONE ? sx->port : host->port);
+}
+
+static void
+write_ehlo_cache_entry(const smtp_context * sx)
+{
+open_db dbblock, * dbm_file;
+
+if ((dbm_file = dbfn_open(US"misc", O_RDWR, &dbblock, TRUE)))
+ {
+ uschar * ehlo_resp_key = ehlo_cache_key(sx);
+ dbdata_ehlo_resp er = { .data = sx->ehlo_resp };
+
+ dbfn_write(dbm_file, ehlo_resp_key, &er, (int)sizeof(er));
+ dbfn_close(dbm_file);
+ }
+}
+
+static void
+invalidate_ehlo_cache_entry(smtp_context * sx)
+{
+open_db dbblock, * dbm_file;
+
+if ( sx->early_pipe_active
+ && (dbm_file = dbfn_open(US"misc", O_RDWR, &dbblock, TRUE)))
+ {
+ uschar * ehlo_resp_key = ehlo_cache_key(sx);
+ dbfn_delete(dbm_file, ehlo_resp_key);
+ dbfn_close(dbm_file);
+ }
+}
+
+static BOOL
+read_ehlo_cache_entry(smtp_context * sx)
+{
+open_db dbblock;
+open_db * dbm_file;
+
+if (!(dbm_file = dbfn_open(US"misc", O_RDONLY, &dbblock, FALSE)))
+ { DEBUG(D_transport) debug_printf("ehlo-cache: no misc DB\n"); }
+else
+ {
+ uschar * ehlo_resp_key = ehlo_cache_key(sx);
+ dbdata_ehlo_resp * er;
+
+ if (!(er = dbfn_read(dbm_file, ehlo_resp_key)))
+ { DEBUG(D_transport) debug_printf("no ehlo-resp record\n"); }
+ else if (time(NULL) - er->time_stamp > retry_data_expire)
+ {
+ DEBUG(D_transport) debug_printf("ehlo-resp record too old\n");
+ dbfn_close(dbm_file);
+ if ((dbm_file = dbfn_open(US"misc", O_RDWR, &dbblock, TRUE)))
+ dbfn_delete(dbm_file, ehlo_resp_key);
+ }
+ else
+ {
+ sx->ehlo_resp = er->data;
+ dbfn_close(dbm_file);
+ DEBUG(D_transport) debug_printf(
+ "EHLO response bits from cache: cleartext 0x%04x crypted 0x%04x\n",
+ er->data.cleartext_features, er->data.crypted_features);
+ return TRUE;
+ }
+ dbfn_close(dbm_file);
+ }
+return FALSE;
+}
+
+
+
+/* Return an auths bitmap for the set of AUTH methods offered by the server
+which match our authenticators. */
+
+static unsigned short
+study_ehlo_auths(smtp_context * sx)
+{
+uschar * names;
+auth_instance * au;
+uschar authnum;
+unsigned short authbits = 0;
+
+if (!sx->esmtp) return 0;
+if (!regex_AUTH) regex_AUTH = regex_must_compile(AUTHS_REGEX, FALSE, TRUE);
+if (!regex_match_and_setup(regex_AUTH, sx->buffer, 0, -1)) return 0;
+expand_nmax = -1; /* reset */
+names = string_copyn(expand_nstring[1], expand_nlength[1]);
+
+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)
+ { authbits |= BIT(authnum); break; }
+ }
+
+DEBUG(D_transport)
+ debug_printf("server offers %s AUTH, methods '%s', bitmap 0x%04x\n",
+ tls_out.active.sock >= 0 ? "crypted" : "plaintext", names, authbits);
+
+if (tls_out.active.sock >= 0)
+ sx->ehlo_resp.crypted_auths = authbits;
+else
+ sx->ehlo_resp.cleartext_auths = authbits;
+return authbits;
+}
+
+
+
+
+/* Wait for and check responses for early-pipelining.
+
+Called from the lower-level smtp_read_response() function
+used for general code that assume synchronisation, if context
+flags indicate outstanding early-pipelining commands. Also
+called fom sync_responses() which handles pipelined commands.
+
+Arguments:
+ sx smtp connection context
+ countp number of outstanding responses, adjusted on return
+
+Return:
+ OK all well
+ FAIL SMTP error in response
+*/
+int
+smtp_reap_early_pipe(smtp_context * sx, int * countp)
+{
+BOOL pending_BANNER = sx->pending_BANNER;
+BOOL pending_EHLO = sx->pending_EHLO;
+
+sx->pending_BANNER = FALSE; /* clear early to avoid recursion */
+sx->pending_EHLO = FALSE;
+
+if (pending_BANNER)
+ {
+ DEBUG(D_transport) debug_printf("%s expect banner\n", __FUNCTION__);
+ (*countp)--;
+ if (!smtp_reap_banner(sx))
+ {
+ DEBUG(D_transport) debug_printf("bad banner\n");
+ goto fail;
+ }
+ }
+
+if (pending_EHLO)
+ {
+ unsigned peer_offered;
+ unsigned short authbits = 0, * ap;
+
+ DEBUG(D_transport) debug_printf("%s expect ehlo\n", __FUNCTION__);
+ (*countp)--;
+ if (!smtp_reap_ehlo(sx))
+ {
+ DEBUG(D_transport) debug_printf("bad response for EHLO\n");
+ goto fail;
+ }
+
+ /* Compare the actual EHLO response to the cached value we assumed;
+ on difference, dump or rewrite the cache and arrange for a retry. */
+
+ ap = tls_out.active.sock < 0
+ ? &sx->ehlo_resp.cleartext_auths : &sx->ehlo_resp.crypted_auths;
+
+ peer_offered = ehlo_response(sx->buffer,
+ (tls_out.active.sock < 0 ? OPTION_TLS : OPTION_REQUIRETLS)
+ | OPTION_CHUNKING | OPTION_PRDR | OPTION_DSN | OPTION_PIPE | OPTION_SIZE
+ | OPTION_UTF8 | OPTION_EARLY_PIPE
+ );
+ if ( peer_offered != sx->peer_offered
+ || (authbits = study_ehlo_auths(sx)) != *ap)
+ {
+ HDEBUG(D_transport)
+ debug_printf("EHLO extensions changed, 0x%04x/0x%04x -> 0x%04x/0x%04x\n",
+ sx->peer_offered, *ap, peer_offered, authbits);
+ *ap = authbits;
+ if (peer_offered & OPTION_EARLY_PIPE)
+ write_ehlo_cache_entry(sx);
+ else
+ invalidate_ehlo_cache_entry(sx);
+
+ return OK; /* just carry on */
+ }
+ }
+return OK;
+
+fail:
+ invalidate_ehlo_cache_entry(sx);
+ (void) smtp_discard_responses(sx, sx->conn_args.ob, *countp);
+ return FAIL;
+}
+#endif
+
+
+/*************************************************
* Synchronize SMTP responses *
*************************************************/
@@ -779,16 +1048,21 @@ Returns: 3 if at least one address had 2xx and one had 5xx
-1 timeout while reading RCPT response
-2 I/O or other non-response error for RCPT
-3 DATA or MAIL failed - errno and buffer set
+ -4 banner or EHLO failed (early-pipelining)
*/
static int
sync_responses(smtp_context * sx, int count, int pending_DATA)
{
-address_item *addr = sx->sync_addr;
-smtp_transport_options_block *ob =
- (smtp_transport_options_block *)sx->tblock->options_block;
+address_item * addr = sx->sync_addr;
+smtp_transport_options_block * ob = sx->conn_args.ob;
int yield = 0;
+#ifdef EXPERIMENTAL_PIPE_CONNECT
+if (smtp_reap_early_pipe(sx, &count) != OK)
+ return -4;
+#endif
+
/* 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. */
@@ -804,20 +1078,13 @@ if (sx->pending_MAIL)
Ustrcpy(big_buffer, mail_command); /* Fits, because it came from there! */
if (errno == 0 && sx->buffer[0] != 0)
{
- uschar flushbuffer[4096];
int save_errno = 0;
if (sx->buffer[0] == '4')
{
save_errno = ERRNO_MAIL4XX;
addr->more_errno |= ((sx->buffer[1] - '0')*10 + sx->buffer[2] - '0') << 8;
}
- while (count-- > 0)
- {
- if (!smtp_read_response(sx, flushbuffer, sizeof(flushbuffer),
- '2', ob->command_timeout)
- && (errno != 0 || flushbuffer[0] == 0))
- break;
- }
+ count = smtp_discard_responses(sx, ob, count);
errno = save_errno;
}
@@ -825,7 +1092,7 @@ if (sx->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 = sx->host;
+ addr->host_used = sx->conn_args.host;
addr = addr->next;
}
return -3;
@@ -845,7 +1112,7 @@ while (count-- > 0)
return -2;
/* The address was accepted */
- addr->host_used = sx->host;
+ addr->host_used = sx->conn_args.host;
DEBUG(D_transport) debug_printf("%s expect rcpt\n", __FUNCTION__);
if (smtp_read_response(sx, sx->buffer, sizeof(sx->buffer),
@@ -872,7 +1139,7 @@ while (count-- > 0)
else if (errno == ETIMEDOUT)
{
uschar *message = string_sprintf("SMTP timeout after RCPT TO:<%s>",
- transport_rcpt_address(addr, sx->tblock->rcpt_include_affixes));
+ transport_rcpt_address(addr, sx->conn_args.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;
@@ -887,7 +1154,7 @@ while (count-- > 0)
else if (errno != 0 || sx->buffer[0] == 0)
{
string_format(big_buffer, big_buffer_size, "RCPT TO:<%s>",
- transport_rcpt_address(addr, sx->tblock->rcpt_include_affixes));
+ transport_rcpt_address(addr, sx->conn_args.tblock->rcpt_include_affixes));
return -2;
}
@@ -897,11 +1164,11 @@ while (count-- > 0)
{
addr->message =
string_sprintf("SMTP error from remote mail server after RCPT TO:<%s>: "
- "%s", transport_rcpt_address(addr, sx->tblock->rcpt_include_affixes),
+ "%s", transport_rcpt_address(addr, sx->conn_args.tblock->rcpt_include_affixes),
string_printing(sx->buffer));
setflag(addr, af_pass_message);
if (!sx->verify)
- msglog_line(sx->host, addr->message);
+ msglog_line(sx->conn_args.host, addr->message);
/* The response was 5xx */
@@ -929,9 +1196,9 @@ while (count-- > 0)
/* Log temporary errors if there are more hosts to be tried.
If not, log this last one in the == line. */
- if (sx->host->next)
+ if (sx->conn_args.host->next)
log_write(0, LOG_MAIN, "H=%s [%s]: %s",
- sx->host->name, sx->host->address, addr->message);
+ sx->conn_args.host->name, sx->conn_args.host->address, addr->message);
#ifndef DISABLE_EVENT
else
@@ -983,7 +1250,7 @@ if (pending_DATA != 0)
}
return -3;
}
- (void)check_response(sx->host, &errno, 0, sx->buffer, &code, &msg, &pass_message);
+ (void)check_response(sx->conn_args.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);
}
@@ -998,11 +1265,82 @@ return yield;
-/* Do the client side of smtp-level authentication */
-/*
+
+
+/* Try an authenticator's client entry */
+
+static int
+try_authenticator(smtp_context * sx, auth_instance * au)
+{
+smtp_transport_options_block * ob = sx->conn_args.ob; /* transport options */
+host_item * host = sx->conn_args.host; /* host to deliver to */
+int rc;
+
+sx->outblock.authenticating = TRUE;
+rc = (au->info->clientcode)(au, sx, ob->command_timeout,
+ sx->buffer, sizeof(sx->buffer));
+sx->outblock.authenticating = FALSE;
+DEBUG(D_transport) debug_printf("%s authenticator yielded %d\n", au->name, rc);
+
+/* A temporary authentication failure must hold up delivery to
+this host. After a permanent authentication failure, we carry on
+to try other authentication methods. If all fail hard, try to
+deliver the message unauthenticated unless require_auth was set. */
+
+switch(rc)
+ {
+ case OK:
+ f.smtp_authenticated = TRUE; /* stops the outer loop */
+ client_authenticator = au->name;
+ if (au->set_client_id)
+ client_authenticated_id = expand_string(au->set_client_id);
+ break;
+
+ /* Failure after writing a command */
+
+ case FAIL_SEND:
+ return FAIL_SEND;
+
+ /* Failure after reading a response */
+
+ case FAIL:
+ if (errno != 0 || sx->buffer[0] != '5') return FAIL;
+ log_write(0, LOG_MAIN, "%s authenticator failed H=%s [%s] %s",
+ au->name, host->name, host->address, sx->buffer);
+ break;
+
+ /* Failure by some other means. In effect, the authenticator
+ decided it wasn't prepared to handle this case. Typically this
+ is the result of "fail" in an expansion string. Do we need to
+ log anything here? Feb 2006: a message is now put in the buffer
+ if logging is required. */
+
+ case CANCELLED:
+ if (*sx->buffer != 0)
+ log_write(0, LOG_MAIN, "%s authenticator cancelled "
+ "authentication H=%s [%s] %s", au->name, host->name,
+ host->address, sx->buffer);
+ break;
+
+ /* Internal problem, message in buffer. */
+
+ case ERROR:
+ set_errno_nohost(sx->addrlist, ERRNO_AUTHPROB, string_copy(sx->buffer),
+ DEFER, FALSE);
+ return ERROR;
+ }
+return OK;
+}
+
+
+
+
+/* Do the client side of smtp-level authentication.
+
Arguments:
- sx smtp connection
- buffer EHLO response from server (gets overwritten)
+ sx smtp connection context
+
+sx->buffer should have the EHLO response from server (gets overwritten)
Returns:
OK Success, or failed (but not required): global "smtp_authenticated" set
@@ -1014,35 +1352,90 @@ Returns:
*/
static int
-smtp_auth(smtp_context * sx, uschar * buffer, unsigned bufsize)
+smtp_auth(smtp_context * sx)
{
-smtp_transport_options_block * ob = sx->ob;
-int require_auth;
-uschar *fail_reason = US"server did not advertise AUTH support";
+host_item * host = sx->conn_args.host; /* host to deliver to */
+smtp_transport_options_block * ob = sx->conn_args.ob; /* transport options */
+int require_auth = verify_check_given_host(CUSS &ob->hosts_require_auth, host);
+#ifdef EXPERIMENTAL_PIPE_CONNECT
+unsigned short authbits = tls_out.active.sock >= 0
+ ? sx->ehlo_resp.crypted_auths : sx->ehlo_resp.cleartext_auths;
+#endif
+uschar * fail_reason = US"server did not advertise AUTH support";
f.smtp_authenticated = FALSE;
client_authenticator = client_authenticated_id = client_authenticated_sender = NULL;
-require_auth = verify_check_given_host(CUSS &ob->hosts_require_auth, sx->host);
-if (sx->esmtp && !regex_AUTH) regex_AUTH =
- regex_must_compile(US"\\n250[\\s\\-]AUTH\\s+([\\-\\w\\s]+)(?:\\n|$)",
- FALSE, TRUE);
+if (!regex_AUTH)
+ regex_AUTH = regex_must_compile(AUTHS_REGEX, FALSE, TRUE);
+
+/* Is the server offering AUTH? */
-if (sx->esmtp && regex_match_and_setup(regex_AUTH, buffer, 0, -1))
+if ( sx->esmtp
+ &&
+#ifdef EXPERIMENTAL_PIPE_CONNECT
+ sx->early_pipe_active ? authbits
+ :
+#endif
+ regex_match_and_setup(regex_AUTH, sx->buffer, 0, -1)
+ )
{
- uschar *names = string_copyn(expand_nstring[1], expand_nlength[1]);
+ uschar * names = NULL;
expand_nmax = -1; /* reset */
+#ifdef EXPERIMENTAL_PIPE_CONNECT
+ if (!sx->early_pipe_active)
+#endif
+ names = string_copyn(expand_nstring[1], expand_nlength[1]);
+
/* Must not do this check until after we have saved the result of the
- regex match above. */
+ regex match above as the check could be another RE. */
- if (require_auth == OK ||
- verify_check_given_host(CUSS &ob->hosts_try_auth, sx->host) == OK)
+ if ( require_auth == OK
+ || verify_check_given_host(CUSS &ob->hosts_try_auth, host) == OK)
{
- auth_instance *au;
- fail_reason = US"no common mechanisms were found";
+ auth_instance * au;
DEBUG(D_transport) debug_printf("scanning authentication mechanisms\n");
+ fail_reason = US"no common mechanisms were found";
+
+#ifdef EXPERIMENTAL_PIPE_CONNECT
+ if (sx->early_pipe_active)
+ {
+ /* Scan our authenticators (which support use by a client and were offered
+ by the server (checked at cache-write time)), not suppressed by
+ client_condition. If one is found, attempt to authenticate by calling its
+ client function. We are limited to supporting up to 16 authenticator
+ public-names by the number of bits in a short. */
+
+ uschar bitnum;
+ int rc;
+
+ for (bitnum = 0, au = auths;
+ !f.smtp_authenticated && au && bitnum < 16;
+ bitnum++, au = au->next) if (authbits & BIT(bitnum))
+ {
+ if ( au->client_condition
+ && !expand_check_condition(au->client_condition, au->name,
+ US"client authenticator"))
+ {
+ DEBUG(D_transport) debug_printf("skipping %s authenticator: %s\n",
+ au->name, "client_condition is false");
+ continue;
+ }
+
+ /* Found data for a listed mechanism. Call its client entry. Set
+ a flag in the outblock so that data is overwritten after sending so
+ that reflections don't show it. */
+
+ fail_reason = US"authentication attempt(s) failed";
+
+ if ((rc = try_authenticator(sx, au)) != OK)
+ return rc;
+ }
+ }
+ else
+#endif
/* Scan the configured authenticators looking for one which is configured
for use as a client, which is not suppressed by client_condition, and
@@ -1053,10 +1446,11 @@ if (sx->esmtp && regex_match_and_setup(regex_AUTH, buffer, 0, -1))
for (au = auths; !f.smtp_authenticated && au; au = au->next)
{
uschar *p = names;
- if (!au->client ||
- (au->client_condition != NULL &&
- !expand_check_condition(au->client_condition, au->name,
- US"client authenticator")))
+
+ if ( !au->client
+ || ( au->client_condition
+ && !expand_check_condition(au->client_condition, au->name,
+ US"client authenticator")))
{
DEBUG(D_transport) debug_printf("skipping %s authenticator: %s\n",
au->name,
@@ -1069,8 +1463,9 @@ if (sx->esmtp && regex_match_and_setup(regex_AUTH, buffer, 0, -1))
while (*p)
{
- int rc;
int len = Ustrlen(au->public_name);
+ int rc;
+
while (isspace(*p)) p++;
if (strncmpic(au->public_name, p, len) != 0 ||
@@ -1085,60 +1480,9 @@ if (sx->esmtp && regex_match_and_setup(regex_AUTH, buffer, 0, -1))
that reflections don't show it. */
fail_reason = US"authentication attempt(s) failed";
- sx->outblock.authenticating = TRUE;
- rc = (au->info->clientcode)(au, sx,
- ob->command_timeout, buffer, bufsize);
- sx->outblock.authenticating = FALSE;
- DEBUG(D_transport) debug_printf("%s authenticator yielded %d\n",
- au->name, rc);
-
- /* A temporary authentication failure must hold up delivery to
- this host. After a permanent authentication failure, we carry on
- to try other authentication methods. If all fail hard, try to
- deliver the message unauthenticated unless require_auth was set. */
-
- switch(rc)
- {
- case OK:
- f.smtp_authenticated = TRUE; /* stops the outer loop */
- client_authenticator = au->name;
- if (au->set_client_id != NULL)
- client_authenticated_id = expand_string(au->set_client_id);
- break;
- /* Failure after writing a command */
-
- case FAIL_SEND:
- return FAIL_SEND;
-
- /* Failure after reading a response */
-
- case FAIL:
- if (errno != 0 || buffer[0] != '5') return FAIL;
- log_write(0, LOG_MAIN, "%s authenticator failed H=%s [%s] %s",
- au->name, sx->host->name, sx->host->address, buffer);
- break;
-
- /* Failure by some other means. In effect, the authenticator
- decided it wasn't prepared to handle this case. Typically this
- is the result of "fail" in an expansion string. Do we need to
- log anything here? Feb 2006: a message is now put in the buffer
- if logging is required. */
-
- case CANCELLED:
- if (*buffer != 0)
- log_write(0, LOG_MAIN, "%s authenticator cancelled "
- "authentication H=%s [%s] %s", au->name, sx->host->name,
- sx->host->address, buffer);
- break;
-
- /* Internal problem, message in buffer. */
-
- case ERROR:
- set_errno_nohost(sx->addrlist, ERRNO_AUTHPROB, string_copy(buffer),
- DEFER, FALSE);
- return ERROR;
- }
+ if ((rc = try_authenticator(sx, au)) != OK)
+ return rc;
break; /* If not authenticated, try next authenticator */
} /* Loop for scanning supported server mechanisms */
@@ -1310,8 +1654,7 @@ uschar * tlsc1 = US"";
#endif
uschar * save_sender_address = sender_address;
uschar * local_identity = NULL;
-smtp_transport_options_block * ob =
- (smtp_transport_options_block *)tblock->options_block;
+smtp_transport_options_block * ob = SOB tblock->options_block;
sender_address = sender;
@@ -1371,17 +1714,19 @@ ehlo_response(uschar * buf, unsigned checks)
{
size_t bsize = Ustrlen(buf);
-#ifdef SUPPORT_TLS
-if ( checks & OPTION_TLS
- && pcre_exec(regex_STARTTLS, NULL, CS buf, bsize, 0, PCRE_EOPT, NULL, 0) < 0)
- checks &= ~OPTION_TLS;
+/* debug_printf("%s: check for 0x%04x\n", __FUNCTION__, checks); */
+#ifdef SUPPORT_TLS
# ifdef EXPERIMENTAL_REQUIRETLS
if ( checks & OPTION_REQUIRETLS
&& pcre_exec(regex_REQUIRETLS, NULL, CS buf,bsize, 0, PCRE_EOPT, NULL,0) < 0)
- checks &= ~OPTION_REQUIRETLS;
# endif
+ checks &= ~OPTION_REQUIRETLS;
+
+if ( checks & OPTION_TLS
+ && pcre_exec(regex_STARTTLS, NULL, CS buf, bsize, 0, PCRE_EOPT, NULL, 0) < 0)
#endif
+ checks &= ~OPTION_TLS;
if ( checks & OPTION_IGNQ
&& pcre_exec(regex_IGNOREQUOTA, NULL, CS buf, bsize, 0,
@@ -1395,14 +1740,14 @@ if ( checks & OPTION_CHUNKING
#ifndef DISABLE_PRDR
if ( checks & OPTION_PRDR
&& pcre_exec(regex_PRDR, NULL, CS buf, bsize, 0, PCRE_EOPT, NULL, 0) < 0)
- checks &= ~OPTION_PRDR;
#endif
+ checks &= ~OPTION_PRDR;
#ifdef SUPPORT_I18N
if ( checks & OPTION_UTF8
&& pcre_exec(regex_UTF8, NULL, CS buf, bsize, 0, PCRE_EOPT, NULL, 0) < 0)
- checks &= ~OPTION_UTF8;
#endif
+ checks &= ~OPTION_UTF8;
if ( checks & OPTION_DSN
&& pcre_exec(regex_DSN, NULL, CS buf, bsize, 0, PCRE_EOPT, NULL, 0) < 0)
@@ -1417,6 +1762,14 @@ if ( checks & OPTION_SIZE
&& pcre_exec(regex_SIZE, NULL, CS buf, bsize, 0, PCRE_EOPT, NULL, 0) < 0)
checks &= ~OPTION_SIZE;
+#ifdef EXPERIMENTAL_PIPE_CONNECT
+if ( checks & OPTION_EARLY_PIPE
+ && pcre_exec(regex_EARLY_PIPE, NULL, CS buf, bsize, 0,
+ PCRE_EOPT, NULL, 0) < 0)
+#endif
+ checks &= ~OPTION_EARLY_PIPE;
+
+/* debug_printf("%s: found 0x%04x\n", __FUNCTION__, checks); */
return checks;
}
@@ -1444,8 +1797,7 @@ static int
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_transport_options_block * ob = SOB tctx->tblock->options_block;
smtp_context * sx = tctx->smtp_context;
int cmd_count = 0;
int prev_cmd_count;
@@ -1455,12 +1807,23 @@ there may be more writes (like, the chunk data) done soon. */
if (chunk_size > 0)
{
+#ifdef EXPERIMENTAL_PIPE_CONNECT
+ BOOL new_conn = !!(sx->outblock.conn_args);
+#endif
if((cmd_count = smtp_write_command(sx,
flags & tc_reap_prev ? SCMD_FLUSH : SCMD_MORE,
"BDAT %u%s\r\n", chunk_size, flags & tc_chunk_last ? " LAST" : "")
) < 0) return ERROR;
if (flags & tc_chunk_last)
data_command = string_copy(big_buffer); /* Save for later error message */
+#ifdef EXPERIMENTAL_PIPE_CONNECT
+ /* That command write could have been the one that made the connection.
+ Copy the fd from the client conn ctx (smtp transport specific) to the
+ generic transport ctx. */
+
+ if (new_conn)
+ tctx->u.fd = sx->outblock.cctx->sock;
+#endif
}
prev_cmd_count = cmd_count += sx->cmd_count;
@@ -1485,6 +1848,9 @@ if (flags & tc_reap_prev && prev_cmd_count > 0)
case 0: break; /* No 2xx or 5xx, but no probs */
case -1: /* Timeout on RCPT */
+#ifdef EXPERIMENTAL_PIPE_CONNECT
+ case -4: /* non-2xx for pipelined banner or EHLO */
+#endif
default: return ERROR; /* I/O error, or any MAIL/DATA error */
}
cmd_count = 1;
@@ -1523,6 +1889,8 @@ return OK;
+
+
/*************************************************
* Make connection for given message *
*************************************************/
@@ -1548,15 +1916,16 @@ smtp_setup_conn(smtp_context * sx, BOOL suppress_tls)
#if defined(SUPPORT_TLS) && defined(SUPPORT_DANE)
dns_answer tlsa_dnsa;
#endif
+smtp_transport_options_block * ob = sx->conn_args.tblock->options_block;
BOOL pass_message = FALSE;
uschar * message = NULL;
int yield = OK;
int rc;
-sx->ob = (smtp_transport_options_block *) sx->tblock->options_block;
+sx->conn_args.ob = ob;
-sx->lmtp = strcmpic(sx->ob->protocol, US"lmtp") == 0;
-sx->smtps = strcmpic(sx->ob->protocol, US"smtps") == 0;
+sx->lmtp = strcmpic(ob->protocol, US"lmtp") == 0;
+sx->smtps = strcmpic(ob->protocol, US"smtps") == 0;
sx->ok = FALSE;
sx->send_rset = TRUE;
sx->send_quit = TRUE;
@@ -1570,14 +1939,19 @@ sx->dsn_all_lasthop = TRUE;
#if defined(SUPPORT_TLS) && defined(SUPPORT_DANE)
sx->dane = FALSE;
sx->dane_required =
- verify_check_given_host(CUSS &sx->ob->hosts_require_dane, sx->host) == OK;
+ verify_check_given_host(CUSS &ob->hosts_require_dane, sx->conn_args.host) == OK;
+#endif
+#ifdef EXPERIMENTAL_PIPE_CONNECT
+sx->early_pipe_active = sx->early_pipe_ok = FALSE;
+sx->ehlo_resp.cleartext_features = sx->ehlo_resp.crypted_features = 0;
+sx->pending_BANNER = sx->pending_EHLO = FALSE;
#endif
-if ((sx->max_rcpt = sx->tblock->max_addresses) == 0) sx->max_rcpt = 999999;
+if ((sx->max_rcpt = sx->conn_args.tblock->max_addresses) == 0) sx->max_rcpt = 999999;
sx->peer_offered = 0;
sx->avoid_option = 0;
sx->igquotstr = US"";
-if (!sx->helo_data) sx->helo_data = sx->ob->helo_data;
+if (!sx->helo_data) sx->helo_data = ob->helo_data;
#ifdef EXPERIMENTAL_DSN_INFO
sx->smtp_greeting = NULL;
sx->helo_response = NULL;
@@ -1600,6 +1974,7 @@ sx->outblock.buffersize = sizeof(sx->outbuffer);
sx->outblock.ptr = sx->outbuffer;
sx->outblock.cmd_count = 0;
sx->outblock.authenticating = FALSE;
+sx->outblock.conn_args = NULL;
/* Reset the parameters of a TLS session. */
@@ -1636,11 +2011,11 @@ specially so they can be identified for retries. */
if (!continue_hostname)
{
if (sx->verify)
- HDEBUG(D_verify) debug_printf("interface=%s port=%d\n", sx->interface, sx->port);
+ HDEBUG(D_verify) debug_printf("interface=%s port=%d\n", sx->conn_args.interface, sx->port);
- /* Get the actual port the connection will use, into sx->host */
+ /* Get the actual port the connection will use, into sx->conn_args.host */
- smtp_port_for_connect(sx->host, sx->port);
+ smtp_port_for_connect(sx->conn_args.host, sx->port);
#if defined(SUPPORT_TLS) && defined(SUPPORT_DANE)
/* Do TLSA lookup for DANE */
@@ -1648,15 +2023,15 @@ if (!continue_hostname)
tls_out.dane_verified = FALSE;
tls_out.tlsa_usage = 0;
- if (sx->host->dnssec == DS_YES)
+ if (sx->conn_args.host->dnssec == DS_YES)
{
if( sx->dane_required
- || verify_check_given_host(CUSS &sx->ob->hosts_try_dane, sx->host) == OK
+ || verify_check_given_host(CUSS &ob->hosts_try_dane, sx->conn_args.host) == OK
)
- switch (rc = tlsa_lookup(sx->host, &tlsa_dnsa, sx->dane_required))
+ switch (rc = tlsa_lookup(sx->conn_args.host, &tlsa_dnsa, sx->dane_required))
{
case OK: sx->dane = TRUE;
- sx->ob->tls_tempfail_tryclear = FALSE;
+ ob->tls_tempfail_tryclear = FALSE;
break;
case FAIL_FORCED: break;
default: set_errno_nohost(sx->addrlist, ERRNO_DNSDEFER,
@@ -1664,7 +2039,7 @@ if (!continue_hostname)
rc == DEFER ? "DEFER" : "FAIL"),
rc, FALSE);
# ifndef DISABLE_EVENT
- (void) event_raise(sx->tblock->event_action,
+ (void) event_raise(sx->conn_args.tblock->event_action,
US"dane:fail", sx->dane_required
? US"dane-required" : US"dnssec-invalid");
# endif
@@ -1674,10 +2049,10 @@ if (!continue_hostname)
else if (sx->dane_required)
{
set_errno_nohost(sx->addrlist, ERRNO_DNSDEFER,
- string_sprintf("DANE error: %s lookup not DNSSEC", sx->host->name),
+ string_sprintf("DANE error: %s lookup not DNSSEC", sx->conn_args.host->name),
FAIL, FALSE);
# ifndef DISABLE_EVENT
- (void) event_raise(sx->tblock->event_action,
+ (void) event_raise(sx->conn_args.tblock->event_action,
US"dane:fail", US"dane-required");
# endif
return FAIL;
@@ -1687,32 +2062,50 @@ if (!continue_hostname)
/* Make the TCP connection */
- sx->cctx.sock =
- smtp_connect(sx->host, sx->host_af, sx->interface,
- sx->ob->connect_timeout, sx->tblock);
sx->cctx.tls_ctx = NULL;
sx->inblock.cctx = sx->outblock.cctx = &sx->cctx;
+ sx->avoid_option = sx->peer_offered = smtp_peer_options = 0;
- if (sx->cctx.sock < 0)
+#ifdef EXPERIMENTAL_PIPE_CONNECT
+ if (verify_check_given_host(CUSS &ob->hosts_pipe_connect, sx->conn_args.host) == OK)
{
- uschar * msg = NULL;
- if (sx->verify)
+ sx->early_pipe_ok = TRUE;
+ if ( read_ehlo_cache_entry(sx)
+ && sx->ehlo_resp.cleartext_features & OPTION_EARLY_PIPE)
{
- msg = US strerror(errno);
- HDEBUG(D_verify) debug_printf("connect: %s\n", msg);
+ DEBUG(D_transport) debug_printf("Using cached cleartext PIPE_CONNECT\n");
+ sx->early_pipe_active = TRUE;
+ sx->peer_offered = sx->ehlo_resp.cleartext_features;
}
- set_errno_nohost(sx->addrlist,
- errno == ETIMEDOUT ? ERRNO_CONNECTTIMEOUT : errno,
- sx->verify ? string_sprintf("could not connect: %s", msg)
- : NULL,
- DEFER, FALSE);
- sx->send_quit = FALSE;
- return DEFER;
}
+ 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)
+ {
+ uschar * msg = NULL;
+ if (sx->verify)
+ {
+ msg = US strerror(errno);
+ HDEBUG(D_verify) debug_printf("connect: %s\n", msg);
+ }
+ set_errno_nohost(sx->addrlist,
+ errno == ETIMEDOUT ? ERRNO_CONNECTTIMEOUT : errno,
+ sx->verify ? string_sprintf("could not connect: %s", msg)
+ : NULL,
+ DEFER, FALSE);
+ sx->send_quit = FALSE;
+ return DEFER;
+ }
+ }
/* Expand the greeting message while waiting for the initial response. (Makes
sense if helo_data contains ${lookup dnsdb ...} stuff). The expansion is
delayed till here so that $sending_interface and $sending_port are set. */
+/*XXX early-pipe: they still will not be. Is there any way to find out what they
+will be? Somehow I doubt it. */
if (sx->helo_data)
if (!(sx->helo_data = expand_string(sx->helo_data)))
@@ -1743,24 +2136,29 @@ if (!continue_hostname)
if (!sx->smtps)
{
- BOOL good_response;
-
-#ifdef TCP_QUICKACK
- (void) setsockopt(sx->cctx.sock, IPPROTO_TCP, TCP_QUICKACK, US &off, sizeof(off));
+#ifdef EXPERIMENTAL_PIPE_CONNECT
+ if (sx->early_pipe_active)
+ {
+ sx->pending_BANNER = TRUE; /* sync_responses() must eventually handle */
+ sx->outblock.cmd_count = 1;
+ }
+ else
#endif
- good_response = smtp_read_response(sx, sx->buffer, sizeof(sx->buffer),
- '2', sx->ob->command_timeout);
-#ifdef EXPERIMENTAL_DSN_INFO
- sx->smtp_greeting = string_copy(sx->buffer);
+ {
+#ifdef TCP_QUICKACK
+ (void) setsockopt(sx->cctx.sock, IPPROTO_TCP, TCP_QUICKACK, US &off,
+ sizeof(off));
#endif
- if (!good_response) goto RESPONSE_FAILED;
+ if (!smtp_reap_banner(sx))
+ goto RESPONSE_FAILED;
+ }
#ifndef DISABLE_EVENT
{
uschar * s;
- lookup_dnssec_authenticated = sx->host->dnssec==DS_YES ? US"yes"
- : sx->host->dnssec==DS_NO ? US"no" : NULL;
- s = event_raise(sx->tblock->event_action, US"smtp:connect", sx->buffer);
+ lookup_dnssec_authenticated = sx->conn_args.host->dnssec==DS_YES ? US"yes"
+ : sx->conn_args.host->dnssec==DS_NO ? US"no" : NULL;
+ s = event_raise(sx->conn_args.tblock->event_action, US"smtp:connect", sx->buffer);
if (s)
{
set_errno_nohost(sx->addrlist, ERRNO_EXPANDFAIL,
@@ -1820,7 +2218,7 @@ goto SEND_QUIT;
mailers use upper case for some reason (the RFC is quite clear about case
independence) so, for peace of mind, I gave in. */
- sx->esmtp = verify_check_given_host(CUSS &sx->ob->hosts_avoid_esmtp, sx->host) != OK;
+ sx->esmtp = verify_check_given_host(CUSS &ob->hosts_avoid_esmtp, sx->conn_args.host) != OK;
/* Alas; be careful, since this goto is not an error-out, so conceivably
we might set data between here and the target which we assume to exist
@@ -1830,7 +2228,7 @@ goto SEND_QUIT;
{
smtp_peer_options |= OPTION_TLS;
suppress_tls = FALSE;
- sx->ob->tls_tempfail_tryclear = FALSE;
+ ob->tls_tempfail_tryclear = FALSE;
smtp_command = US"SSL-on-connect";
goto TLS_NEGOTIATE;
}
@@ -1838,67 +2236,116 @@ goto SEND_QUIT;
if (sx->esmtp)
{
- if (smtp_write_command(sx, SCMD_FLUSH, "%s %s\r\n",
- sx->lmtp ? "LHLO" : "EHLO", sx->helo_data) < 0)
+ if (smtp_write_command(sx,
+#ifdef EXPERIMENTAL_PIPE_CONNECT
+ sx->early_pipe_active ? SCMD_BUFFER :
+#endif
+ SCMD_FLUSH,
+ "%s %s\r\n", sx->lmtp ? "LHLO" : "EHLO", sx->helo_data) < 0)
goto SEND_FAILED;
sx->esmtp_sent = TRUE;
- if (!smtp_read_response(sx, sx->buffer, sizeof(sx->buffer), '2',
- sx->ob->command_timeout))
+
+#ifdef EXPERIMENTAL_PIPE_CONNECT
+ if (sx->early_pipe_active)
{
- if (errno != 0 || sx->buffer[0] == 0 || sx->lmtp)
+ sx->pending_EHLO = TRUE;
+
+ /* If we have too many authenticators to handle and might need to AUTH
+ for this transport, pipeline no further as we will need the
+ list of auth methods offered. Reap the banner and EHLO. */
+
+ if ( (ob->hosts_require_auth || ob->hosts_try_auth)
+ && f.smtp_in_early_pipe_no_auth)
{
-#ifdef EXPERIMENTAL_DSN_INFO
- sx->helo_response = string_copy(sx->buffer);
-#endif
- goto RESPONSE_FAILED;
+ DEBUG(D_transport) debug_printf("may need to auth, so pipeline no further\n");
+ if (smtp_write_command(sx, SCMD_FLUSH, NULL) < 0)
+ goto SEND_FAILED;
+ if (sync_responses(sx, 2, 0) != 0)
+ {
+ HDEBUG(D_transport)
+ debug_printf("failed reaping pipelined cmd responses\n");
+ goto RESPONSE_FAILED;
+ }
+ sx->early_pipe_active = FALSE;
}
- sx->esmtp = FALSE;
}
-#ifdef EXPERIMENTAL_DSN_INFO
- sx->helo_response = string_copy(sx->buffer);
+ else
#endif
+ if (!smtp_reap_ehlo(sx))
+ goto RESPONSE_FAILED;
}
else
DEBUG(D_transport)
debug_printf("not sending EHLO (host matches hosts_avoid_esmtp)\n");
- if (!sx->esmtp)
- {
- BOOL good_response;
- int n = sizeof(sx->buffer);
- uschar * rsp = sx->buffer;
+#ifdef EXPERIMENTAL_PIPE_CONNECT
+ if (!sx->early_pipe_active)
+#endif
+ if (!sx->esmtp)
+ {
+ BOOL good_response;
+ int n = sizeof(sx->buffer);
+ uschar * rsp = sx->buffer;
- if (sx->esmtp_sent && (n = Ustrlen(sx->buffer)) < sizeof(sx->buffer)/2)
- { rsp = sx->buffer + n + 1; n = sizeof(sx->buffer) - n; }
+ if (sx->esmtp_sent && (n = Ustrlen(sx->buffer)) < sizeof(sx->buffer)/2)
+ { rsp = sx->buffer + n + 1; n = sizeof(sx->buffer) - n; }
- if (smtp_write_command(sx, SCMD_FLUSH, "HELO %s\r\n", sx->helo_data) < 0)
- goto SEND_FAILED;
- good_response = smtp_read_response(sx, rsp, n, '2', sx->ob->command_timeout);
+ if (smtp_write_command(sx, SCMD_FLUSH, "HELO %s\r\n", sx->helo_data) < 0)
+ goto SEND_FAILED;
+ good_response = smtp_read_response(sx, rsp, n, '2', ob->command_timeout);
#ifdef EXPERIMENTAL_DSN_INFO
- sx->helo_response = string_copy(rsp);
+ sx->helo_response = string_copy(rsp);
#endif
- if (!good_response)
- {
- /* Handle special logging for a closed connection after HELO
- when had previously sent EHLO */
-
- if (rsp != sx->buffer && rsp[0] == 0 && (errno == 0 || errno == ECONNRESET))
+ if (!good_response)
{
- errno = ERRNO_SMTPCLOSED;
- goto EHLOHELO_FAILED;
+ /* Handle special logging for a closed connection after HELO
+ when had previously sent EHLO */
+
+ if (rsp != sx->buffer && rsp[0] == 0 && (errno == 0 || errno == ECONNRESET))
+ {
+ errno = ERRNO_SMTPCLOSED;
+ goto EHLOHELO_FAILED;
+ }
+ memmove(sx->buffer, rsp, Ustrlen(rsp));
+ goto RESPONSE_FAILED;
}
- memmove(sx->buffer, rsp, Ustrlen(rsp));
- goto RESPONSE_FAILED;
}
- }
-
- sx->avoid_option = sx->peer_offered = smtp_peer_options = 0;
if (sx->esmtp || sx->lmtp)
{
- sx->peer_offered = ehlo_response(sx->buffer,
- OPTION_TLS /* others checked later */
- );
+#ifdef EXPERIMENTAL_PIPE_CONNECT
+ if (!sx->early_pipe_active)
+#endif
+ {
+ sx->peer_offered = ehlo_response(sx->buffer,
+ OPTION_TLS /* others checked later */
+#ifdef EXPERIMENTAL_PIPE_CONNECT
+ | (sx->early_pipe_ok
+ ? OPTION_IGNQ
+ | OPTION_CHUNKING | OPTION_PRDR | OPTION_DSN | OPTION_PIPE | OPTION_SIZE
+#ifdef SUPPORT_I18N
+ | OPTION_UTF8
+#endif
+ | OPTION_EARLY_PIPE
+ : 0
+ )
+#endif
+ );
+#ifdef EXPERIMENTAL_PIPE_CONNECT
+ if (sx->early_pipe_ok)
+ {
+ sx->ehlo_resp.cleartext_features = sx->peer_offered;
+
+ if ( (sx->peer_offered & (OPTION_PIPE | OPTION_EARLY_PIPE))
+ == (OPTION_PIPE | OPTION_EARLY_PIPE))
+ {
+ DEBUG(D_transport) debug_printf("PIPE_CONNECT usable in future for this IP\n");
+ sx->ehlo_resp.cleartext_auths = study_ehlo_auths(sx);
+ write_ehlo_cache_entry(sx);
+ }
+ }
+#endif
+ }
/* Set tls_offered if the response to EHLO specifies support for STARTTLS. */
@@ -1927,13 +2374,13 @@ else
if (cutthrough.cctx.sock >= 0 && cutthrough.callout_hold_only)
{
sx->cctx = cutthrough.cctx;
- sx->host->port = sx->port = cutthrough.host.port;
+ sx->conn_args.host->port = sx->port = cutthrough.host.port;
}
else
{
sx->cctx.sock = 0; /* stdin */
sx->cctx.tls_ctx = NULL;
- smtp_port_for_connect(sx->host, sx->port); /* Record the port that was used */
+ smtp_port_for_connect(sx->conn_args.host, sx->port); /* Record the port that was used */
}
sx->inblock.cctx = sx->outblock.cctx = &sx->cctx;
smtp_command = big_buffer;
@@ -1967,15 +2414,28 @@ for error analysis. */
#ifdef SUPPORT_TLS
if ( smtp_peer_options & OPTION_TLS
&& !suppress_tls
- && verify_check_given_host(CUSS &sx->ob->hosts_avoid_tls, sx->host) != OK
+ && verify_check_given_host(CUSS &ob->hosts_avoid_tls, sx->conn_args.host) != OK
&& ( !sx->verify
- || verify_check_given_host(CUSS &sx->ob->hosts_verify_avoid_tls, sx->host) != OK
+ || verify_check_given_host(CUSS &ob->hosts_verify_avoid_tls, sx->conn_args.host) != OK
) )
{
uschar buffer2[4096];
+
if (smtp_write_command(sx, SCMD_FLUSH, "STARTTLS\r\n") < 0)
goto SEND_FAILED;
+#ifdef EXPERIMENTAL_PIPE_CONNECT
+ /* If doing early-pipelining reap the banner and EHLO-response but leave
+ the response for the STARTTLS we just sent alone. */
+
+ if (sx->early_pipe_active && sync_responses(sx, 2, 0) != 0)
+ {
+ HDEBUG(D_transport)
+ debug_printf("failed reaping pipelined cmd responses\n");
+ goto RESPONSE_FAILED;
+ }
+#endif
+
/* If there is an I/O error, transmission of this message is deferred. If
there is a temporary rejection of STARRTLS and tls_tempfail_tryclear is
false, we also defer. However, if there is a temporary rejection of STARTTLS
@@ -1983,12 +2443,11 @@ if ( smtp_peer_options & OPTION_TLS
STARTTLS, we carry on. This means we will try to send the message in clear,
unless the host is in hosts_require_tls (tested below). */
- if (!smtp_read_response(sx, buffer2, sizeof(buffer2), '2',
- sx->ob->command_timeout))
+ if (!smtp_read_response(sx, buffer2, sizeof(buffer2), '2', ob->command_timeout))
{
if ( errno != 0
|| buffer2[0] == 0
- || (buffer2[0] == '4' && !sx->ob->tls_tempfail_tryclear)
+ || (buffer2[0] == '4' && !ob->tls_tempfail_tryclear)
)
{
Ustrncpy(sx->buffer, buffer2, sizeof(sx->buffer));
@@ -2004,8 +2463,8 @@ if ( smtp_peer_options & OPTION_TLS
{
address_item * addr;
uschar * errstr;
- sx->cctx.tls_ctx = tls_client_start(sx->cctx.sock, sx->host,
- sx->addrlist, sx->tblock,
+ sx->cctx.tls_ctx = tls_client_start(sx->cctx.sock, sx->conn_args.host,
+ sx->addrlist, sx->conn_args.tblock,
# ifdef SUPPORT_DANE
sx->dane ? &tlsa_dnsa : NULL,
# endif
@@ -2023,9 +2482,9 @@ if ( smtp_peer_options & OPTION_TLS
{
log_write(0, LOG_MAIN,
"DANE attempt failed; TLS connection to %s [%s]: %s",
- sx->host->name, sx->host->address, errstr);
+ sx->conn_args.host->name, sx->conn_args.host->address, errstr);
# ifndef DISABLE_EVENT
- (void) event_raise(sx->tblock->event_action,
+ (void) event_raise(sx->conn_args.tblock->event_action,
US"dane:fail", US"validation-failure"); /* could do with better detail */
# endif
}
@@ -2064,10 +2523,9 @@ start of the Exim process (in exim.c). */
if (tls_out.active.sock >= 0)
{
- char *greeting_cmd;
- BOOL good_response;
+ uschar * greeting_cmd;
- if (!sx->helo_data && !(sx->helo_data = expand_string(sx->ob->helo_data)))
+ if (!sx->helo_data && !(sx->helo_data = expand_string(ob->helo_data)))
{
uschar *message = string_sprintf("failed to expand helo_data: %s",
expand_string_message);
@@ -2076,36 +2534,64 @@ if (tls_out.active.sock >= 0)
goto SEND_QUIT;
}
+#ifdef EXPERIMENTAL_PIPE_CONNECT
+ /* For SMTPS there is no cleartext early-pipe; use the crypted permission bit.
+ We're unlikely to get the group sent and delivered before the server sends its
+ banner, but it's still worth sending as a group.
+ For STARTTLS allow for cleartext early-pipe but no crypted early-pipe, but not
+ the reverse. */
+
+ if (sx->smtps ? sx->early_pipe_ok : sx->early_pipe_active)
+ {
+ sx->peer_offered = sx->ehlo_resp.crypted_features;
+ if ((sx->early_pipe_active =
+ !!(sx->ehlo_resp.crypted_features & OPTION_EARLY_PIPE)))
+ DEBUG(D_transport) debug_printf("Using cached crypted PIPE_CONNECT\n");
+ }
+#endif
+
/* For SMTPS we need to wait for the initial OK response. */
if (sx->smtps)
- {
- good_response = smtp_read_response(sx, sx->buffer, sizeof(sx->buffer),
- '2', sx->ob->command_timeout);
-#ifdef EXPERIMENTAL_DSN_INFO
- sx->smtp_greeting = string_copy(sx->buffer);
+#ifdef EXPERIMENTAL_PIPE_CONNECT
+ if (sx->early_pipe_active)
+ {
+ sx->pending_BANNER = TRUE;
+ sx->outblock.cmd_count = 1;
+ }
+ else
#endif
- if (!good_response) goto RESPONSE_FAILED;
- }
+ if (!smtp_reap_banner(sx))
+ goto RESPONSE_FAILED;
- if (sx->esmtp)
- greeting_cmd = "EHLO";
+ if (sx->lmtp)
+ greeting_cmd = US"LHLO";
+ else if (sx->esmtp)
+ greeting_cmd = US"EHLO";
else
{
- greeting_cmd = "HELO";
+ greeting_cmd = US"HELO";
DEBUG(D_transport)
debug_printf("not sending EHLO (host matches hosts_avoid_esmtp)\n");
}
- if (smtp_write_command(sx, SCMD_FLUSH, "%s %s\r\n",
- sx->lmtp ? "LHLO" : greeting_cmd, sx->helo_data) < 0)
+ if (smtp_write_command(sx,
+#ifdef EXPERIMENTAL_PIPE_CONNECT
+ sx->early_pipe_active ? SCMD_BUFFER :
+#endif
+ SCMD_FLUSH,
+ "%s %s\r\n", greeting_cmd, sx->helo_data) < 0)
goto SEND_FAILED;
- good_response = smtp_read_response(sx, sx->buffer, sizeof(sx->buffer),
- '2', sx->ob->command_timeout);
-#ifdef EXPERIMENTAL_DSN_INFO
- sx->helo_response = string_copy(sx->buffer);
+
+#ifdef EXPERIMENTAL_PIPE_CONNECT
+ if (sx->early_pipe_active)
+ sx->pending_EHLO = TRUE;
+ else
#endif
- if (!good_response) goto RESPONSE_FAILED;
- smtp_peer_options = 0;
+ {
+ if (!smtp_reap_ehlo(sx))
+ goto RESPONSE_FAILED;
+ smtp_peer_options = 0;
+ }
}
/* If the host is required to use a secure channel, ensure that we
@@ -2118,7 +2604,7 @@ else if ( sx->smtps
# ifdef EXPERIMENTAL_REQUIRETLS
|| tls_requiretls & REQUIRETLS_MSG
# endif
- || verify_check_given_host(CUSS &sx->ob->hosts_require_tls, sx->host) == OK
+ || verify_check_given_host(CUSS &ob->hosts_require_tls, sx->conn_args.host) == OK
)
{
errno =
@@ -2131,7 +2617,7 @@ else if ( sx->smtps
? "an attempt to start TLS failed" : "the server did not offer TLS support");
# if defined(SUPPORT_DANE) && !defined(DISABLE_EVENT)
if (sx->dane)
- (void) event_raise(sx->tblock->event_action, US"dane:fail",
+ (void) event_raise(sx->conn_args.tblock->event_action, US"dane:fail",
smtp_peer_options & OPTION_TLS
? US"validation-failure" /* could do with better detail */
: US"starttls-not-supported");
@@ -2153,23 +2639,41 @@ if (continue_hostname == NULL
{
if (sx->esmtp || sx->lmtp)
{
+#ifdef EXPERIMENTAL_PIPE_CONNECT
+ if (!sx->early_pipe_active)
+#endif
+ {
sx->peer_offered = ehlo_response(sx->buffer,
0 /* no TLS */
- | (sx->lmtp && sx->ob->lmtp_ignore_quota ? OPTION_IGNQ : 0)
+#ifdef EXPERIMENTAL_PIPE_CONNECT
+ | (sx->lmtp && ob->lmtp_ignore_quota ? OPTION_IGNQ : 0)
+ | OPTION_DSN | OPTION_PIPE | OPTION_SIZE
+ | OPTION_CHUNKING | OPTION_PRDR | OPTION_UTF8 | OPTION_REQUIRETLS
+ | (tls_out.active.sock >= 0 ? OPTION_EARLY_PIPE : 0) /* not for lmtp */
+
+#else
+
+ | (sx->lmtp && ob->lmtp_ignore_quota ? OPTION_IGNQ : 0)
| OPTION_CHUNKING
| OPTION_PRDR
-#ifdef SUPPORT_I18N
+# ifdef SUPPORT_I18N
| (sx->addrlist->prop.utf8_msg ? OPTION_UTF8 : 0)
/*XXX if we hand peercaps on to continued-conn processes,
must not depend on this addr */
-#endif
+# endif
| OPTION_DSN
| OPTION_PIPE
- | (sx->ob->size_addition >= 0 ? OPTION_SIZE : 0)
-#if defined(SUPPORT_TLS) && defined(EXPERIMENTAL_REQUIRETLS)
+ | (ob->size_addition >= 0 ? OPTION_SIZE : 0)
+# if defined(SUPPORT_TLS) && defined(EXPERIMENTAL_REQUIRETLS)
| (tls_requiretls & REQUIRETLS_MSG ? OPTION_REQUIRETLS : 0)
+# endif
#endif
);
+#ifdef EXPERIMENTAL_PIPE_CONNECT
+ if (tls_out.active.sock >= 0)
+ sx->ehlo_resp.crypted_features = sx->peer_offered;
+#endif
+ }
/* Set for IGNOREQUOTA if the response to LHLO specifies support and the
lmtp_ignore_quota option was set. */
@@ -2186,26 +2690,26 @@ if (continue_hostname == NULL
the current host matches hosts_avoid_pipelining, don't do it. */
if ( sx->peer_offered & OPTION_PIPE
- && verify_check_given_host(CUSS &sx->ob->hosts_avoid_pipelining, sx->host) != OK)
+ && verify_check_given_host(CUSS &ob->hosts_avoid_pipelining, sx->conn_args.host) != OK)
smtp_peer_options |= OPTION_PIPE;
DEBUG(D_transport) debug_printf("%susing PIPELINING\n",
smtp_peer_options & OPTION_PIPE ? "" : "not ");
if ( sx->peer_offered & OPTION_CHUNKING
- && verify_check_given_host(CUSS &sx->ob->hosts_try_chunking, sx->host) != OK)
+ && verify_check_given_host(CUSS &ob->hosts_try_chunking, sx->conn_args.host) != OK)
sx->peer_offered &= ~OPTION_CHUNKING;
if (sx->peer_offered & OPTION_CHUNKING)
- {DEBUG(D_transport) debug_printf("CHUNKING usable\n");}
+ DEBUG(D_transport) debug_printf("CHUNKING usable\n");
#ifndef DISABLE_PRDR
if ( sx->peer_offered & OPTION_PRDR
- && verify_check_given_host(CUSS &sx->ob->hosts_try_prdr, sx->host) != OK)
+ && verify_check_given_host(CUSS &ob->hosts_try_prdr, sx->conn_args.host) != OK)
sx->peer_offered &= ~OPTION_PRDR;
if (sx->peer_offered & OPTION_PRDR)
- {DEBUG(D_transport) debug_printf("PRDR usable\n");}
+ DEBUG(D_transport) debug_printf("PRDR usable\n");
#endif
/* Note if the server supports DSN */
@@ -2223,12 +2727,26 @@ if (continue_hostname == NULL
}
#endif
+#ifdef EXPERIMENTAL_PIPE_CONNECT
+ if ( sx->early_pipe_ok
+ && !sx->early_pipe_active
+ && tls_out.active.sock >= 0
+ && smtp_peer_options & OPTION_PIPE
+ && ( sx->ehlo_resp.cleartext_features | sx->ehlo_resp.crypted_features)
+ & OPTION_EARLY_PIPE)
+ {
+ DEBUG(D_transport) debug_printf("PIPE_CONNECT usable in future for this IP\n");
+ sx->ehlo_resp.crypted_auths = study_ehlo_auths(sx);
+ write_ehlo_cache_entry(sx);
+ }
+#endif
+
/* Note if the response to EHLO specifies support for the AUTH extension.
If it has, check that this host is one we want to authenticate to, and do
the business. The host name and address must be available when the
authenticator's client driver is running. */
- switch (yield = smtp_auth(sx, sx->buffer, sizeof(sx->buffer)))
+ switch (yield = smtp_auth(sx))
{
default: goto SEND_QUIT;
case OK: break;
@@ -2252,7 +2770,7 @@ if (sx->addrlist->prop.utf8_msg)
/* If the transport sets a downconversion mode it overrides any set by ACL
for the message. */
- if ((s = sx->ob->utf8_downconvert))
+ if ((s = ob->utf8_downconvert))
{
if (!(s = expand_string(s)))
{
@@ -2318,7 +2836,7 @@ return OK;
RESPONSE_FAILED:
message = NULL;
- sx->send_quit = check_response(sx->host, &errno, sx->addrlist->more_errno,
+ sx->send_quit = check_response(sx->conn_args.host, &errno, sx->addrlist->more_errno,
sx->buffer, &code, &message, &pass_message);
yield = DEFER;
goto FAILED;
@@ -2326,7 +2844,7 @@ return OK;
SEND_FAILED:
code = '4';
message = US string_sprintf("send() to %s [%s] failed: %s",
- sx->host->name, sx->host->address, strerror(errno));
+ sx->conn_args.host->name, sx->conn_args.host->address, strerror(errno));
sx->send_quit = FALSE;
yield = DEFER;
goto FAILED;
@@ -2373,14 +2891,14 @@ return OK;
FAILED:
sx->ok = FALSE; /* For when reached by GOTO */
set_errno(sx->addrlist, errno, message,
- sx->host->next
+ sx->conn_args.host->next
? DEFER
: code == '5'
#ifdef SUPPORT_I18N
|| errno == ERRNO_UTF8_FWD
#endif
? FAIL : DEFER,
- pass_message, sx->host
+ pass_message, sx->conn_args.host
#ifdef EXPERIMENTAL_DSN_INFO
, sx->smtp_greeting, sx->helo_response
#endif
@@ -2419,7 +2937,7 @@ if (sx->send_quit)
sx->cctx.sock = -1;
#ifndef DISABLE_EVENT
-(void) event_raise(sx->tblock->event_action, US"tcp:close", NULL);
+(void) event_raise(sx->conn_args.tblock->event_action, US"tcp:close", NULL);
#endif
continue_transport = NULL;
@@ -2455,7 +2973,7 @@ if ( message_size > 0
/*XXX problem here under spool_files_wireformat?
Or just forget about lines? Or inflate by a fixed proportion? */
- sprintf(CS p, " SIZE=%d", message_size+message_linecount+sx->ob->size_addition);
+ sprintf(CS p, " SIZE=%d", message_size+message_linecount+(SOB sx->conn_args.ob)->size_addition);
while (*p) p++;
}
@@ -2531,7 +3049,7 @@ Other expansion failures are serious. An empty result is ignored, but there is
otherwise no check - this feature is expected to be used with LMTP and other
cases where non-standard addresses (e.g. without domains) might be required. */
-if (smtp_mail_auth_str(p, sizeof(sx->buffer) - (p-sx->buffer), addrlist, sx->ob))
+if (smtp_mail_auth_str(p, sizeof(sx->buffer) - (p-sx->buffer), addrlist, sx->conn_args.ob))
return ERROR;
return OK;
@@ -2642,7 +3160,7 @@ switch(rc)
case +1: /* Cmd was sent */
if (!smtp_read_response(sx, sx->buffer, sizeof(sx->buffer), '2',
- sx->ob->command_timeout))
+ (SOB sx->conn_args.ob)->command_timeout))
{
if (errno == 0 && sx->buffer[0] == '4')
{
@@ -2680,12 +3198,6 @@ for (addr = sx->first_addr, address_count = 0;
BOOL no_flush;
uschar * rcpt_addr;
- if (tcp_out_fastopen != TFO_NOT_USED && !f.tcp_out_fastopen_logged)
- {
- setflag(addr, af_tcp_fastopen_conn);
- if (tcp_out_fastopen >= TFO_USED) setflag(addr, af_tcp_fastopen);
- }
-
addr->dsn_aware = sx->peer_offered & OPTION_DSN
? dsn_support_yes : dsn_support_no;
@@ -2700,7 +3212,7 @@ for (addr = sx->first_addr, address_count = 0;
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);
+ rcpt_addr = transport_rcpt_address(addr, sx->conn_args.tblock->rcpt_include_affixes);
#ifdef SUPPORT_I18N
if ( testflag(sx->addrlist, af_utf8_downcvt)
@@ -2734,12 +3246,15 @@ for (addr = sx->first_addr, address_count = 0;
case -1: return -3; /* Timeout on RCPT */
case -2: return -2; /* non-MAIL read i/o error */
default: return -1; /* any MAIL error */
+
+#ifdef EXPERIMENTAL_PIPE_CONNECT
+ case -4: return -1; /* non-2xx for pipelined banner or EHLO */
+#endif
}
sx->pending_MAIL = FALSE; /* Dealt with MAIL */
}
} /* Loop for next address */
-f.tcp_out_fastopen_logged = TRUE;
sx->next_addr = addr;
return 0;
}
@@ -2910,6 +3425,7 @@ smtp_deliver(address_item *addrlist, host_item *host, int host_af, int defport,
BOOL *message_defer, BOOL suppress_tls)
{
address_item *addr;
+smtp_transport_options_block * ob = SOB tblock->options_block;
int yield = OK;
int save_errno;
int rc;
@@ -2926,13 +3442,16 @@ suppress_tls = suppress_tls; /* stop compiler warning when no TLS support */
*message_defer = FALSE;
sx.addrlist = addrlist;
-sx.host = host;
-sx.host_af = host_af,
+sx.conn_args.host = host;
+sx.conn_args.host_af = host_af,
sx.port = defport;
-sx.interface = interface;
+sx.conn_args.interface = interface;
sx.helo_data = NULL;
-sx.tblock = tblock;
+sx.conn_args.tblock = tblock;
sx.verify = FALSE;
+#ifdef EXPERIMENTAL_PIPE_CONNECT
+sx.sync_addr = sx.first_addr = addrlist;
+#endif
/* Get the channel set up ready for a message (MAIL FROM being the next
SMTP command to send */
@@ -2971,8 +3490,6 @@ if (tblock->filter_command)
}
}
-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
comments in deliver.c as to how this reconciles, heuristically, with
@@ -3065,6 +3582,11 @@ if ( !(sx.peer_offered & OPTION_CHUNKING)
case 0: break; /* No 2xx or 5xx, but no probs */
case -1: goto END_OFF; /* Timeout on RCPT */
+
+#ifdef EXPERIMENTAL_PIPE_CONNECT
+ case -4: HDEBUG(D_transport)
+ debug_printf("failed reaping pipelined cmd responses\n");
+#endif
default: goto RESPONSE_FAILED; /* I/O error, or any MAIL/DATA error */
}
pipelining_active = FALSE;
@@ -3088,11 +3610,13 @@ if (!(sx.peer_offered & OPTION_CHUNKING) && !sx.ok)
else
{
transport_ctx tctx = {
- {sx.cctx.sock}, /*XXX will this need TLS info? */
- tblock,
- addrlist,
- US".", US"..", /* Escaping strings */
- topt_use_crlf | topt_escape_headers
+ .u = {.fd = sx.cctx.sock}, /*XXX will this need TLS info? */
+ .tblock = tblock,
+ .addr = addrlist,
+ .check_string = US".",
+ .escape_string = US"..", /* Escaping strings */
+ .options =
+ topt_use_crlf | topt_escape_headers
| (tblock->body_only ? topt_no_headers : 0)
| (tblock->headers_only ? topt_no_body : 0)
| (tblock->return_path_add ? topt_add_return_path : 0)
@@ -3127,7 +3651,7 @@ else
sx.buffer[0] = 0;
sigalrm_seen = FALSE;
- transport_write_timeout = sx.ob->data_timeout;
+ transport_write_timeout = ob->data_timeout;
smtp_command = US"sending data block"; /* For error messages */
DEBUG(D_transport|D_v)
if (sx.peer_offered & OPTION_CHUNKING)
@@ -3140,10 +3664,10 @@ else
dkim_exim_sign_init();
# ifdef EXPERIMENTAL_ARC
{
- uschar * s = sx.ob->arc_sign;
+ uschar * s = ob->arc_sign;
if (s)
{
- if (!(sx.ob->dkim.arc_signspec = s = expand_string(s)))
+ if (!(ob->dkim.arc_signspec = s = expand_string(s)))
{
if (!f.expand_string_forcedfail)
{
@@ -3156,12 +3680,12 @@ else
{
/* Ask dkim code to hash the body for ARC */
(void) arc_ams_setup_sign_bodyhash();
- sx.ob->dkim.force_bodyhash = TRUE;
+ ob->dkim.force_bodyhash = TRUE;
}
}
}
# endif
- sx.ok = dkim_transport_write_message(&tctx, &sx.ob->dkim, CUSS &message);
+ sx.ok = dkim_transport_write_message(&tctx, &ob->dkim, CUSS &message);
#else
sx.ok = transport_write_message(&tctx, 0);
#endif
@@ -3203,19 +3727,23 @@ else
case 0: break; /* No 2xx or 5xx, but no probs */
case -1: goto END_OFF; /* Timeout on RCPT */
+
+#ifdef EXPERIMENTAL_PIPE_CONNECT
+ case -4: HDEBUG(D_transport)
+ debug_printf("failed reaping pipelined cmd responses\n");
+#endif
default: goto RESPONSE_FAILED; /* I/O error, or any MAIL/DATA error */
}
}
#ifndef DISABLE_PRDR
- /* For PRDR we optionally get a partial-responses warning
- * followed by the individual responses, before going on with
- * the overall response. If we don't get the warning then deal
- * with per non-PRDR. */
+ /* For PRDR we optionally get a partial-responses warning followed by the
+ individual responses, before going on with the overall response. If we don't
+ get the warning then deal with per non-PRDR. */
+
if(sx.prdr_active)
{
- sx.ok = smtp_read_response(&sx, sx.buffer, sizeof(sx.buffer), '3',
- sx.ob->final_timeout);
+ sx.ok = smtp_read_response(&sx, sx.buffer, sizeof(sx.buffer), '3', ob->final_timeout);
if (!sx.ok && errno == 0) switch(sx.buffer[0])
{
case '2': sx.prdr_active = FALSE;
@@ -3236,7 +3764,7 @@ else
if (!sx.lmtp)
{
sx.ok = smtp_read_response(&sx, sx.buffer, sizeof(sx.buffer), '2',
- sx.ob->final_timeout);
+ ob->final_timeout);
if (!sx.ok && errno == 0 && sx.buffer[0] == '4')
{
errno = ERRNO_DATA4XX;
@@ -3300,7 +3828,7 @@ else
#endif
{
if (!smtp_read_response(&sx, sx.buffer, sizeof(sx.buffer), '2',
- sx.ob->final_timeout))
+ ob->final_timeout))
{
if (errno != 0 || sx.buffer[0] == 0) goto RESPONSE_FAILED;
addr->message = string_sprintf(
@@ -3344,7 +3872,16 @@ else
addr->special_action = flag;
addr->message = conf;
+ if (tcp_out_fastopen)
+ {
+ setflag(addr, af_tcp_fastopen_conn);
+ if (tcp_out_fastopen >= TFO_USED_NODATA) setflag(addr, af_tcp_fastopen);
+ if (tcp_out_fastopen >= TFO_USED_DATA) setflag(addr, af_tcp_fastopen_data);
+ }
if (sx.pipelining_used) setflag(addr, af_pipelining);
+#ifdef EXPERIMENTAL_PIPE_CONNECT
+ if (sx.early_pipe_active) setflag(addr, af_early_pipe);
+#endif
#ifndef DISABLE_PRDR
if (sx.prdr_active) setflag(addr, af_prdr_used);
#endif
@@ -3382,7 +3919,7 @@ else
upgrade all the address statuses. */
sx.ok = smtp_read_response(&sx, sx.buffer, sizeof(sx.buffer), '2',
- sx.ob->final_timeout);
+ ob->final_timeout);
if (!sx.ok)
{
if(errno == 0 && sx.buffer[0] == '4')
@@ -3544,9 +4081,20 @@ if (!sx.ok)
else
{
+#ifdef EXPERIMENTAL_PIPE_CONNECT
+ /* If we were early-pipelinng and the actual EHLO response did not match
+ the cached value we assumed, we could have detected it and passed a
+ custom errno through to here. It would be nice to RSET and retry right
+ away, but to reliably do that we eould need an extra synch point before
+ we committed to data and that would discard half the gained roundrips.
+ Or we could summarily drop the TCP connection. but that is also ugly.
+ Instead, we ignore the possibility (having freshened the cache) and rely
+ on the server telling us with a nonmessage error if we have tried to
+ do something it no longer supports. */
+#endif
set_rc = DEFER;
yield = (save_errno == ERRNO_CHHEADER_FAIL ||
- save_errno == ERRNO_FILTER_FAIL)? ERROR : DEFER;
+ save_errno == ERRNO_FILTER_FAIL) ? ERROR : DEFER;
}
}
@@ -3604,7 +4152,7 @@ if (sx.completed_addr && sx.ok && sx.send_quit)
|| (
#ifdef SUPPORT_TLS
( tls_out.active.sock < 0 && !continue_proxy_cipher
- || verify_check_given_host(CUSS &sx.ob->hosts_nopass_tls, host) != OK
+ || verify_check_given_host(CUSS &ob->hosts_nopass_tls, host) != OK
)
&&
#endif
@@ -3623,8 +4171,8 @@ if (sx.completed_addr && sx.ok && sx.send_quit)
host->address, strerror(errno));
sx.send_quit = FALSE;
}
- else if (! (sx.ok = smtp_read_response(&sx, sx.buffer,
- sizeof(sx.buffer), '2', sx.ob->command_timeout)))
+ else if (! (sx.ok = smtp_read_response(&sx, sx.buffer, sizeof(sx.buffer),
+ '2', ob->command_timeout)))
{
int code;
sx.send_quit = check_response(host, &errno, 0, sx.buffer, &code, &msg,
@@ -3660,7 +4208,7 @@ if (sx.completed_addr && sx.ok && sx.send_quit)
#ifdef SUPPORT_TLS
if (tls_out.active.sock >= 0)
if ( f.continue_more
- || verify_check_given_host(CUSS &sx.ob->hosts_noproxy_tls, host) == OK)
+ || verify_check_given_host(CUSS &ob->hosts_noproxy_tls, host) == OK)
{
/* Before passing the socket on, or returning to caller with it still
open, we must shut down TLS. Not all MTAs allow for the continuation
@@ -3675,7 +4223,7 @@ if (sx.completed_addr && sx.ok && sx.send_quit)
&& smtp_write_command(&sx, SCMD_FLUSH, "EHLO %s\r\n", sx.helo_data)
>= 0
&& smtp_read_response(&sx, sx.buffer, sizeof(sx.buffer),
- '2', sx.ob->command_timeout);
+ '2', ob->command_timeout);
if (sx.ok && f.continue_more)
return yield; /* More addresses for another run */
@@ -3725,7 +4273,7 @@ propagate it from the initial
if (f.running_in_test_harness) millisleep(100); /* let parent debug out */
/* does not return */
smtp_proxy_tls(sx.cctx.tls_ctx, sx.buffer, sizeof(sx.buffer), pfd,
- sx.ob->command_timeout);
+ ob->command_timeout);
}
if (pid > 0) /* parent */
@@ -3800,6 +4348,7 @@ HDEBUG(D_transport|D_acl|D_v) debug_printf_indent(" SMTP(close)>>\n");
if (sx.send_quit)
{
shutdown(sx.cctx.sock, SHUT_WR);
+ millisleep(f.running_in_test_harness ? 200 : 20);
if (fcntl(sx.cctx.sock, F_SETFL, O_NONBLOCK) == 0)
for (rc = 16; read(sx.cctx.sock, sx.inbuffer, sizeof(sx.inbuffer)) > 0 && rc > 0;)
rc--; /* drain socket */
@@ -3838,8 +4387,7 @@ Returns: nothing
void
smtp_transport_closedown(transport_instance *tblock)
{
-smtp_transport_options_block *ob =
- (smtp_transport_options_block *)tblock->options_block;
+smtp_transport_options_block * ob = SOB tblock->options_block;
client_conn_ctx cctx;
smtp_context sx;
uschar buffer[256];
@@ -3947,8 +4495,7 @@ BOOL expired = TRUE;
uschar *expanded_hosts = NULL;
uschar *pistring;
uschar *tid = string_sprintf("%s transport", tblock->name);
-smtp_transport_options_block *ob =
- (smtp_transport_options_block *)(tblock->options_block);
+smtp_transport_options_block *ob = SOB tblock->options_block;
host_item *hostlist = addrlist->host_list;
host_item *host;
diff --git a/src/src/transports/smtp.h b/src/src/transports/smtp.h
index 3219b05a9..b9e2b0b02 100644
--- a/src/src/transports/smtp.h
+++ b/src/src/transports/smtp.h
@@ -46,6 +46,9 @@ typedef struct {
uschar *hosts_avoid_tls;
uschar *hosts_verify_avoid_tls;
uschar *hosts_avoid_pipelining;
+#ifdef EXPERIMENTAL_PIPE_CONNECT
+ uschar *hosts_pipe_connect;
+#endif
uschar *hosts_avoid_esmtp;
#ifdef SUPPORT_TLS
uschar *hosts_nopass_tls;
@@ -99,20 +102,26 @@ typedef struct {
#endif
} smtp_transport_options_block;
+#define SOB (smtp_transport_options_block *)
+
+
/* smtp connect context */
typedef struct {
uschar * from_addr;
address_item * addrlist;
- host_item * host;
- int host_af;
+
+ smtp_connect_args conn_args;
int port;
- uschar * interface;
BOOL verify:1;
BOOL lmtp:1;
BOOL smtps:1;
BOOL ok:1;
BOOL setting_up:1;
+#ifdef EXPERIMENTAL_PIPE_CONNECT
+ BOOL early_pipe_ok:1;
+ BOOL early_pipe_active:1;
+#endif
BOOL esmtp:1;
BOOL esmtp_sent:1;
BOOL pipelining_used:1;
@@ -127,6 +136,10 @@ typedef struct {
BOOL dane:1;
BOOL dane_required:1;
#endif
+#ifdef EXPERIMENTAL_PIPE_CONNECT
+ BOOL pending_BANNER:1;
+ BOOL pending_EHLO:1;
+#endif
BOOL pending_MAIL:1;
BOOL pending_BDAT:1;
BOOL good_RCPT:1;
@@ -145,6 +158,7 @@ typedef struct {
uschar * smtp_greeting;
uschar * helo_response;
#endif
+ ehlo_resp_precis ehlo_resp;
address_item * first_addr;
address_item * next_addr;
@@ -156,13 +170,11 @@ typedef struct {
uschar buffer[DELIVER_BUFFER_SIZE];
uschar inbuffer[4096];
uschar outbuffer[4096];
-
- transport_instance * tblock;
- smtp_transport_options_block * ob;
} smtp_context;
extern int smtp_setup_conn(smtp_context *, BOOL);
extern int smtp_write_mail_and_rcpt_cmds(smtp_context *, int *);
+extern int smtp_reap_early_pipe(smtp_context *, int *);
/* Data for reading the private options. */
diff --git a/src/src/verify.c b/src/src/verify.c
index d14cb685e..236a87c52 100644
--- a/src/src/verify.c
+++ b/src/src/verify.c
@@ -669,12 +669,12 @@ coding means skipping this whole loop and doing the append separately. */
addr->message);
sx.addrlist = addr;
- sx.host = host;
- sx.host_af = host_af,
+ sx.conn_args.host = host;
+ sx.conn_args.host_af = host_af,
sx.port = port;
- sx.interface = interface;
+ sx.conn_args.interface = interface;
sx.helo_data = tf->helo_data;
- sx.tblock = addr->transport;
+ sx.conn_args.tblock = addr->transport;
sx.verify = TRUE;
tls_retry_connection:
@@ -1320,7 +1320,7 @@ cutthrough_data_puts(US"\r\n", 2);
static uschar
cutthrough_response(client_conn_ctx * cctx, char expect, uschar ** copy, int timeout)
{
-smtp_context sx;
+smtp_context sx = {0};
uschar inbuffer[4096];
uschar responsebuffer[4096];
diff --git a/test/aux-var-src/tls_conf_prefix b/test/aux-var-src/tls_conf_prefix
index aa29a2c85..0a14cee24 100644
--- a/test/aux-var-src/tls_conf_prefix
+++ b/test/aux-var-src/tls_conf_prefix
@@ -17,3 +17,6 @@ chunking_advertise_hosts =
.ifdef _HAVE_REQTLS
tls_advertise_requiretls =
.endif
+.ifdef _HAVE_PIPE_CONNECT
+pipelining_connect_advertise_hosts = :
+.endif
diff --git a/test/confs/0002 b/test/confs/0002
index 6ca2d1bcb..165f0a4b2 100644
--- a/test/confs/0002
+++ b/test/confs/0002
@@ -12,6 +12,9 @@ log_file_path = DIR/spool/log/%slog
gecos_pattern = ""
gecos_name = CALLER_NAME
tls_advertise_hosts =
+.ifdef _HAVE_PIPE_CONNECT
+pipelining_connect_advertise_hosts = :
+.endif
# ----- Main settings -----
diff --git a/test/confs/0564 b/test/confs/0564
index 6a23896ef..e01628550 100644
--- a/test/confs/0564
+++ b/test/confs/0564
@@ -9,6 +9,9 @@ gecos_pattern = ""
gecos_name = CALLER_NAME
chunking_advertise_hosts =
tls_advertise_hosts =
+.ifdef _HAVE_PIPE_CONNECT
+pipelining_connect_advertise_hosts =
+.endif
# ----- Main settings -----
diff --git a/test/confs/0900 b/test/confs/0900
index dfb58eb43..caf17be81 100644
--- a/test/confs/0900
+++ b/test/confs/0900
@@ -16,6 +16,9 @@ tls_advertise_hosts = ${if eq {SRV}{tls} {*}}
.ifdef _HAVE_REQTLS
tls_advertise_requiretls = :
.endif
+.ifdef _HAVE_PIPE_CONNECT
+pipelining_connect_advertise_hosts = :
+.endif
# ----- Main settings -----
diff --git a/test/confs/0906 b/test/confs/0906
index 8bb0a9e26..ff99d9304 100644
--- a/test/confs/0906
+++ b/test/confs/0906
@@ -10,6 +10,9 @@ gecos_pattern = ""
gecos_name = CALLER_NAME
chunking_advertise_hosts = *
tls_advertise_hosts = ${if eq {SRV}{tls} {*}}
+.ifdef _HAVE_PIPE_CONNECT
+pipelining_connect_advertise_hosts =
+.endif
# ----- Main settings -----
diff --git a/test/confs/4050 b/test/confs/4050
new file mode 100644
index 000000000..06a85c089
--- /dev/null
+++ b/test/confs/4050
@@ -0,0 +1,63 @@
+# test config 4050
+# Pipelining the early part of the SMTP conversation
+
+CONNECTCOND=
+CONTROL=
+RETRY=7d
+
+keep_environment = PATH
+exim_path = EXIM_PATH
+host_lookup_order = bydns
+spool_directory = DIR/spool
+
+.ifdef SERVER
+log_file_path = DIR/spool/log/SERVER%slog
+.else
+log_file_path = DIR/spool/log/%slog
+.endif
+
+gecos_pattern = ""
+gecos_name = CALLER_NAME
+dns_cname_loops = 9
+chunking_advertise_hosts =
+tls_advertise_hosts =
+
+pipelining_connect_advertise_hosts = CONNECTCOND
+retry_data_expire = RETRY
+log_selector = +received_recipients +pipelining
+queue_only
+
+acl_smtp_rcpt = accept
+
+#
+begin routers
+
+server:
+ driver = redirect
+ condition = ${if eq {SERVER}{server}}
+ data = :blackhole:
+
+clientbounce:
+ driver = redirect
+ condition = ${if !def:sender_address}
+ data = :blackhole:
+
+client:
+ driver = manualroute
+ route_data = 127.0.0.1::PORT_D
+ self = send
+ transport = smtp
+
+#
+begin transports
+
+smtp:
+ driver = smtp
+ hosts_pipe_connect = CONTROL
+ max_rcpt = 1
+
+#
+begin retry
+
+* * F,5d,1d
+
diff --git a/test/confs/4051 b/test/confs/4051
new file mode 120000
index 000000000..ca1c19b14
--- /dev/null
+++ b/test/confs/4051
@@ -0,0 +1 @@
+4050 \ No newline at end of file
diff --git a/test/confs/4052 b/test/confs/4052
new file mode 120000
index 000000000..ca1c19b14
--- /dev/null
+++ b/test/confs/4052
@@ -0,0 +1 @@
+4050 \ No newline at end of file
diff --git a/test/confs/4053 b/test/confs/4053
new file mode 100644
index 000000000..3a85deeab
--- /dev/null
+++ b/test/confs/4053
@@ -0,0 +1,47 @@
+# test config 4053
+# Early-pipe, CHUNKING
+
+keep_environment = PATH
+exim_path = EXIM_PATH
+host_lookup_order = bydns
+spool_directory = DIR/spool
+
+.ifdef SERVER
+log_file_path = DIR/spool/log/SERVER%slog
+.else
+log_file_path = DIR/spool/log/%slog
+.endif
+
+gecos_pattern = ""
+gecos_name = CALLER_NAME
+dns_cname_loops = 9
+chunking_advertise_hosts = *
+tls_advertise_hosts =
+
+pipelining_connect_advertise_hosts = *
+log_selector = +pipelining
+queue_only
+
+acl_smtp_rcpt = accept
+
+#
+begin routers
+
+server:
+ driver = redirect
+ condition = ${if eq {SERVER}{server}}
+ data = :blackhole:
+
+client:
+ driver = manualroute
+ route_data = 127.0.0.1::PORT_D
+ self = send
+ transport = smtp
+
+#
+begin transports
+
+smtp:
+ driver = smtp
+ hosts_pipe_connect = *
+
diff --git a/test/confs/4056 b/test/confs/4056
new file mode 100644
index 000000000..c77c439e4
--- /dev/null
+++ b/test/confs/4056
@@ -0,0 +1,62 @@
+# test config 4056
+# Early-pipe, AUTH (no TLS!)
+
+keep_environment = PATH
+exim_path = EXIM_PATH
+host_lookup_order = bydns
+spool_directory = DIR/spool
+
+.ifdef SERVER
+log_file_path = DIR/spool/log/SERVER%slog
+.else
+log_file_path = DIR/spool/log/%slog
+.endif
+
+gecos_pattern = ""
+gecos_name = CALLER_NAME
+dns_cname_loops = 9
+chunking_advertise_hosts =
+tls_advertise_hosts =
+
+pipelining_connect_advertise_hosts = *
+auth_advertise_hosts = *
+log_selector = +pipelining
+queue_only
+
+acl_smtp_rcpt = accept
+
+#
+begin routers
+
+server:
+ driver = redirect
+ condition = ${if eq {SERVER}{server}}
+ data = :blackhole:
+
+client:
+ driver = manualroute
+ route_data = 127.0.0.1::PORT_D
+ self = send
+ transport = smtp
+
+#
+begin transports
+
+smtp:
+ driver = smtp
+ hosts_pipe_connect = *
+ hosts_require_auth = *
+
+#
+begin authenticators
+
+plain:
+ driver = plaintext
+ public_name = PLAIN
+
+ server_condition = "\
+ ${if and {{eq{$auth2}{userx}}{eq{$auth3}{secret}}}{yes}{no}}"
+ server_set_id = $auth2
+
+ client_send = ^userx^secret
+
diff --git a/test/confs/4058 b/test/confs/4058
new file mode 100644
index 000000000..887427a57
--- /dev/null
+++ b/test/confs/4058
@@ -0,0 +1,50 @@
+# test config 4058
+# Early-pipelining with TCP Fast Open
+
+OPT=
+
+keep_environment = PATH
+exim_path = EXIM_PATH
+host_lookup_order = bydns
+spool_directory = DIR/spool
+
+.ifdef SERVER
+log_file_path = DIR/spool/log/SERVER%slog
+.else
+log_file_path = DIR/spool/log/%slog
+.endif
+log_selector = +received_recipients +millisec +pipelining
+
+gecos_pattern = ""
+gecos_name = CALLER_NAME
+dns_cname_loops = 9
+chunking_advertise_hosts = OPT
+tls_advertise_hosts =
+
+pipelining_connect_advertise_hosts = *
+queue_only
+
+acl_smtp_rcpt = accept
+
+#
+begin routers
+
+server:
+ driver = redirect
+ condition = ${if eq {SERVER}{server}}
+ data = :blackhole:
+
+client:
+ driver = manualroute
+ route_data = 127.0.0.1::PORT_D
+ self = send
+ transport = smtp
+
+#
+begin transports
+
+smtp:
+ driver = smtp
+ hosts_try_fastopen = *
+ hosts_pipe_connect = *
+
diff --git a/test/confs/4060 b/test/confs/4060
new file mode 100644
index 000000000..935d49fe1
--- /dev/null
+++ b/test/confs/4060
@@ -0,0 +1,58 @@
+# test config 4060
+# Pipelining the early part of the SMTP conversation, starttls
+
+CONTROL=*
+OPT=
+
+keep_environment = PATH
+exim_path = EXIM_PATH
+host_lookup_order = bydns
+spool_directory = DIR/spool
+
+.ifdef SERVER
+log_file_path = DIR/spool/log/SERVER%slog
+.else
+log_file_path = DIR/spool/log/%slog
+.endif
+
+gecos_pattern = ""
+gecos_name = CALLER_NAME
+dns_cname_loops = 9
+chunking_advertise_hosts = OPT
+tls_advertise_hosts = *
+tls_certificate = ${if eq {SERVER}{server}{DIR/aux-fixed/cert1}fail}
+
+# Avoid ECDHE key-exchange so that we can wireshark-decode
+.ifdef _HAVE_GNUTLS
+tls_require_ciphers = NORMAL:-KX-ALL:+RSA
+.endif
+
+pipelining_connect_advertise_hosts = *
+log_selector = +received_recipients +pipelining
+queue_only
+
+acl_smtp_rcpt = accept
+
+#
+begin routers
+
+server:
+ driver = redirect
+ condition = ${if eq {SERVER}{server}}
+ data = :blackhole:
+
+client:
+ driver = manualroute
+ route_data = 127.0.0.1::PORT_D
+ self = send
+ transport = smtp
+
+#
+begin transports
+
+smtp:
+ driver = smtp
+ hosts_pipe_connect = CONTROL
+ tls_verify_hosts =
+ tls_try_verify_hosts =
+
diff --git a/test/confs/4061 b/test/confs/4061
new file mode 100644
index 000000000..3ff106f8b
--- /dev/null
+++ b/test/confs/4061
@@ -0,0 +1,59 @@
+# test config 4061
+# Pipelining the early part of the SMTP conversation, tls-on-connect
+
+CONTROL=*
+
+keep_environment = PATH
+exim_path = EXIM_PATH
+host_lookup_order = bydns
+spool_directory = DIR/spool
+
+.ifdef SERVER
+log_file_path = DIR/spool/log/SERVER%slog
+.else
+log_file_path = DIR/spool/log/%slog
+.endif
+
+gecos_pattern = ""
+gecos_name = CALLER_NAME
+dns_cname_loops = 9
+chunking_advertise_hosts =
+tls_on_connect_ports = PORT_D
+tls_advertise_hosts = *
+tls_certificate = ${if eq {SERVER}{server}{DIR/aux-fixed/cert1}fail}
+
+# Avoid ECDHE key-exchange so that we can wireshark-decode
+.ifdef _HAVE_GNUTLS
+tls_require_ciphers = NORMAL:-KX-ALL:+RSA
+.endif
+
+pipelining_connect_advertise_hosts = *
+log_selector = +received_recipients +pipelining
+queue_only
+
+acl_smtp_rcpt = accept
+
+#
+begin routers
+
+server:
+ driver = redirect
+ condition = ${if eq {SERVER}{server}}
+ data = :blackhole:
+
+client:
+ driver = manualroute
+ route_data = 127.0.0.1::PORT_D
+ self = send
+ transport = smtp
+
+#
+begin transports
+
+smtp:
+ driver = smtp
+ hosts_pipe_connect = CONTROL
+ protocol = smtps
+ tls_verify_hosts =
+ tls_try_verify_hosts =
+
diff --git a/test/confs/4062 b/test/confs/4062
new file mode 120000
index 000000000..f9a2604f2
--- /dev/null
+++ b/test/confs/4062
@@ -0,0 +1 @@
+4060 \ No newline at end of file
diff --git a/test/confs/4063 b/test/confs/4063
new file mode 120000
index 000000000..723e8a8a0
--- /dev/null
+++ b/test/confs/4063
@@ -0,0 +1 @@
+4061 \ No newline at end of file
diff --git a/test/confs/4064 b/test/confs/4064
new file mode 100644
index 000000000..8c3c300a5
--- /dev/null
+++ b/test/confs/4064
@@ -0,0 +1,72 @@
+# test config 4064
+# Early-pipe, AUTH, GnuTLS
+
+keep_environment = PATH
+exim_path = EXIM_PATH
+host_lookup_order = bydns
+spool_directory = DIR/spool
+
+.ifdef SERVER
+log_file_path = DIR/spool/log/SERVER%slog
+.else
+log_file_path = DIR/spool/log/%slog
+.endif
+
+gecos_pattern = ""
+gecos_name = CALLER_NAME
+dns_cname_loops = 9
+chunking_advertise_hosts = OPT
+tls_advertise_hosts = *
+tls_certificate = ${if eq {SERVER}{server}{DIR/aux-fixed/cert1}fail}
+
+# Avoid ECDHE key-exchange so that we can wireshark-decode
+.ifdef _HAVE_GNUTLS
+tls_require_ciphers = NORMAL:-KX-ALL:+RSA
+.endif
+
+pipelining_connect_advertise_hosts = *
+auth_advertise_hosts = *
+
+log_selector = +received_recipients +pipelining
+queue_only
+
+acl_smtp_rcpt = accept
+
+#
+begin routers
+
+server:
+ driver = redirect
+ condition = ${if eq {SERVER}{server}}
+ data = :blackhole:
+
+client:
+ driver = manualroute
+ route_data = 127.0.0.1::PORT_D
+ self = send
+ transport = smtp
+
+#
+begin transports
+
+smtp:
+ driver = smtp
+ hosts_pipe_connect = *
+ tls_verify_hosts =
+ tls_try_verify_hosts =
+ hosts_require_auth = *
+
+#
+begin authenticators
+
+plain:
+ driver = plaintext
+ public_name = PLAIN
+
+ server_advertise_condition = ${if eq{$tls_in_cipher}{}{no}{yes}}
+ server_condition = "\
+ ${if and {{eq{$auth2}{userx}}{eq{$auth3}{secret}}}{yes}{no}}"
+ server_set_id = $auth2
+
+ client_send = ^userx^secret
+
diff --git a/test/confs/4065 b/test/confs/4065
new file mode 100644
index 000000000..dcf0d6f5d
--- /dev/null
+++ b/test/confs/4065
@@ -0,0 +1,73 @@
+# test config 4065
+# Early-pipe, AUTH, GnuTLS, tls-on-connect
+
+keep_environment = PATH
+exim_path = EXIM_PATH
+host_lookup_order = bydns
+spool_directory = DIR/spool
+
+.ifdef SERVER
+log_file_path = DIR/spool/log/SERVER%slog
+.else
+log_file_path = DIR/spool/log/%slog
+.endif
+
+gecos_pattern = ""
+gecos_name = CALLER_NAME
+dns_cname_loops = 9
+chunking_advertise_hosts =
+tls_on_connect_ports = PORT_D
+tls_advertise_hosts = *
+tls_certificate = ${if eq {SERVER}{server}{DIR/aux-fixed/cert1}fail}
+
+# Avoid ECDHE key-exchange so that we can wireshark-decode
+.ifdef _HAVE_GNUTLS
+tls_require_ciphers = NORMAL:-KX-ALL:+RSA
+.endif
+
+pipelining_connect_advertise_hosts = *
+auth_advertise_hosts = *
+
+log_selector = +received_recipients +pipelining
+queue_only
+
+acl_smtp_rcpt = accept
+
+#
+begin routers
+
+server:
+ driver = redirect
+ condition = ${if eq {SERVER}{server}}
+ data = :blackhole:
+
+client:
+ driver = manualroute
+ route_data = 127.0.0.1::PORT_D
+ self = send
+ transport = smtp
+
+#
+begin transports
+
+smtp:
+ driver = smtp
+ hosts_pipe_connect = *
+ protocol = smtps
+ tls_verify_hosts =
+ tls_try_verify_hosts =
+ hosts_require_auth = *
+
+#
+begin authenticators
+
+plain:
+ driver = plaintext
+ public_name = PLAIN
+
+ server_condition = "\
+ ${if and {{eq{$auth2}{userx}}{eq{$auth3}{secret}}}{yes}{no}}"
+ server_set_id = $auth2
+
+ client_send = ^userx^secret
+
diff --git a/test/confs/4066 b/test/confs/4066
new file mode 120000
index 000000000..8d736b10e
--- /dev/null
+++ b/test/confs/4066
@@ -0,0 +1 @@
+4064 \ No newline at end of file
diff --git a/test/confs/4067 b/test/confs/4067
new file mode 120000
index 000000000..ddeca2101
--- /dev/null
+++ b/test/confs/4067
@@ -0,0 +1 @@
+4065 \ No newline at end of file
diff --git a/test/confs/5910 b/test/confs/5910
index 2437758bf..55838b3db 100644
--- a/test/confs/5910
+++ b/test/confs/5910
@@ -21,6 +21,9 @@ log_file_path = DIR/spool/log/%slog
gecos_pattern = ""
gecos_name = CALLER_NAME
chunking_advertise_hosts =
+.ifdef _HAVE_PIPE_CONNECT
+pipelining_connect_advertise_hosts =
+.endif
primary_hostname = myhost.test.ex
diff --git a/test/log/4027 b/test/log/4027
index 7df91966f..06903fe7f 100644
--- a/test/log/4027
+++ b/test/log/4027
@@ -2,8 +2,8 @@
1999-03-02 09:44:33 10HmaX-0005vi-00 => user_tfo@test.ex R=my_main_router T=my_smtp H=127.0.0.1 [127.0.0.1]:1224 PRX=[127.0.0.1]:1225 C="250 accepted OK"
1999-03-02 09:44:33 10HmaX-0005vi-00 Completed
1999-03-02 09:44:33 10HmaY-0005vi-00 <= CALLER@myhost.test.ex U=CALLER P=local-esmtp S=sss
-1999-03-02 09:44:33 10HmaY-0005vi-00 => user_tfo@test.ex R=my_main_router T=my_smtp H=127.0.0.1 [127.0.0.1]:1224 PRX=[127.0.0.1]:1225 TFO C="250 accepted OK"
+1999-03-02 09:44:33 10HmaY-0005vi-00 => user_tfo@test.ex R=my_main_router T=my_smtp H=127.0.0.1 [127.0.0.1]:1224 PRX=[127.0.0.1]:1225 C="250 accepted OK"
1999-03-02 09:44:33 10HmaY-0005vi-00 Completed
1999-03-02 09:44:33 10HmaZ-0005vi-00 <= CALLER@myhost.test.ex U=CALLER P=local-esmtp S=sss
-1999-03-02 09:44:33 10HmaZ-0005vi-00 => user_tfo@test.ex R=my_main_router T=my_smtp H=127.0.0.1 [127.0.0.1]:1224 PRX=[127.0.0.1]:1225 TFO C="250 accepted OK"
+1999-03-02 09:44:33 10HmaZ-0005vi-00 => user_tfo@test.ex R=my_main_router T=my_smtp H=127.0.0.1 [127.0.0.1]:1224 PRX=[127.0.0.1]:1225 TFO* C="250 accepted OK"
1999-03-02 09:44:33 10HmaZ-0005vi-00 Completed
diff --git a/test/log/4050 b/test/log/4050
new file mode 100644
index 000000000..bd994fe1f
--- /dev/null
+++ b/test/log/4050
@@ -0,0 +1,6 @@
+
+******** SERVER ********
+1999-03-02 09:44:33 exim x.yz daemon started: pid=pppp, no queue runs, listening for SMTP on port 1225
+1999-03-02 09:44:33 exim x.yz daemon started: pid=pppp, no queue runs, listening for SMTP on port 1225
+1999-03-02 09:44:33 exim x.yz daemon started: pid=pppp, no queue runs, listening for SMTP on port 1225
+1999-03-02 09:44:33 10HmaX-0005vi-00 <= a@test.ex H=(testclient) [127.0.0.1] P=esmtp L* S=sss for a@test.ex
diff --git a/test/log/4051 b/test/log/4051
new file mode 100644
index 000000000..00fa3ebc9
--- /dev/null
+++ b/test/log/4051
@@ -0,0 +1,44 @@
+1999-03-02 09:44:33 10HmaX-0005vi-00 <= CALLER@the.local.host.name U=CALLER P=local S=sss for notadvertised@test.ex
+1999-03-02 09:44:33 Start queue run: pid=pppp
+1999-03-02 09:44:33 10HmaX-0005vi-00 => notadvertised@test.ex R=client T=smtp H=127.0.0.1 [127.0.0.1] L C="250 OK id=10HmaY-0005vi-00"
+1999-03-02 09:44:33 10HmaX-0005vi-00 Completed
+1999-03-02 09:44:33 End queue run: pid=pppp
+1999-03-02 09:44:33 10HmaZ-0005vi-00 <= CALLER@the.local.host.name U=CALLER P=local S=sss for nocache@test.ex
+1999-03-02 09:44:33 Start queue run: pid=pppp
+1999-03-02 09:44:33 10HmaZ-0005vi-00 => nocache@test.ex R=client T=smtp H=127.0.0.1 [127.0.0.1] L C="250 OK id=10HmbA-0005vi-00"
+1999-03-02 09:44:33 10HmaZ-0005vi-00 Completed
+1999-03-02 09:44:33 End queue run: pid=pppp
+1999-03-02 09:44:33 10HmbB-0005vi-00 <= CALLER@the.local.host.name U=CALLER P=local S=sss for hascache@test.ex
+1999-03-02 09:44:33 Start queue run: pid=pppp
+1999-03-02 09:44:33 10HmbB-0005vi-00 => hascache@test.ex R=client T=smtp H=127.0.0.1 [127.0.0.1] L* C="250 OK id=10HmbC-0005vi-00"
+1999-03-02 09:44:33 10HmbB-0005vi-00 Completed
+1999-03-02 09:44:33 End queue run: pid=pppp
+1999-03-02 09:44:33 10HmbD-0005vi-00 <= CALLER@the.local.host.name U=CALLER P=local S=sss for clientno@test.ex
+1999-03-02 09:44:33 Start queue run: pid=pppp
+1999-03-02 09:44:33 10HmbD-0005vi-00 => clientno@test.ex R=client T=smtp H=127.0.0.1 [127.0.0.1] L C="250 OK id=10HmbE-0005vi-00"
+1999-03-02 09:44:33 10HmbD-0005vi-00 Completed
+1999-03-02 09:44:33 End queue run: pid=pppp
+
+******** SERVER ********
+1999-03-02 09:44:33 exim x.yz daemon started: pid=pppp, no queue runs, listening for SMTP on port 1225
+1999-03-02 09:44:33 10HmaY-0005vi-00 <= CALLER@the.local.host.name H=localhost (the.local.host.name) [127.0.0.1] P=esmtp L S=sss id=E10HmaX-0005vi-00@the.local.host.name for notadvertised@test.ex
+1999-03-02 09:44:33 Start queue run: pid=pppp
+1999-03-02 09:44:33 10HmaY-0005vi-00 => :blackhole: <notadvertised@test.ex> R=server
+1999-03-02 09:44:33 10HmaY-0005vi-00 Completed
+1999-03-02 09:44:33 End queue run: pid=pppp
+1999-03-02 09:44:33 exim x.yz daemon started: pid=pppp, no queue runs, listening for SMTP on port 1225
+1999-03-02 09:44:33 10HmbA-0005vi-00 <= CALLER@the.local.host.name H=localhost (the.local.host.name) [127.0.0.1] P=esmtp L. S=sss id=E10HmaZ-0005vi-00@the.local.host.name for nocache@test.ex
+1999-03-02 09:44:33 Start queue run: pid=pppp
+1999-03-02 09:44:33 10HmbA-0005vi-00 => :blackhole: <nocache@test.ex> R=server
+1999-03-02 09:44:33 10HmbA-0005vi-00 Completed
+1999-03-02 09:44:33 End queue run: pid=pppp
+1999-03-02 09:44:33 10HmbC-0005vi-00 <= CALLER@the.local.host.name H=localhost (the.local.host.name) [127.0.0.1] P=esmtp L* S=sss id=E10HmbB-0005vi-00@the.local.host.name for hascache@test.ex
+1999-03-02 09:44:33 Start queue run: pid=pppp
+1999-03-02 09:44:33 10HmbC-0005vi-00 => :blackhole: <hascache@test.ex> R=server
+1999-03-02 09:44:33 10HmbC-0005vi-00 Completed
+1999-03-02 09:44:33 End queue run: pid=pppp
+1999-03-02 09:44:33 10HmbE-0005vi-00 <= CALLER@the.local.host.name H=localhost (the.local.host.name) [127.0.0.1] P=esmtp L. S=sss id=E10HmbD-0005vi-00@the.local.host.name for clientno@test.ex
+1999-03-02 09:44:33 Start queue run: pid=pppp
+1999-03-02 09:44:33 10HmbE-0005vi-00 => :blackhole: <clientno@test.ex> R=server
+1999-03-02 09:44:33 10HmbE-0005vi-00 Completed
+1999-03-02 09:44:33 End queue run: pid=pppp
diff --git a/test/log/4052 b/test/log/4052
new file mode 100644
index 000000000..91fc0b78b
--- /dev/null
+++ b/test/log/4052
@@ -0,0 +1,52 @@
+1999-03-02 09:44:33 10HmaX-0005vi-00 <= CALLER@the.local.host.name U=CALLER P=local S=sss for nocache@test.ex
+1999-03-02 09:44:33 Start queue run: pid=pppp
+1999-03-02 09:44:33 10HmaX-0005vi-00 => nocache@test.ex R=client T=smtp H=127.0.0.1 [127.0.0.1] L C="250 OK id=10HmaY-0005vi-00"
+1999-03-02 09:44:33 10HmaX-0005vi-00 Completed
+1999-03-02 09:44:33 End queue run: pid=pppp
+1999-03-02 09:44:33 10HmaZ-0005vi-00 <= CALLER@the.local.host.name U=CALLER P=local S=sss for hascache@test.ex
+1999-03-02 09:44:33 Start queue run: pid=pppp
+1999-03-02 09:44:33 10HmaZ-0005vi-00 => hascache@test.ex R=client T=smtp H=127.0.0.1 [127.0.0.1] L* C="250 OK id=10HmbA-0005vi-00"
+1999-03-02 09:44:33 10HmaZ-0005vi-00 Completed
+1999-03-02 09:44:33 End queue run: pid=pppp
+1999-03-02 09:44:33 10HmbB-0005vi-00 <= CALLER@the.local.host.name U=CALLER P=local S=sss for client_old@test.ex
+1999-03-02 09:44:33 Start queue run: pid=pppp
+1999-03-02 09:44:33 10HmbB-0005vi-00 => client_old@test.ex R=client T=smtp H=127.0.0.1 [127.0.0.1] L C="250 OK id=10HmbC-0005vi-00"
+1999-03-02 09:44:33 10HmbB-0005vi-00 Completed
+1999-03-02 09:44:33 End queue run: pid=pppp
+1999-03-02 09:44:33 10HmbD-0005vi-00 <= CALLER@the.local.host.name U=CALLER P=local S=sss for nocache2@test.ex withcache2@test.ex badbanner@test.ex
+1999-03-02 09:44:33 10HmbD-0005vi-00 => nocache2@test.ex R=client T=smtp H=127.0.0.1 [127.0.0.1] L C="250 message accepted"
+1999-03-02 09:44:33 10HmbD-0005vi-00 => withcache2@test.ex R=client T=smtp H=127.0.0.1 [127.0.0.1] L* C="250 message accepted"
+1999-03-02 09:44:33 10HmbD-0005vi-00 H=127.0.0.1 [127.0.0.1]: SMTP error from remote mail server after pipelined DATA: 450 sorry no banner for you today
+1999-03-02 09:44:33 10HmbD-0005vi-00 == badbanner@test.ex R=client T=smtp defer (0) H=127.0.0.1 [127.0.0.1]: SMTP error from remote mail server after pipelined DATA: 450 sorry no banner for you today
+1999-03-02 09:44:33 10HmbE-0005vi-00 <= CALLER@the.local.host.name U=CALLER P=local S=sss for nocache@test.ex withcache@test.ex baddata@test.ex badrcpt@test.ex badmail@test.ex withcache2@test.ex
+1999-03-02 09:44:33 10HmbE-0005vi-00 => nocache@test.ex R=client T=smtp H=127.0.0.1 [127.0.0.1] L C="250 message accepted"
+1999-03-02 09:44:33 10HmbE-0005vi-00 => withcache@test.ex R=client T=smtp H=127.0.0.1 [127.0.0.1] L* C="250 message accepted"
+1999-03-02 09:44:33 10HmbE-0005vi-00 ** baddata@test.ex R=client T=smtp H=127.0.0.1 [127.0.0.1]: SMTP error from remote mail server after pipelined DATA: 550 obscure data error
+1999-03-02 09:44:33 10HmbE-0005vi-00 ** badrcpt@test.ex R=client T=smtp H=127.0.0.1 [127.0.0.1]: SMTP error from remote mail server after RCPT TO:<badrcpt@test.ex>: 550 rcpt refused
+1999-03-02 09:44:33 10HmbE-0005vi-00 ** badmail@test.ex R=client T=smtp H=127.0.0.1 [127.0.0.1]: SMTP error from remote mail server after pipelined MAIL FROM:<CALLER@the.local.host.name>: 550 mail cmd refused
+1999-03-02 09:44:33 10HmbE-0005vi-00 => withcache2@test.ex R=client T=smtp H=127.0.0.1 [127.0.0.1] L* C="250 message accepted"
+1999-03-02 09:44:33 10HmbF-0005vi-00 <= <> R=10HmbE-0005vi-00 U=EXIMUSER P=local S=sss for CALLER@the.local.host.name
+1999-03-02 09:44:33 10HmbF-0005vi-00 => :blackhole: <CALLER@the.local.host.name> R=clientbounce
+1999-03-02 09:44:33 10HmbF-0005vi-00 Completed
+1999-03-02 09:44:33 10HmbE-0005vi-00 Completed
+1999-03-02 09:44:33 10HmbG-0005vi-00 <= CALLER@the.local.host.name U=CALLER P=local S=sss for extchange@test.ex
+1999-03-02 09:44:33 10HmbG-0005vi-00 => extchange@test.ex R=client T=smtp H=127.0.0.1 [127.0.0.1] L* C="250 message accepted"
+1999-03-02 09:44:33 10HmbG-0005vi-00 Completed
+
+******** SERVER ********
+1999-03-02 09:44:33 exim x.yz daemon started: pid=pppp, no queue runs, listening for SMTP on port 1225
+1999-03-02 09:44:33 10HmaY-0005vi-00 <= CALLER@the.local.host.name H=localhost (the.local.host.name) [127.0.0.1] P=esmtp L. S=sss id=E10HmaX-0005vi-00@the.local.host.name for nocache@test.ex
+1999-03-02 09:44:33 Start queue run: pid=pppp
+1999-03-02 09:44:33 10HmaY-0005vi-00 => :blackhole: <nocache@test.ex> R=server
+1999-03-02 09:44:33 10HmaY-0005vi-00 Completed
+1999-03-02 09:44:33 End queue run: pid=pppp
+1999-03-02 09:44:33 10HmbA-0005vi-00 <= CALLER@the.local.host.name H=localhost (the.local.host.name) [127.0.0.1] P=esmtp L* S=sss id=E10HmaZ-0005vi-00@the.local.host.name for hascache@test.ex
+1999-03-02 09:44:33 Start queue run: pid=pppp
+1999-03-02 09:44:33 10HmbA-0005vi-00 => :blackhole: <hascache@test.ex> R=server
+1999-03-02 09:44:33 10HmbA-0005vi-00 Completed
+1999-03-02 09:44:33 End queue run: pid=pppp
+1999-03-02 09:44:33 10HmbC-0005vi-00 <= CALLER@the.local.host.name H=localhost (the.local.host.name) [127.0.0.1] P=esmtp L. S=sss id=E10HmbB-0005vi-00@the.local.host.name for client_old@test.ex
+1999-03-02 09:44:33 Start queue run: pid=pppp
+1999-03-02 09:44:33 10HmbC-0005vi-00 => :blackhole: <client_old@test.ex> R=server
+1999-03-02 09:44:33 10HmbC-0005vi-00 Completed
+1999-03-02 09:44:33 End queue run: pid=pppp
diff --git a/test/log/4053 b/test/log/4053
new file mode 100644
index 000000000..758be7c40
--- /dev/null
+++ b/test/log/4053
@@ -0,0 +1,23 @@
+1999-03-02 09:44:33 10HmaX-0005vi-00 <= CALLER@the.local.host.name U=CALLER P=local S=sss
+1999-03-02 09:44:33 Start queue run: pid=pppp
+1999-03-02 09:44:33 10HmaX-0005vi-00 => a@test.ex R=client T=smtp H=127.0.0.1 [127.0.0.1] L K C="250- 3nn byte chunk, total 3nn\\n250 OK id=10HmaY-0005vi-00"
+1999-03-02 09:44:33 10HmaX-0005vi-00 Completed
+1999-03-02 09:44:33 End queue run: pid=pppp
+1999-03-02 09:44:33 10HmaZ-0005vi-00 <= CALLER@the.local.host.name U=CALLER P=local S=sss
+1999-03-02 09:44:33 Start queue run: pid=pppp
+1999-03-02 09:44:33 10HmaZ-0005vi-00 => b@test.ex R=client T=smtp H=127.0.0.1 [127.0.0.1] L* K C="250- 3nn byte chunk, total 3nn\\n250 OK id=10HmbA-0005vi-00"
+1999-03-02 09:44:33 10HmaZ-0005vi-00 Completed
+1999-03-02 09:44:33 End queue run: pid=pppp
+
+******** SERVER ********
+1999-03-02 09:44:33 exim x.yz daemon started: pid=pppp, no queue runs, listening for SMTP on port 1225
+1999-03-02 09:44:33 10HmaY-0005vi-00 <= CALLER@the.local.host.name H=localhost (the.local.host.name) [127.0.0.1] P=esmtp L. K S=sss id=E10HmaX-0005vi-00@the.local.host.name
+1999-03-02 09:44:33 Start queue run: pid=pppp
+1999-03-02 09:44:33 10HmaY-0005vi-00 => :blackhole: <a@test.ex> R=server
+1999-03-02 09:44:33 10HmaY-0005vi-00 Completed
+1999-03-02 09:44:33 End queue run: pid=pppp
+1999-03-02 09:44:33 10HmbA-0005vi-00 <= CALLER@the.local.host.name H=localhost (the.local.host.name) [127.0.0.1] P=esmtp L* K S=sss id=E10HmaZ-0005vi-00@the.local.host.name
+1999-03-02 09:44:33 Start queue run: pid=pppp
+1999-03-02 09:44:33 10HmbA-0005vi-00 => :blackhole: <b@test.ex> R=server
+1999-03-02 09:44:33 10HmbA-0005vi-00 Completed
+1999-03-02 09:44:33 End queue run: pid=pppp
diff --git a/test/log/4056 b/test/log/4056
new file mode 100644
index 000000000..03f13aebd
--- /dev/null
+++ b/test/log/4056
@@ -0,0 +1,23 @@
+1999-03-02 09:44:33 10HmaX-0005vi-00 <= CALLER@the.local.host.name U=CALLER P=local S=sss
+1999-03-02 09:44:33 Start queue run: pid=pppp
+1999-03-02 09:44:33 10HmaX-0005vi-00 => a@test.ex R=client T=smtp H=127.0.0.1 [127.0.0.1] A=plain L C="250 OK id=10HmaY-0005vi-00"
+1999-03-02 09:44:33 10HmaX-0005vi-00 Completed
+1999-03-02 09:44:33 End queue run: pid=pppp
+1999-03-02 09:44:33 10HmaZ-0005vi-00 <= CALLER@the.local.host.name U=CALLER P=local S=sss
+1999-03-02 09:44:33 Start queue run: pid=pppp
+1999-03-02 09:44:33 10HmaZ-0005vi-00 => b@test.ex R=client T=smtp H=127.0.0.1 [127.0.0.1] A=plain L* C="250 OK id=10HmbA-0005vi-00"
+1999-03-02 09:44:33 10HmaZ-0005vi-00 Completed
+1999-03-02 09:44:33 End queue run: pid=pppp
+
+******** SERVER ********
+1999-03-02 09:44:33 exim x.yz daemon started: pid=pppp, no queue runs, listening for SMTP on port 1225
+1999-03-02 09:44:33 10HmaY-0005vi-00 <= CALLER@the.local.host.name H=localhost (the.local.host.name) [127.0.0.1] P=esmtpa L. A=plain:userx S=sss id=E10HmaX-0005vi-00@the.local.host.name
+1999-03-02 09:44:33 Start queue run: pid=pppp
+1999-03-02 09:44:33 10HmaY-0005vi-00 => :blackhole: <a@test.ex> R=server
+1999-03-02 09:44:33 10HmaY-0005vi-00 Completed
+1999-03-02 09:44:33 End queue run: pid=pppp
+1999-03-02 09:44:33 10HmbA-0005vi-00 <= CALLER@the.local.host.name H=localhost (the.local.host.name) [127.0.0.1] P=esmtpa L* A=plain:userx S=sss id=E10HmaZ-0005vi-00@the.local.host.name
+1999-03-02 09:44:33 Start queue run: pid=pppp
+1999-03-02 09:44:33 10HmbA-0005vi-00 => :blackhole: <b@test.ex> R=server
+1999-03-02 09:44:33 10HmbA-0005vi-00 Completed
+1999-03-02 09:44:33 End queue run: pid=pppp
diff --git a/test/log/4058 b/test/log/4058
new file mode 100644
index 000000000..21ce510cb
--- /dev/null
+++ b/test/log/4058
@@ -0,0 +1,44 @@
+2017-07-30 18:51:05.712 10HmaX-0005vi-00 <= CALLER@the.local.host.name U=CALLER P=local S=sss for nocache@test.ex
+2017-07-30 18:51:05.712 Start queue run: pid=pppp
+2017-07-30 18:51:05.712 10HmaX-0005vi-00 => nocache@test.ex R=client T=smtp H=127.0.0.1 [127.0.0.1] L C="250 OK id=10HmaY-0005vi-00"
+2017-07-30 18:51:05.712 10HmaX-0005vi-00 Completed
+2017-07-30 18:51:05.712 End queue run: pid=pppp
+2017-07-30 18:51:05.712 10HmaZ-0005vi-00 <= CALLER@the.local.host.name U=CALLER P=local S=sss for hascache@test.ex
+2017-07-30 18:51:05.712 Start queue run: pid=pppp
+2017-07-30 18:51:05.712 10HmaZ-0005vi-00 => hascache@test.ex R=client T=smtp H=127.0.0.1 [127.0.0.1] TFO* L* C="250 OK id=10HmbA-0005vi-00"
+2017-07-30 18:51:05.712 10HmaZ-0005vi-00 Completed
+2017-07-30 18:51:05.712 End queue run: pid=pppp
+2017-07-30 18:51:05.712 10HmbB-0005vi-00 <= CALLER@the.local.host.name U=CALLER P=local S=sss for nocache@test.ex
+2017-07-30 18:51:05.712 Start queue run: pid=pppp
+2017-07-30 18:51:05.712 10HmbB-0005vi-00 => nocache@test.ex R=client T=smtp H=127.0.0.1 [127.0.0.1] TFO L K C="250- 3nn byte chunk, total 3nn\\n250 OK id=10HmbC-0005vi-00"
+2017-07-30 18:51:05.712 10HmbB-0005vi-00 Completed
+2017-07-30 18:51:05.712 End queue run: pid=pppp
+2017-07-30 18:51:05.712 10HmbD-0005vi-00 <= CALLER@the.local.host.name U=CALLER P=local S=sss for chunking@test.ex
+2017-07-30 18:51:05.712 Start queue run: pid=pppp
+2017-07-30 18:51:05.712 10HmbD-0005vi-00 => chunking@test.ex R=client T=smtp H=127.0.0.1 [127.0.0.1] TFO* L* K C="250- 3nn byte chunk, total 3nn\\n250 OK id=10HmbE-0005vi-00"
+2017-07-30 18:51:05.712 10HmbD-0005vi-00 Completed
+2017-07-30 18:51:05.712 End queue run: pid=pppp
+
+******** SERVER ********
+2017-07-30 18:51:05.712 exim x.yz daemon started: pid=pppp, no queue runs, listening for SMTP on port 1225
+2017-07-30 18:51:05.712 10HmaY-0005vi-00 <= CALLER@the.local.host.name H=localhost (the.local.host.name) [127.0.0.1] P=esmtp L. S=sss id=E10HmaX-0005vi-00@the.local.host.name for nocache@test.ex
+2017-07-30 18:51:05.712 Start queue run: pid=pppp
+2017-07-30 18:51:05.712 10HmaY-0005vi-00 => :blackhole: <nocache@test.ex> R=server
+2017-07-30 18:51:05.712 10HmaY-0005vi-00 Completed
+2017-07-30 18:51:05.712 End queue run: pid=pppp
+2017-07-30 18:51:05.712 10HmbA-0005vi-00 <= CALLER@the.local.host.name H=localhost (the.local.host.name) [127.0.0.1] TFO* P=esmtp L* S=sss id=E10HmaZ-0005vi-00@the.local.host.name for hascache@test.ex
+2017-07-30 18:51:05.712 Start queue run: pid=pppp
+2017-07-30 18:51:05.712 10HmbA-0005vi-00 => :blackhole: <hascache@test.ex> R=server
+2017-07-30 18:51:05.712 10HmbA-0005vi-00 Completed
+2017-07-30 18:51:05.712 End queue run: pid=pppp
+2017-07-30 18:51:05.712 exim x.yz daemon started: pid=pppp, no queue runs, listening for SMTP on port 1225
+2017-07-30 18:51:05.712 10HmbC-0005vi-00 <= CALLER@the.local.host.name H=localhost (the.local.host.name) [127.0.0.1] TFO P=esmtp L. K S=sss id=E10HmbB-0005vi-00@the.local.host.name for nocache@test.ex
+2017-07-30 18:51:05.712 Start queue run: pid=pppp
+2017-07-30 18:51:05.712 10HmbC-0005vi-00 => :blackhole: <nocache@test.ex> R=server
+2017-07-30 18:51:05.712 10HmbC-0005vi-00 Completed
+2017-07-30 18:51:05.712 End queue run: pid=pppp
+2017-07-30 18:51:05.712 10HmbE-0005vi-00 <= CALLER@the.local.host.name H=localhost (the.local.host.name) [127.0.0.1] TFO* P=esmtp L* K S=sss id=E10HmbD-0005vi-00@the.local.host.name for chunking@test.ex
+2017-07-30 18:51:05.712 Start queue run: pid=pppp
+2017-07-30 18:51:05.712 10HmbE-0005vi-00 => :blackhole: <chunking@test.ex> R=server
+2017-07-30 18:51:05.712 10HmbE-0005vi-00 Completed
+2017-07-30 18:51:05.712 End queue run: pid=pppp
diff --git a/test/log/4060 b/test/log/4060
new file mode 100644
index 000000000..cb227a6bc
--- /dev/null
+++ b/test/log/4060
@@ -0,0 +1,54 @@
+1999-03-02 09:44:33 10HmaX-0005vi-00 <= CALLER@the.local.host.name U=CALLER P=local S=sss for nocache@test.ex
+1999-03-02 09:44:33 Start queue run: pid=pppp
+1999-03-02 09:44:33 10HmaX-0005vi-00 => nocache@test.ex R=client T=smtp H=127.0.0.1 [127.0.0.1] X=TLS1.x:ke_RSA_AES_256_CBC_SHAnnn:256 CV=no L C="250 OK id=10HmaY-0005vi-00"
+1999-03-02 09:44:33 10HmaX-0005vi-00 Completed
+1999-03-02 09:44:33 End queue run: pid=pppp
+1999-03-02 09:44:33 10HmaZ-0005vi-00 <= CALLER@the.local.host.name U=CALLER P=local S=sss for hascache@test.ex
+1999-03-02 09:44:33 Start queue run: pid=pppp
+1999-03-02 09:44:33 10HmaZ-0005vi-00 => hascache@test.ex R=client T=smtp H=127.0.0.1 [127.0.0.1] X=TLS1.x:ke_RSA_AES_256_CBC_SHAnnn:256 CV=no L* C="250 OK id=10HmbA-0005vi-00"
+1999-03-02 09:44:33 10HmaZ-0005vi-00 Completed
+1999-03-02 09:44:33 End queue run: pid=pppp
+1999-03-02 09:44:33 10HmbB-0005vi-00 <= CALLER@the.local.host.name U=CALLER P=local S=sss for clientno@test.ex
+1999-03-02 09:44:33 Start queue run: pid=pppp
+1999-03-02 09:44:33 10HmbB-0005vi-00 => clientno@test.ex R=client T=smtp H=127.0.0.1 [127.0.0.1] X=TLS1.x:ke_RSA_AES_256_CBC_SHAnnn:256 CV=no L C="250 OK id=10HmbC-0005vi-00"
+1999-03-02 09:44:33 10HmbB-0005vi-00 Completed
+1999-03-02 09:44:33 End queue run: pid=pppp
+1999-03-02 09:44:33 10HmbD-0005vi-00 <= CALLER@the.local.host.name U=CALLER P=local S=sss for nocache@test.ex
+1999-03-02 09:44:33 Start queue run: pid=pppp
+1999-03-02 09:44:33 10HmbD-0005vi-00 => nocache@test.ex R=client T=smtp H=127.0.0.1 [127.0.0.1] X=TLS1.x:ke_RSA_AES_256_CBC_SHAnnn:256 CV=no L K C="250- 3nn byte chunk, total 3nn\\n250 OK id=10HmbE-0005vi-00"
+1999-03-02 09:44:33 10HmbD-0005vi-00 Completed
+1999-03-02 09:44:33 End queue run: pid=pppp
+1999-03-02 09:44:33 10HmbF-0005vi-00 <= CALLER@the.local.host.name U=CALLER P=local S=sss for chunking@test.ex
+1999-03-02 09:44:33 Start queue run: pid=pppp
+1999-03-02 09:44:33 10HmbF-0005vi-00 => chunking@test.ex R=client T=smtp H=127.0.0.1 [127.0.0.1] X=TLS1.x:ke_RSA_AES_256_CBC_SHAnnn:256 CV=no L* K C="250- 3nn byte chunk, total 3nn\\n250 OK id=10HmbG-0005vi-00"
+1999-03-02 09:44:33 10HmbF-0005vi-00 Completed
+1999-03-02 09:44:33 End queue run: pid=pppp
+
+******** SERVER ********
+1999-03-02 09:44:33 exim x.yz daemon started: pid=pppp, no queue runs, listening for SMTP on port 1225
+1999-03-02 09:44:33 10HmaY-0005vi-00 <= CALLER@the.local.host.name H=localhost (the.local.host.name) [127.0.0.1] P=esmtps L. X=TLS1.x:ke_RSA_AES_256_CBC_SHAnnn:256 CV=no S=sss id=E10HmaX-0005vi-00@the.local.host.name for nocache@test.ex
+1999-03-02 09:44:33 Start queue run: pid=pppp
+1999-03-02 09:44:33 10HmaY-0005vi-00 => :blackhole: <nocache@test.ex> R=server
+1999-03-02 09:44:33 10HmaY-0005vi-00 Completed
+1999-03-02 09:44:33 End queue run: pid=pppp
+1999-03-02 09:44:33 10HmbA-0005vi-00 <= CALLER@the.local.host.name H=localhost (the.local.host.name) [127.0.0.1] P=esmtps L* X=TLS1.x:ke_RSA_AES_256_CBC_SHAnnn:256 CV=no S=sss id=E10HmaZ-0005vi-00@the.local.host.name for hascache@test.ex
+1999-03-02 09:44:33 Start queue run: pid=pppp
+1999-03-02 09:44:33 10HmbA-0005vi-00 => :blackhole: <hascache@test.ex> R=server
+1999-03-02 09:44:33 10HmbA-0005vi-00 Completed
+1999-03-02 09:44:33 End queue run: pid=pppp
+1999-03-02 09:44:33 10HmbC-0005vi-00 <= CALLER@the.local.host.name H=localhost (the.local.host.name) [127.0.0.1] P=esmtps L. X=TLS1.x:ke_RSA_AES_256_CBC_SHAnnn:256 CV=no S=sss id=E10HmbB-0005vi-00@the.local.host.name for clientno@test.ex
+1999-03-02 09:44:33 Start queue run: pid=pppp
+1999-03-02 09:44:33 10HmbC-0005vi-00 => :blackhole: <clientno@test.ex> R=server
+1999-03-02 09:44:33 10HmbC-0005vi-00 Completed
+1999-03-02 09:44:33 End queue run: pid=pppp
+1999-03-02 09:44:33 exim x.yz daemon started: pid=pppp, no queue runs, listening for SMTP on port 1225
+1999-03-02 09:44:33 10HmbE-0005vi-00 <= CALLER@the.local.host.name H=localhost (the.local.host.name) [127.0.0.1] P=esmtps L. X=TLS1.x:ke_RSA_AES_256_CBC_SHAnnn:256 CV=no K S=sss id=E10HmbD-0005vi-00@the.local.host.name for nocache@test.ex
+1999-03-02 09:44:33 Start queue run: pid=pppp
+1999-03-02 09:44:33 10HmbE-0005vi-00 => :blackhole: <nocache@test.ex> R=server
+1999-03-02 09:44:33 10HmbE-0005vi-00 Completed
+1999-03-02 09:44:33 End queue run: pid=pppp
+1999-03-02 09:44:33 10HmbG-0005vi-00 <= CALLER@the.local.host.name H=localhost (the.local.host.name) [127.0.0.1] P=esmtps L* X=TLS1.x:ke_RSA_AES_256_CBC_SHAnnn:256 CV=no K S=sss id=E10HmbF-0005vi-00@the.local.host.name for chunking@test.ex
+1999-03-02 09:44:33 Start queue run: pid=pppp
+1999-03-02 09:44:33 10HmbG-0005vi-00 => :blackhole: <chunking@test.ex> R=server
+1999-03-02 09:44:33 10HmbG-0005vi-00 Completed
+1999-03-02 09:44:33 End queue run: pid=pppp
diff --git a/test/log/4061 b/test/log/4061
new file mode 100644
index 000000000..d1edf2dea
--- /dev/null
+++ b/test/log/4061
@@ -0,0 +1,33 @@
+1999-03-02 09:44:33 10HmaX-0005vi-00 <= CALLER@the.local.host.name U=CALLER P=local S=sss for nocache@test.ex
+1999-03-02 09:44:33 Start queue run: pid=pppp
+1999-03-02 09:44:33 10HmaX-0005vi-00 => nocache@test.ex R=client T=smtp H=127.0.0.1 [127.0.0.1] X=TLS1.x:ke_RSA_AES_256_CBC_SHAnnn:256 CV=no L C="250 OK id=10HmaY-0005vi-00"
+1999-03-02 09:44:33 10HmaX-0005vi-00 Completed
+1999-03-02 09:44:33 End queue run: pid=pppp
+1999-03-02 09:44:33 10HmaZ-0005vi-00 <= CALLER@the.local.host.name U=CALLER P=local S=sss for hascache@test.ex
+1999-03-02 09:44:33 Start queue run: pid=pppp
+1999-03-02 09:44:33 10HmaZ-0005vi-00 => hascache@test.ex R=client T=smtp H=127.0.0.1 [127.0.0.1] X=TLS1.x:ke_RSA_AES_256_CBC_SHAnnn:256 CV=no L* C="250 OK id=10HmbA-0005vi-00"
+1999-03-02 09:44:33 10HmaZ-0005vi-00 Completed
+1999-03-02 09:44:33 End queue run: pid=pppp
+1999-03-02 09:44:33 10HmbB-0005vi-00 <= CALLER@the.local.host.name U=CALLER P=local S=sss for clientno@test.ex
+1999-03-02 09:44:33 Start queue run: pid=pppp
+1999-03-02 09:44:33 10HmbB-0005vi-00 => clientno@test.ex R=client T=smtp H=127.0.0.1 [127.0.0.1] X=TLS1.x:ke_RSA_AES_256_CBC_SHAnnn:256 CV=no L C="250 OK id=10HmbC-0005vi-00"
+1999-03-02 09:44:33 10HmbB-0005vi-00 Completed
+1999-03-02 09:44:33 End queue run: pid=pppp
+
+******** SERVER ********
+1999-03-02 09:44:33 exim x.yz daemon started: pid=pppp, no queue runs, listening for SMTPS on port 1225
+1999-03-02 09:44:33 10HmaY-0005vi-00 <= CALLER@the.local.host.name H=localhost (the.local.host.name) [127.0.0.1] P=esmtps L. X=TLS1.x:ke_RSA_AES_256_CBC_SHAnnn:256 CV=no S=sss id=E10HmaX-0005vi-00@the.local.host.name for nocache@test.ex
+1999-03-02 09:44:33 Start queue run: pid=pppp
+1999-03-02 09:44:33 10HmaY-0005vi-00 => :blackhole: <nocache@test.ex> R=server
+1999-03-02 09:44:33 10HmaY-0005vi-00 Completed
+1999-03-02 09:44:33 End queue run: pid=pppp
+1999-03-02 09:44:33 10HmbA-0005vi-00 <= CALLER@the.local.host.name H=localhost (the.local.host.name) [127.0.0.1] P=esmtps L* X=TLS1.x:ke_RSA_AES_256_CBC_SHAnnn:256 CV=no S=sss id=E10HmaZ-0005vi-00@the.local.host.name for hascache@test.ex
+1999-03-02 09:44:33 Start queue run: pid=pppp
+1999-03-02 09:44:33 10HmbA-0005vi-00 => :blackhole: <hascache@test.ex> R=server
+1999-03-02 09:44:33 10HmbA-0005vi-00 Completed
+1999-03-02 09:44:33 End queue run: pid=pppp
+1999-03-02 09:44:33 10HmbC-0005vi-00 <= CALLER@the.local.host.name H=localhost (the.local.host.name) [127.0.0.1] P=esmtps L. X=TLS1.x:ke_RSA_AES_256_CBC_SHAnnn:256 CV=no S=sss id=E10HmbB-0005vi-00@the.local.host.name for clientno@test.ex
+1999-03-02 09:44:33 Start queue run: pid=pppp
+1999-03-02 09:44:33 10HmbC-0005vi-00 => :blackhole: <clientno@test.ex> R=server
+1999-03-02 09:44:33 10HmbC-0005vi-00 Completed
+1999-03-02 09:44:33 End queue run: pid=pppp
diff --git a/test/log/4062 b/test/log/4062
new file mode 100644
index 000000000..6302a8bb2
--- /dev/null
+++ b/test/log/4062
@@ -0,0 +1,54 @@
+1999-03-02 09:44:33 10HmaX-0005vi-00 <= CALLER@the.local.host.name U=CALLER P=local S=sss for a@test.ex
+1999-03-02 09:44:33 Start queue run: pid=pppp
+1999-03-02 09:44:33 10HmaX-0005vi-00 => a@test.ex R=client T=smtp H=127.0.0.1 [127.0.0.1] X=TLSv1:ke-RSA-AES256-SHA:xxx CV=no L C="250 OK id=10HmaY-0005vi-00"
+1999-03-02 09:44:33 10HmaX-0005vi-00 Completed
+1999-03-02 09:44:33 End queue run: pid=pppp
+1999-03-02 09:44:33 10HmaZ-0005vi-00 <= CALLER@the.local.host.name U=CALLER P=local S=sss for b@test.ex
+1999-03-02 09:44:33 Start queue run: pid=pppp
+1999-03-02 09:44:33 10HmaZ-0005vi-00 => b@test.ex R=client T=smtp H=127.0.0.1 [127.0.0.1] X=TLSv1:ke-RSA-AES256-SHA:xxx CV=no L* C="250 OK id=10HmbA-0005vi-00"
+1999-03-02 09:44:33 10HmaZ-0005vi-00 Completed
+1999-03-02 09:44:33 End queue run: pid=pppp
+1999-03-02 09:44:33 10HmbB-0005vi-00 <= CALLER@the.local.host.name U=CALLER P=local S=sss for c@test.ex
+1999-03-02 09:44:33 Start queue run: pid=pppp
+1999-03-02 09:44:33 10HmbB-0005vi-00 => c@test.ex R=client T=smtp H=127.0.0.1 [127.0.0.1] X=TLSv1:ke-RSA-AES256-SHA:xxx CV=no L C="250 OK id=10HmbC-0005vi-00"
+1999-03-02 09:44:33 10HmbB-0005vi-00 Completed
+1999-03-02 09:44:33 End queue run: pid=pppp
+1999-03-02 09:44:33 10HmbD-0005vi-00 <= CALLER@the.local.host.name U=CALLER P=local S=sss for nocache@test.ex
+1999-03-02 09:44:33 Start queue run: pid=pppp
+1999-03-02 09:44:33 10HmbD-0005vi-00 => nocache@test.ex R=client T=smtp H=127.0.0.1 [127.0.0.1] X=TLSv1:ke-RSA-AES256-SHA:xxx CV=no L K C="250- 3nn byte chunk, total 3nn\\n250 OK id=10HmbE-0005vi-00"
+1999-03-02 09:44:33 10HmbD-0005vi-00 Completed
+1999-03-02 09:44:33 End queue run: pid=pppp
+1999-03-02 09:44:33 10HmbF-0005vi-00 <= CALLER@the.local.host.name U=CALLER P=local S=sss for chunking@test.ex
+1999-03-02 09:44:33 Start queue run: pid=pppp
+1999-03-02 09:44:33 10HmbF-0005vi-00 => chunking@test.ex R=client T=smtp H=127.0.0.1 [127.0.0.1] X=TLSv1:ke-RSA-AES256-SHA:xxx CV=no L* K C="250- 3nn byte chunk, total 3nn\\n250 OK id=10HmbG-0005vi-00"
+1999-03-02 09:44:33 10HmbF-0005vi-00 Completed
+1999-03-02 09:44:33 End queue run: pid=pppp
+
+******** SERVER ********
+1999-03-02 09:44:33 exim x.yz daemon started: pid=pppp, no queue runs, listening for SMTP on port 1225
+1999-03-02 09:44:33 10HmaY-0005vi-00 <= CALLER@the.local.host.name H=localhost (the.local.host.name) [127.0.0.1] P=esmtps L. X=TLSv1:ke-RSA-AES256-SHA:xxx CV=no S=sss id=E10HmaX-0005vi-00@the.local.host.name for a@test.ex
+1999-03-02 09:44:33 Start queue run: pid=pppp
+1999-03-02 09:44:33 10HmaY-0005vi-00 => :blackhole: <a@test.ex> R=server
+1999-03-02 09:44:33 10HmaY-0005vi-00 Completed
+1999-03-02 09:44:33 End queue run: pid=pppp
+1999-03-02 09:44:33 10HmbA-0005vi-00 <= CALLER@the.local.host.name H=localhost (the.local.host.name) [127.0.0.1] P=esmtps L* X=TLSv1:ke-RSA-AES256-SHA:xxx CV=no S=sss id=E10HmaZ-0005vi-00@the.local.host.name for b@test.ex
+1999-03-02 09:44:33 Start queue run: pid=pppp
+1999-03-02 09:44:33 10HmbA-0005vi-00 => :blackhole: <b@test.ex> R=server
+1999-03-02 09:44:33 10HmbA-0005vi-00 Completed
+1999-03-02 09:44:33 End queue run: pid=pppp
+1999-03-02 09:44:33 10HmbC-0005vi-00 <= CALLER@the.local.host.name H=localhost (the.local.host.name) [127.0.0.1] P=esmtps L. X=TLSv1:ke-RSA-AES256-SHA:xxx CV=no S=sss id=E10HmbB-0005vi-00@the.local.host.name for c@test.ex
+1999-03-02 09:44:33 Start queue run: pid=pppp
+1999-03-02 09:44:33 10HmbC-0005vi-00 => :blackhole: <c@test.ex> R=server
+1999-03-02 09:44:33 10HmbC-0005vi-00 Completed
+1999-03-02 09:44:33 End queue run: pid=pppp
+1999-03-02 09:44:33 exim x.yz daemon started: pid=pppp, no queue runs, listening for SMTP on port 1225
+1999-03-02 09:44:33 10HmbE-0005vi-00 <= CALLER@the.local.host.name H=localhost (the.local.host.name) [127.0.0.1] P=esmtps L. X=TLSv1:ke-RSA-AES256-SHA:xxx CV=no K S=sss id=E10HmbD-0005vi-00@the.local.host.name for nocache@test.ex
+1999-03-02 09:44:33 Start queue run: pid=pppp
+1999-03-02 09:44:33 10HmbE-0005vi-00 => :blackhole: <nocache@test.ex> R=server
+1999-03-02 09:44:33 10HmbE-0005vi-00 Completed
+1999-03-02 09:44:33 End queue run: pid=pppp
+1999-03-02 09:44:33 10HmbG-0005vi-00 <= CALLER@the.local.host.name H=localhost (the.local.host.name) [127.0.0.1] P=esmtps L* X=TLSv1:ke-RSA-AES256-SHA:xxx CV=no K S=sss id=E10HmbF-0005vi-00@the.local.host.name for chunking@test.ex
+1999-03-02 09:44:33 Start queue run: pid=pppp
+1999-03-02 09:44:33 10HmbG-0005vi-00 => :blackhole: <chunking@test.ex> R=server
+1999-03-02 09:44:33 10HmbG-0005vi-00 Completed
+1999-03-02 09:44:33 End queue run: pid=pppp
diff --git a/test/log/4063 b/test/log/4063
new file mode 100644
index 000000000..090c36d41
--- /dev/null
+++ b/test/log/4063
@@ -0,0 +1,33 @@
+1999-03-02 09:44:33 10HmaX-0005vi-00 <= CALLER@the.local.host.name U=CALLER P=local S=sss for a@test.ex
+1999-03-02 09:44:33 Start queue run: pid=pppp
+1999-03-02 09:44:33 10HmaX-0005vi-00 => a@test.ex R=client T=smtp H=127.0.0.1 [127.0.0.1] X=TLSv1:ke-RSA-AES256-SHA:xxx CV=no L C="250 OK id=10HmaY-0005vi-00"
+1999-03-02 09:44:33 10HmaX-0005vi-00 Completed
+1999-03-02 09:44:33 End queue run: pid=pppp
+1999-03-02 09:44:33 10HmaZ-0005vi-00 <= CALLER@the.local.host.name U=CALLER P=local S=sss for b@test.ex
+1999-03-02 09:44:33 Start queue run: pid=pppp
+1999-03-02 09:44:33 10HmaZ-0005vi-00 => b@test.ex R=client T=smtp H=127.0.0.1 [127.0.0.1] X=TLSv1:ke-RSA-AES256-SHA:xxx CV=no L* C="250 OK id=10HmbA-0005vi-00"
+1999-03-02 09:44:33 10HmaZ-0005vi-00 Completed
+1999-03-02 09:44:33 End queue run: pid=pppp
+1999-03-02 09:44:33 10HmbB-0005vi-00 <= CALLER@the.local.host.name U=CALLER P=local S=sss for c@test.ex
+1999-03-02 09:44:33 Start queue run: pid=pppp
+1999-03-02 09:44:33 10HmbB-0005vi-00 => c@test.ex R=client T=smtp H=127.0.0.1 [127.0.0.1] X=TLSv1:ke-RSA-AES256-SHA:xxx CV=no L C="250 OK id=10HmbC-0005vi-00"
+1999-03-02 09:44:33 10HmbB-0005vi-00 Completed
+1999-03-02 09:44:33 End queue run: pid=pppp
+
+******** SERVER ********
+1999-03-02 09:44:33 exim x.yz daemon started: pid=pppp, no queue runs, listening for SMTPS on port 1225
+1999-03-02 09:44:33 10HmaY-0005vi-00 <= CALLER@the.local.host.name H=localhost (the.local.host.name) [127.0.0.1] P=esmtps L. X=TLSv1:ke-RSA-AES256-SHA:xxx CV=no S=sss id=E10HmaX-0005vi-00@the.local.host.name for a@test.ex
+1999-03-02 09:44:33 Start queue run: pid=pppp
+1999-03-02 09:44:33 10HmaY-0005vi-00 => :blackhole: <a@test.ex> R=server
+1999-03-02 09:44:33 10HmaY-0005vi-00 Completed
+1999-03-02 09:44:33 End queue run: pid=pppp
+1999-03-02 09:44:33 10HmbA-0005vi-00 <= CALLER@the.local.host.name H=localhost (the.local.host.name) [127.0.0.1] P=esmtps L* X=TLSv1:ke-RSA-AES256-SHA:xxx CV=no S=sss id=E10HmaZ-0005vi-00@the.local.host.name for b@test.ex
+1999-03-02 09:44:33 Start queue run: pid=pppp
+1999-03-02 09:44:33 10HmbA-0005vi-00 => :blackhole: <b@test.ex> R=server
+1999-03-02 09:44:33 10HmbA-0005vi-00 Completed
+1999-03-02 09:44:33 End queue run: pid=pppp
+1999-03-02 09:44:33 10HmbC-0005vi-00 <= CALLER@the.local.host.name H=localhost (the.local.host.name) [127.0.0.1] P=esmtps L. X=TLSv1:ke-RSA-AES256-SHA:xxx CV=no S=sss id=E10HmbB-0005vi-00@the.local.host.name for c@test.ex
+1999-03-02 09:44:33 Start queue run: pid=pppp
+1999-03-02 09:44:33 10HmbC-0005vi-00 => :blackhole: <c@test.ex> R=server
+1999-03-02 09:44:33 10HmbC-0005vi-00 Completed
+1999-03-02 09:44:33 End queue run: pid=pppp
diff --git a/test/log/4064 b/test/log/4064
new file mode 100644
index 000000000..0d89b0ebb
--- /dev/null
+++ b/test/log/4064
@@ -0,0 +1,48 @@
+1999-03-02 09:44:33 10HmaX-0005vi-00 <= CALLER@the.local.host.name U=CALLER P=local S=sss for nocache@test.ex
+1999-03-02 09:44:33 Start queue run: pid=pppp
+1999-03-02 09:44:33 10HmaX-0005vi-00 => nocache@test.ex R=client T=smtp H=127.0.0.1 [127.0.0.1] X=TLS1.x:ke_RSA_AES_256_CBC_SHAnnn:256 CV=no A=plain L C="250 OK id=10HmaY-0005vi-00"
+1999-03-02 09:44:33 10HmaX-0005vi-00 Completed
+1999-03-02 09:44:33 End queue run: pid=pppp
+1999-03-02 09:44:33 10HmaZ-0005vi-00 <= CALLER@the.local.host.name U=CALLER P=local S=sss for hascache@test.ex
+1999-03-02 09:44:33 Start queue run: pid=pppp
+1999-03-02 09:44:33 10HmaZ-0005vi-00 => hascache@test.ex R=client T=smtp H=127.0.0.1 [127.0.0.1] X=TLS1.x:ke_RSA_AES_256_CBC_SHAnnn:256 CV=no A=plain L* C="250 OK id=10HmbA-0005vi-00"
+1999-03-02 09:44:33 10HmaZ-0005vi-00 Completed
+1999-03-02 09:44:33 End queue run: pid=pppp
+1999-03-02 09:44:33 10HmbB-0005vi-00 <= CALLER@the.local.host.name U=CALLER P=local S=sss for nocache@test.ex
+1999-03-02 09:44:33 Start queue run: pid=pppp
+1999-03-02 09:44:33 10HmbB-0005vi-00 => nocache@test.ex R=client T=smtp H=127.0.0.1 [127.0.0.1] X=TLS1.x:ke_RSA_AES_256_CBC_SHAnnn:256 CV=no A=plain L K C="250- 3nn byte chunk, total 3nn\\n250 OK id=10HmbC-0005vi-00"
+1999-03-02 09:44:33 10HmbB-0005vi-00 Completed
+1999-03-02 09:44:33 End queue run: pid=pppp
+1999-03-02 09:44:33 10HmbD-0005vi-00 <= CALLER@the.local.host.name U=CALLER P=local S=sss for chunking@test.ex
+1999-03-02 09:44:33 Start queue run: pid=pppp
+1999-03-02 09:44:33 10HmbD-0005vi-00 => chunking@test.ex R=client T=smtp H=127.0.0.1 [127.0.0.1] X=TLS1.x:ke_RSA_AES_256_CBC_SHAnnn:256 CV=no A=plain L* K C="250- 3nn byte chunk, total 3nn\\n250 OK id=10HmbE-0005vi-00"
+1999-03-02 09:44:33 10HmbD-0005vi-00 Completed
+1999-03-02 09:44:33 End queue run: pid=pppp
+
+******** SERVER ********
+1999-03-02 09:44:33 exim x.yz daemon started: pid=pppp, no queue runs, listening for SMTP on port 1225
+1999-03-02 09:44:33 no IP address found for host OPT (during SMTP connection from localhost (the.local.host.name) [127.0.0.1])
+1999-03-02 09:44:33 no IP address found for host OPT (during SMTP connection from localhost (the.local.host.name) [127.0.0.1])
+1999-03-02 09:44:33 10HmaY-0005vi-00 <= CALLER@the.local.host.name H=localhost (the.local.host.name) [127.0.0.1] P=esmtpsa L. X=TLS1.x:ke_RSA_AES_256_CBC_SHAnnn:256 CV=no A=plain:userx S=sss id=E10HmaX-0005vi-00@the.local.host.name for nocache@test.ex
+1999-03-02 09:44:33 Start queue run: pid=pppp
+1999-03-02 09:44:33 10HmaY-0005vi-00 => :blackhole: <nocache@test.ex> R=server
+1999-03-02 09:44:33 10HmaY-0005vi-00 Completed
+1999-03-02 09:44:33 End queue run: pid=pppp
+1999-03-02 09:44:33 no IP address found for host OPT (during SMTP connection from localhost (the.local.host.name) [127.0.0.1])
+1999-03-02 09:44:33 no IP address found for host OPT (during SMTP connection from localhost (the.local.host.name) [127.0.0.1])
+1999-03-02 09:44:33 10HmbA-0005vi-00 <= CALLER@the.local.host.name H=localhost (the.local.host.name) [127.0.0.1] P=esmtpsa L* X=TLS1.x:ke_RSA_AES_256_CBC_SHAnnn:256 CV=no A=plain:userx S=sss id=E10HmaZ-0005vi-00@the.local.host.name for hascache@test.ex
+1999-03-02 09:44:33 Start queue run: pid=pppp
+1999-03-02 09:44:33 10HmbA-0005vi-00 => :blackhole: <hascache@test.ex> R=server
+1999-03-02 09:44:33 10HmbA-0005vi-00 Completed
+1999-03-02 09:44:33 End queue run: pid=pppp
+1999-03-02 09:44:33 exim x.yz daemon started: pid=pppp, no queue runs, listening for SMTP on port 1225
+1999-03-02 09:44:33 10HmbC-0005vi-00 <= CALLER@the.local.host.name H=localhost (the.local.host.name) [127.0.0.1] P=esmtpsa L. X=TLS1.x:ke_RSA_AES_256_CBC_SHAnnn:256 CV=no A=plain:userx K S=sss id=E10HmbB-0005vi-00@the.local.host.name for nocache@test.ex
+1999-03-02 09:44:33 Start queue run: pid=pppp
+1999-03-02 09:44:33 10HmbC-0005vi-00 => :blackhole: <nocache@test.ex> R=server
+1999-03-02 09:44:33 10HmbC-0005vi-00 Completed
+1999-03-02 09:44:33 End queue run: pid=pppp
+1999-03-02 09:44:33 10HmbE-0005vi-00 <= CALLER@the.local.host.name H=localhost (the.local.host.name) [127.0.0.1] P=esmtpsa L* X=TLS1.x:ke_RSA_AES_256_CBC_SHAnnn:256 CV=no A=plain:userx K S=sss id=E10HmbD-0005vi-00@the.local.host.name for chunking@test.ex
+1999-03-02 09:44:33 Start queue run: pid=pppp
+1999-03-02 09:44:33 10HmbE-0005vi-00 => :blackhole: <chunking@test.ex> R=server
+1999-03-02 09:44:33 10HmbE-0005vi-00 Completed
+1999-03-02 09:44:33 End queue run: pid=pppp
diff --git a/test/log/4065 b/test/log/4065
new file mode 100644
index 000000000..dd3534270
--- /dev/null
+++ b/test/log/4065
@@ -0,0 +1,23 @@
+1999-03-02 09:44:33 10HmaX-0005vi-00 <= CALLER@the.local.host.name U=CALLER P=local S=sss for nocache@test.ex
+1999-03-02 09:44:33 Start queue run: pid=pppp
+1999-03-02 09:44:33 10HmaX-0005vi-00 => nocache@test.ex R=client T=smtp H=127.0.0.1 [127.0.0.1] X=TLS1.x:ke_RSA_AES_256_CBC_SHAnnn:256 CV=no A=plain L C="250 OK id=10HmaY-0005vi-00"
+1999-03-02 09:44:33 10HmaX-0005vi-00 Completed
+1999-03-02 09:44:33 End queue run: pid=pppp
+1999-03-02 09:44:33 10HmaZ-0005vi-00 <= CALLER@the.local.host.name U=CALLER P=local S=sss for hascache@test.ex
+1999-03-02 09:44:33 Start queue run: pid=pppp
+1999-03-02 09:44:33 10HmaZ-0005vi-00 => hascache@test.ex R=client T=smtp H=127.0.0.1 [127.0.0.1] X=TLS1.x:ke_RSA_AES_256_CBC_SHAnnn:256 CV=no A=plain L* C="250 OK id=10HmbA-0005vi-00"
+1999-03-02 09:44:33 10HmaZ-0005vi-00 Completed
+1999-03-02 09:44:33 End queue run: pid=pppp
+
+******** SERVER ********
+1999-03-02 09:44:33 exim x.yz daemon started: pid=pppp, no queue runs, listening for SMTPS on port 1225
+1999-03-02 09:44:33 10HmaY-0005vi-00 <= CALLER@the.local.host.name H=localhost (the.local.host.name) [127.0.0.1] P=esmtpsa L. X=TLS1.x:ke_RSA_AES_256_CBC_SHAnnn:256 CV=no A=plain:userx S=sss id=E10HmaX-0005vi-00@the.local.host.name for nocache@test.ex
+1999-03-02 09:44:33 Start queue run: pid=pppp
+1999-03-02 09:44:33 10HmaY-0005vi-00 => :blackhole: <nocache@test.ex> R=server
+1999-03-02 09:44:33 10HmaY-0005vi-00 Completed
+1999-03-02 09:44:33 End queue run: pid=pppp
+1999-03-02 09:44:33 10HmbA-0005vi-00 <= CALLER@the.local.host.name H=localhost (the.local.host.name) [127.0.0.1] P=esmtpsa L* X=TLS1.x:ke_RSA_AES_256_CBC_SHAnnn:256 CV=no A=plain:userx S=sss id=E10HmaZ-0005vi-00@the.local.host.name for hascache@test.ex
+1999-03-02 09:44:33 Start queue run: pid=pppp
+1999-03-02 09:44:33 10HmbA-0005vi-00 => :blackhole: <hascache@test.ex> R=server
+1999-03-02 09:44:33 10HmbA-0005vi-00 Completed
+1999-03-02 09:44:33 End queue run: pid=pppp
diff --git a/test/log/4066 b/test/log/4066
new file mode 100644
index 000000000..8daec2d56
--- /dev/null
+++ b/test/log/4066
@@ -0,0 +1,48 @@
+1999-03-02 09:44:33 10HmaX-0005vi-00 <= CALLER@the.local.host.name U=CALLER P=local S=sss for nocache@test.ex
+1999-03-02 09:44:33 Start queue run: pid=pppp
+1999-03-02 09:44:33 10HmaX-0005vi-00 => nocache@test.ex R=client T=smtp H=127.0.0.1 [127.0.0.1] X=TLSv1:ke-RSA-AES256-SHA:xxx CV=no A=plain L C="250 OK id=10HmaY-0005vi-00"
+1999-03-02 09:44:33 10HmaX-0005vi-00 Completed
+1999-03-02 09:44:33 End queue run: pid=pppp
+1999-03-02 09:44:33 10HmaZ-0005vi-00 <= CALLER@the.local.host.name U=CALLER P=local S=sss for hascache@test.ex
+1999-03-02 09:44:33 Start queue run: pid=pppp
+1999-03-02 09:44:33 10HmaZ-0005vi-00 => hascache@test.ex R=client T=smtp H=127.0.0.1 [127.0.0.1] X=TLSv1:ke-RSA-AES256-SHA:xxx CV=no A=plain L* C="250 OK id=10HmbA-0005vi-00"
+1999-03-02 09:44:33 10HmaZ-0005vi-00 Completed
+1999-03-02 09:44:33 End queue run: pid=pppp
+1999-03-02 09:44:33 10HmbB-0005vi-00 <= CALLER@the.local.host.name U=CALLER P=local S=sss for nocache@test.ex
+1999-03-02 09:44:33 Start queue run: pid=pppp
+1999-03-02 09:44:33 10HmbB-0005vi-00 => nocache@test.ex R=client T=smtp H=127.0.0.1 [127.0.0.1] X=TLSv1:ke-RSA-AES256-SHA:xxx CV=no A=plain L K C="250- 3nn byte chunk, total 3nn\\n250 OK id=10HmbC-0005vi-00"
+1999-03-02 09:44:33 10HmbB-0005vi-00 Completed
+1999-03-02 09:44:33 End queue run: pid=pppp
+1999-03-02 09:44:33 10HmbD-0005vi-00 <= CALLER@the.local.host.name U=CALLER P=local S=sss for chunking@test.ex
+1999-03-02 09:44:33 Start queue run: pid=pppp
+1999-03-02 09:44:33 10HmbD-0005vi-00 => chunking@test.ex R=client T=smtp H=127.0.0.1 [127.0.0.1] X=TLSv1:ke-RSA-AES256-SHA:xxx CV=no A=plain L* K C="250- 3nn byte chunk, total 3nn\\n250 OK id=10HmbE-0005vi-00"
+1999-03-02 09:44:33 10HmbD-0005vi-00 Completed
+1999-03-02 09:44:33 End queue run: pid=pppp
+
+******** SERVER ********
+1999-03-02 09:44:33 exim x.yz daemon started: pid=pppp, no queue runs, listening for SMTP on port 1225
+1999-03-02 09:44:33 no IP address found for host OPT (during SMTP connection from localhost (the.local.host.name) [127.0.0.1])
+1999-03-02 09:44:33 no IP address found for host OPT (during SMTP connection from localhost (the.local.host.name) [127.0.0.1])
+1999-03-02 09:44:33 10HmaY-0005vi-00 <= CALLER@the.local.host.name H=localhost (the.local.host.name) [127.0.0.1] P=esmtpsa L. X=TLSv1:ke-RSA-AES256-SHA:xxx CV=no A=plain:userx S=sss id=E10HmaX-0005vi-00@the.local.host.name for nocache@test.ex
+1999-03-02 09:44:33 Start queue run: pid=pppp
+1999-03-02 09:44:33 10HmaY-0005vi-00 => :blackhole: <nocache@test.ex> R=server
+1999-03-02 09:44:33 10HmaY-0005vi-00 Completed
+1999-03-02 09:44:33 End queue run: pid=pppp
+1999-03-02 09:44:33 no IP address found for host OPT (during SMTP connection from localhost (the.local.host.name) [127.0.0.1])
+1999-03-02 09:44:33 no IP address found for host OPT (during SMTP connection from localhost (the.local.host.name) [127.0.0.1])
+1999-03-02 09:44:33 10HmbA-0005vi-00 <= CALLER@the.local.host.name H=localhost (the.local.host.name) [127.0.0.1] P=esmtpsa L* X=TLSv1:ke-RSA-AES256-SHA:xxx CV=no A=plain:userx S=sss id=E10HmaZ-0005vi-00@the.local.host.name for hascache@test.ex
+1999-03-02 09:44:33 Start queue run: pid=pppp
+1999-03-02 09:44:33 10HmbA-0005vi-00 => :blackhole: <hascache@test.ex> R=server
+1999-03-02 09:44:33 10HmbA-0005vi-00 Completed
+1999-03-02 09:44:33 End queue run: pid=pppp
+1999-03-02 09:44:33 exim x.yz daemon started: pid=pppp, no queue runs, listening for SMTP on port 1225
+1999-03-02 09:44:33 10HmbC-0005vi-00 <= CALLER@the.local.host.name H=localhost (the.local.host.name) [127.0.0.1] P=esmtpsa L. X=TLSv1:ke-RSA-AES256-SHA:xxx CV=no A=plain:userx K S=sss id=E10HmbB-0005vi-00@the.local.host.name for nocache@test.ex
+1999-03-02 09:44:33 Start queue run: pid=pppp
+1999-03-02 09:44:33 10HmbC-0005vi-00 => :blackhole: <nocache@test.ex> R=server
+1999-03-02 09:44:33 10HmbC-0005vi-00 Completed
+1999-03-02 09:44:33 End queue run: pid=pppp
+1999-03-02 09:44:33 10HmbE-0005vi-00 <= CALLER@the.local.host.name H=localhost (the.local.host.name) [127.0.0.1] P=esmtpsa L* X=TLSv1:ke-RSA-AES256-SHA:xxx CV=no A=plain:userx K S=sss id=E10HmbD-0005vi-00@the.local.host.name for chunking@test.ex
+1999-03-02 09:44:33 Start queue run: pid=pppp
+1999-03-02 09:44:33 10HmbE-0005vi-00 => :blackhole: <chunking@test.ex> R=server
+1999-03-02 09:44:33 10HmbE-0005vi-00 Completed
+1999-03-02 09:44:33 End queue run: pid=pppp
diff --git a/test/log/4067 b/test/log/4067
new file mode 100644
index 000000000..11d59e3d8
--- /dev/null
+++ b/test/log/4067
@@ -0,0 +1,23 @@
+1999-03-02 09:44:33 10HmaX-0005vi-00 <= CALLER@the.local.host.name U=CALLER P=local S=sss for nocache@test.ex
+1999-03-02 09:44:33 Start queue run: pid=pppp
+1999-03-02 09:44:33 10HmaX-0005vi-00 => nocache@test.ex R=client T=smtp H=127.0.0.1 [127.0.0.1] X=TLSv1:ke-RSA-AES256-SHA:xxx CV=no A=plain L C="250 OK id=10HmaY-0005vi-00"
+1999-03-02 09:44:33 10HmaX-0005vi-00 Completed
+1999-03-02 09:44:33 End queue run: pid=pppp
+1999-03-02 09:44:33 10HmaZ-0005vi-00 <= CALLER@the.local.host.name U=CALLER P=local S=sss for hascache@test.ex
+1999-03-02 09:44:33 Start queue run: pid=pppp
+1999-03-02 09:44:33 10HmaZ-0005vi-00 => hascache@test.ex R=client T=smtp H=127.0.0.1 [127.0.0.1] X=TLSv1:ke-RSA-AES256-SHA:xxx CV=no A=plain L* C="250 OK id=10HmbA-0005vi-00"
+1999-03-02 09:44:33 10HmaZ-0005vi-00 Completed
+1999-03-02 09:44:33 End queue run: pid=pppp
+
+******** SERVER ********
+1999-03-02 09:44:33 exim x.yz daemon started: pid=pppp, no queue runs, listening for SMTPS on port 1225
+1999-03-02 09:44:33 10HmaY-0005vi-00 <= CALLER@the.local.host.name H=localhost (the.local.host.name) [127.0.0.1] P=esmtpsa L. X=TLSv1:ke-RSA-AES256-SHA:xxx CV=no A=plain:userx S=sss id=E10HmaX-0005vi-00@the.local.host.name for nocache@test.ex
+1999-03-02 09:44:33 Start queue run: pid=pppp
+1999-03-02 09:44:33 10HmaY-0005vi-00 => :blackhole: <nocache@test.ex> R=server
+1999-03-02 09:44:33 10HmaY-0005vi-00 Completed
+1999-03-02 09:44:33 End queue run: pid=pppp
+1999-03-02 09:44:33 10HmbA-0005vi-00 <= CALLER@the.local.host.name H=localhost (the.local.host.name) [127.0.0.1] P=esmtpsa L* X=TLSv1:ke-RSA-AES256-SHA:xxx CV=no A=plain:userx S=sss id=E10HmaZ-0005vi-00@the.local.host.name for hascache@test.ex
+1999-03-02 09:44:33 Start queue run: pid=pppp
+1999-03-02 09:44:33 10HmbA-0005vi-00 => :blackhole: <hascache@test.ex> R=server
+1999-03-02 09:44:33 10HmbA-0005vi-00 Completed
+1999-03-02 09:44:33 End queue run: pid=pppp
diff --git a/test/runtest b/test/runtest
index 3814261f3..76e1177c5 100755
--- a/test/runtest
+++ b/test/runtest
@@ -1119,6 +1119,9 @@ RESET_AFTER_EXTRA_LINE_READ:
# TCP Fast Open
next if /^(ppppp )?setsockopt FASTOPEN: Network Error/;
+ # Experimental_PIPE_CONNECT
+ next if / in (pipelining_connect_advertise_hosts|hosts_pipe_connect)?\? no /;
+
# Environment cleaning
next if /\w+ in keep_environment\? (yes|no)/;
@@ -1628,9 +1631,11 @@ $munges =
{ 'stdout' => '/^(
dkim_(canon|domain|private_key|selector|sign_headers|strict|hash|identity|timestamps)
|gnutls_require_(kx|mac|protocols)
+ |hosts_pipe_connect
|hosts_(requ(est|ire)|try)_(dane|ocsp)
|dane_require_tls_ciphers
|hosts_(avoid|nopass|noproxy|require|verify_avoid)_tls
+ |pipelining_connect_advertise_hosts
|socks_proxy
|tls_[^ ]*
|utf8_downconvert
diff --git a/test/scripts/4050-pipe-conn/4050 b/test/scripts/4050-pipe-conn/4050
new file mode 100644
index 000000000..9dc04576f
--- /dev/null
+++ b/test/scripts/4050-pipe-conn/4050
@@ -0,0 +1,103 @@
+# pipelined connect, server
+
+# not advertised when not wanted
+exim -bd -DSERVER=server -oX PORT_D
+****
+#
+client 127.0.0.1 PORT_D
+??? 220
+EHLO testclient
+??? 250-
+??? 250-SIZE
+??? 250-8BITMIME
+??? 250-PIPELINING
+??? 250 HELP
+QUIT
+??? 221
+****
+killdaemon
+#
+# advertised when wanted
+exim -bd -DSERVER=server -DCONNECTCOND=127.0.0.1 -oX PORT_D
+****
+#
+client 127.0.0.1 PORT_D
+??? 220
+EHLO testclient
+??? 250-
+??? 250-SIZE
+??? 250-8BITMIME
+??? 250-PIPELINING
+??? 250-X_PIPE_CONNECT
+??? 250 HELP
+QUIT
+??? 221
+****
+killdaemon
+#
+# Client does not wait for banner
+exim -bd -DSERVER=server -DCONNECTCOND=127.0.0.1 -oX PORT_D
+****
+#
+client 127.0.0.1 PORT_D
+EHLO testclient
+??? 220
+??? 250-
+??? 250-SIZE
+??? 250-8BITMIME
+??? 250-PIPELINING
+??? 250-X_PIPE_CONNECT
+??? 250 HELP
+QUIT
+??? 221
+****
+# No banner wait; pipelined MAIL
+client 127.0.0.1 PORT_D
+EHLO testclient\r\nMAIL FROM:<a@test.ex>
+??? 220
+??? 250-
+??? 250-SIZE
+??? 250-8BITMIME
+??? 250-PIPELINING
+??? 250-X_PIPE_CONNECT
+??? 250 HELP
+??? 250
+QUIT
+??? 221
+****
+# No banner wait; pipelined MAIL,RCPT
+client 127.0.0.1 PORT_D
+EHLO testclient\r\nMAIL FROM:<a@test.ex>\r\nRCPT TO:<a@test.ex>
+??? 220
+??? 250-
+??? 250-SIZE
+??? 250-8BITMIME
+??? 250-PIPELINING
+??? 250-X_PIPE_CONNECT
+??? 250 HELP
+??? 250
+??? 250
+QUIT
+??? 221
+****
+# No banner wait; pipelined MAIL,RCPT,DATA
+client 127.0.0.1 PORT_D
+EHLO testclient\r\nMAIL FROM:<a@test.ex>\r\nRCPT TO:<a@test.ex>\r\nDATA
+??? 220
+??? 250-
+??? 250-SIZE
+??? 250-8BITMIME
+??? 250-PIPELINING
+??? 250-X_PIPE_CONNECT
+??? 250 HELP
+??? 250
+??? 250
+??? 354
+.
+??? 250
+QUIT
+??? 221
+****
+#
+killdaemon
+no_msglog_check
diff --git a/test/scripts/4050-pipe-conn/4051 b/test/scripts/4050-pipe-conn/4051
new file mode 100644
index 000000000..45e4b7410
--- /dev/null
+++ b/test/scripts/4050-pipe-conn/4051
@@ -0,0 +1,59 @@
+# pipelined-connect, client
+#
+# Not attempted when not advertised
+exim -bd -DSERVER=server -oX PORT_D
+****
+#
+exim notadvertised@test.ex
+Subject: test 1
+
+****
+exim -DCONTROL=127.0.0.1 -q
+****
+killdaemon
+exim -DNOTDAEMON -DSERVER=server -q
+****
+#
+#
+#
+# Not attempted without a cache entry
+exim -bd -DSERVER=server -DCONNECTCOND=* -oX PORT_D
+****
+#
+exim nocache@test.ex
+Subject test 2
+
+****
+exim -DCONTROL=127.0.0.1 -q
+****
+exim -DNOTDAEMON -DSERVER=server -q
+****
+#
+#
+#
+# Go for it. This one should do early-pipelinng.
+exim hascache@test.ex
+Subject test 3
+
+****
+exim -DCONTROL=127.0.0.1 -q
+****
+exim -DNOTDAEMON -DSERVER=server -q
+****
+#
+#
+# Check that client doesn't try when not told to, even now there is
+# a cache entry
+exim clientno@test.ex
+Subject test 4
+
+****
+exim -DCONTROL=: -q
+****
+exim -DNOTDAEMON -DSERVER=server -q
+****
+#
+#
+killdaemon
+#
+no_msglog_check
diff --git a/test/scripts/4050-pipe-conn/4052 b/test/scripts/4050-pipe-conn/4052
new file mode 100644
index 000000000..d41cf8123
--- /dev/null
+++ b/test/scripts/4050-pipe-conn/4052
@@ -0,0 +1,243 @@
+# pipelined-connect, cache invalidation
+#
+#
+# Not attempted without a cache entry
+exim -bd -DSERVER=server -DCONNECTCOND=* -oX PORT_D
+****
+#
+exim nocache@test.ex
+Subject test 1
+
+****
+exim -DCONTROL=127.0.0.1 -q
+****
+exim -DNOTDAEMON -DSERVER=server -q
+****
+#
+#
+#
+# Go for it. This one should do early-pipelinng.
+exim hascache@test.ex
+Subject test 2
+
+****
+exim -DCONTROL=127.0.0.1 -q
+****
+exim -DNOTDAEMON -DSERVER=server -q
+****
+#
+#
+# Check that client doesn't try when the cache entry is too old
+# It will however create a new entry
+sleep 2
+#
+exim client_old@test.ex
+Subject test 3
+
+****
+exim -DRETRY=1s -DCONTROL=127.0.0.1 -q
+****
+exim -DNOTDAEMON -DSERVER=server -q
+****
+#
+killdaemon
+#
+#
+#
+sudo rm DIR/spool/db/misc
+#
+# Check that the cache is invalidated on SMTP errors from the server for the banner
+#
+server PORT_D 3
+>220 banner
+<EHLO
+>250-esmtp-resp
+>250-PIPELINING
+>250-X_PIPE_CONNECT
+>250 ok
+<MAIL
+>250 mail-from accepted
+<RCPT
+>250 rcpt-to accepted
+<DATA
+>354 data go-ahead
+.
+>250 message accepted
+<QUIT
+>220 bye
+*eof
+>220 banner
+<EHLO
+>250-esmtp-resp
+>250-PIPELINING
+>250-X_PIPE_CONNECT
+>250 ok
+<MAIL
+>250 mail-from accepted
+<RCPT
+>250 rcpt-to accepted
+<DATA
+>354 data go-ahead
+.
+>250 message accepted
+<QUIT
+>220 bye
+*eof
+*sleep 2
+<EHLO
+<MAIL
+<RCPT
+<DATA
+>450 sorry no banner for you today
+>550 sync error
+>550 sync error
+>550 sync error
+>550 sync error
+<QUIT
+>>220 bye
+*eof
+****
+exim -DCONTROL=127.0.0.1 -odi nocache2@test.ex withcache2@test.ex badbanner@test.ex
+body 2
+****
+#
+# Check that the cache is not invalidated on SMTP errors from the server after early-pipelining
+#
+server PORT_D 6
+>220 banner
+<EHLO
+>250-esmtp-resp
+>250-PIPELINING
+>250-X_PIPE_CONNECT
+>250 ok
+<MAIL
+>250 mail-from accepted
+<RCPT
+>250 rcpt-to accepted
+<DATA
+>354 data go-ahead
+.
+>250 message accepted
+<QUIT
+>220 bye
+*eof
+>220 banner
+<EHLO
+>250-esmtp-resp
+>250-PIPELINING
+>250-X_PIPE_CONNECT
+>250 ok
+<MAIL
+>250 mail-from accepted
+<RCPT
+>250 rcpt-to accepted
+<DATA
+>354 data go-ahead
+.
+>250 message accepted
+<QUIT
+>220 bye
+*eof
+*sleep 2
+<EHLO
+<MAIL
+<RCPT
+<DATA
+>220 banner
+>250-esmtp-resp
+>250-PIPELINING
+>250-X_PIPE_CONNECT
+>250 ok
+>250 mail good
+>250 rcpt good
+>550 obscure data error
+<QUIT
+>>220 bye
+*eof
+*sleep 2
+<EHLO
+<MAIL
+<RCPT
+<DATA
+>220 banner
+>250-esmtp-resp
+>250-PIPELINING
+>250-X_PIPE_CONNECT
+>250 ok
+>250 mail good
+>550 rcpt refused
+>550 data cmd rejected - no valid recipient
+<QUIT
+>>220 bye
+*eof
+*sleep 2
+<EHLO
+<MAIL
+<RCPT
+<DATA
+>220 banner
+>250-esmtp-resp
+>250-PIPELINING
+>250-X_PIPE_CONNECT
+>250 ok
+>550 mail cmd refused
+>550 rcpt cmd rejected - no valid mail
+>550 data cmd rejected - no valid mail
+<QUIT
+>>220 bye
+*eof
+*sleep 2
+>220 banner
+<EHLO
+>250-esmtp-resp
+>250-PIPELINING
+>250-X_PIPE_CONNECT
+>250 ok
+<MAIL
+>250 mail-from accepted
+<RCPT
+>250 rcpt-to accepted
+<DATA
+>354 data go-ahead
+.
+>250 message accepted
+<QUIT
+>220 bye
+*eof
+****
+exim -DCONTROL=127.0.0.1 -odi nocache@test.ex withcache@test.ex baddata@test.ex badrcpt@test.ex badmail@test.ex withcache2@test.ex
+body
+****
+#
+#
+# Check that the cache is updated on a change of advertised extensions
+# The conn will be made pipelined anyway (because we don't find out early)
+# but it's the DB dump that matters. Unfortunately the dump doesn't show
+# us the (binary) record content, only the key. So you need to look at the
+# debug output after all.
+server PORT_D 1
+>220 banner
+<EHLO
+>250-esmtp-resp
+>250-PIPELINING
+>250-X_PIPE_CONNECT
+>250-STARTTLS
+>250 ok
+<MAIL
+>250 mail-from accepted
+<RCPT
+>250 rcpt-to accepted
+<DATA
+>354 data go-ahead
+.
+>250 message accepted
+<QUIT
+>220 bye
+*eof
+****
+exim -d-all+transport -DCONTROL=127.0.0.1 -odi extchange@test.ex
+body
+****
+dump misc
+#
+no_msglog_check
diff --git a/test/scripts/4050-pipe-conn/4053 b/test/scripts/4050-pipe-conn/4053
new file mode 100644
index 000000000..eea284f6e
--- /dev/null
+++ b/test/scripts/4050-pipe-conn/4053
@@ -0,0 +1,31 @@
+# CHUNKING
+#
+# Not attempted without a cache entry
+exim -bd -DSERVER=server -oX PORT_D
+****
+#
+exim a@test.ex
+Subject test 1
+
+****
+exim -q
+****
+exim -DNOTDAEMON -DSERVER=server -q
+****
+#
+#
+#
+# Go for it. This one should do early-pipelinng.
+exim b@test.ex
+Subject test 2
+
+****
+exim -q
+****
+exim -DNOTDAEMON -DSERVER=server -q
+****
+#
+#
+killdaemon
+#
+no_msglog_check
diff --git a/test/scripts/4050-pipe-conn/REQUIRES b/test/scripts/4050-pipe-conn/REQUIRES
new file mode 100644
index 000000000..fd2535fa5
--- /dev/null
+++ b/test/scripts/4050-pipe-conn/REQUIRES
@@ -0,0 +1 @@
+support Experimental_PIPE_CONNECT
diff --git a/test/scripts/4056-pipe-conn-auth/4056 b/test/scripts/4056-pipe-conn-auth/4056
new file mode 100644
index 000000000..403cf621c
--- /dev/null
+++ b/test/scripts/4056-pipe-conn-auth/4056
@@ -0,0 +1,31 @@
+# AUTH
+#
+# Not attempted without a cache entry
+exim -bd -DSERVER=server -oX PORT_D
+****
+#
+exim a@test.ex
+Subject test 1
+
+****
+exim -q
+****
+exim -DNOTDAEMON -DSERVER=server -q
+****
+#
+#
+#
+# Go for it. This one should do (limited) early-pipelinng.
+exim b@test.ex
+Subject test 2
+
+****
+exim -q
+****
+exim -DNOTDAEMON -DSERVER=server -q
+****
+#
+#
+killdaemon
+#
+no_msglog_check
diff --git a/test/scripts/4056-pipe-conn-auth/REQUIRES b/test/scripts/4056-pipe-conn-auth/REQUIRES
new file mode 100644
index 000000000..9ae94aaaf
--- /dev/null
+++ b/test/scripts/4056-pipe-conn-auth/REQUIRES
@@ -0,0 +1,2 @@
+support Experimental_PIPE_CONNECT
+authenticator plaintext
diff --git a/test/scripts/4058-pipe-conn-tfo/4058 b/test/scripts/4058-pipe-conn-tfo/4058
new file mode 100644
index 000000000..3a388a2d9
--- /dev/null
+++ b/test/scripts/4058-pipe-conn-tfo/4058
@@ -0,0 +1,78 @@
+# Early-pipelining, with TFO
+#
+#
+# Packet delays so we can see TFO operational in packet captures
+sudo perl
+system ("tc qdisc add dev lo root netem delay 50ms");
+****
+#
+# Wipe any stored TFO cookie, to start from known state.
+sudo perl
+system ("ip tcp_metrics delete 127.0.0.1");
+****
+#
+#
+exim -bd -DSERVER=server -oX PORT_D
+****
+#
+# No early-pipe cache yet. Run one through to prime it;
+# this should not use early-pipe despite both the client & server being enabled.
+# Should also prime the TFO cookie cache.
+exim nocache@test.ex
+Subject: tfo test
+
+Testing
+****
+exim -q
+****
+exim -DNOTDAEMON -DSERVER=server -q
+****
+#
+#
+#
+# Having primed the cache, this one should use it.
+exim hascache@test.ex
+Subject: tfo test 2
+
+Testing
+****
+exim -q
+****
+exim -DNOTDAEMON -DSERVER=server -q
+****
+#
+#
+killdaemon
+#
+# Again, with CHUNKING
+sudo rm DIR/spool/db/misc
+exim -bd -DSERVER=server -DOPT=* -oX PORT_D
+****
+#
+exim nocache@test.ex
+Subject: tfo test 3
+
+Testing
+****
+exim -q
+****
+exim -DNOTDAEMON -DSERVER=server -q
+****
+exim chunking@test.ex
+Subject: tfo test 4
+
+Testing
+****
+exim -q
+****
+exim -DNOTDAEMON -DSERVER=server -q
+****
+#
+#
+killdaemon
+#
+sudo perl
+system ("tc qdisc delete dev lo root");
+****
+#
+no_msglog_check
diff --git a/test/scripts/4058-pipe-conn-tfo/REQUIRES b/test/scripts/4058-pipe-conn-tfo/REQUIRES
new file mode 100644
index 000000000..c18c49b2e
--- /dev/null
+++ b/test/scripts/4058-pipe-conn-tfo/REQUIRES
@@ -0,0 +1,2 @@
+support Experimental_PIPE_CONNECT
+support TCP_Fast_Open
diff --git a/test/scripts/4060-pipe-conn-gnutls/4060 b/test/scripts/4060-pipe-conn-gnutls/4060
new file mode 100644
index 000000000..b07c5c07d
--- /dev/null
+++ b/test/scripts/4060-pipe-conn-gnutls/4060
@@ -0,0 +1,71 @@
+# starttls
+#
+# Not attempted without a cache entry
+exim -bd -DSERVER=server -oX PORT_D
+****
+#
+exim nocache@test.ex
+Subject test 1
+
+****
+exim -q
+****
+exim -DNOTDAEMON -DSERVER=server -q
+****
+#
+#
+#
+# Go for it. This one should do early-pipelinng.
+exim hascache@test.ex
+Subject test 2
+
+****
+exim -q
+****
+exim -DNOTDAEMON -DSERVER=server -q
+****
+#
+#
+# Check that client doesn't try when not told to, even now there is
+# a cache entry
+exim clientno@test.ex
+Subject test 3
+
+****
+exim -DCONTROL=: -q
+****
+exim -DNOTDAEMON -DSERVER=server -q
+****
+#
+#
+killdaemon
+#
+#
+# And again with CHUNKING
+sudo rm DIR/spool/db/misc
+exim -bd -DSERVER=server -DOPT=* -oX PORT_D
+****
+#
+exim nocache@test.ex
+Subject test 4
+
+****
+exim -q
+****
+exim -DNOTDAEMON -DSERVER=server -q
+****
+#
+#
+#
+# Go for it
+exim chunking@test.ex
+Subject test 5
+
+****
+exim -q
+****
+exim -DNOTDAEMON -DSERVER=server -q
+****
+#
+killdaemon
+no_msglog_check
diff --git a/test/scripts/4060-pipe-conn-gnutls/4061 b/test/scripts/4060-pipe-conn-gnutls/4061
new file mode 100644
index 000000000..e4922b86b
--- /dev/null
+++ b/test/scripts/4060-pipe-conn-gnutls/4061
@@ -0,0 +1,43 @@
+# tls-on-connect
+#
+# Not attempted without a cache entry
+exim -bd -DSERVER=server -oX PORT_D
+****
+#
+exim nocache@test.ex
+Subject test 1
+
+****
+exim -q
+****
+exim -DNOTDAEMON -DSERVER=server -q
+****
+#
+#
+#
+# Go for it. This one should do early-pipelinng.
+exim hascache@test.ex
+Subject test 2
+
+****
+exim -q
+****
+exim -DNOTDAEMON -DSERVER=server -q
+****
+#
+#
+# Check that client doesn't try when not told to, even now there is
+# a cache entry
+exim clientno@test.ex
+Subject test 3
+
+****
+exim -DCONTROL=: -q
+****
+exim -DNOTDAEMON -DSERVER=server -q
+****
+#
+#
+killdaemon
+#
+no_msglog_check
diff --git a/test/scripts/4060-pipe-conn-gnutls/REQUIRES b/test/scripts/4060-pipe-conn-gnutls/REQUIRES
new file mode 100644
index 000000000..36c96e737
--- /dev/null
+++ b/test/scripts/4060-pipe-conn-gnutls/REQUIRES
@@ -0,0 +1,2 @@
+support Experimental_PIPE_CONNECT
+support GnuTLS
diff --git a/test/scripts/4062-pipe-conn-openssl/4062 b/test/scripts/4062-pipe-conn-openssl/4062
new file mode 100644
index 000000000..87b156c6c
--- /dev/null
+++ b/test/scripts/4062-pipe-conn-openssl/4062
@@ -0,0 +1,72 @@
+# starttls
+#
+# Not attempted without a cache entry
+exim -bd -DSERVER=server -oX PORT_D
+****
+#
+exim a@test.ex
+Subject test 1
+
+****
+exim -q
+****
+exim -DNOTDAEMON -DSERVER=server -q
+****
+#
+#
+#
+# Go for it. This one should do early-pipelinng.
+exim b@test.ex
+Subject test 2
+
+****
+exim -q
+****
+exim -DNOTDAEMON -DSERVER=server -q
+****
+#
+#
+# Check that client doesn't try when not told to, even now there is
+# a cache entry
+exim c@test.ex
+Subject test 3
+
+****
+exim -DCONTROL=: -q
+****
+exim -DNOTDAEMON -DSERVER=server -q
+****
+#
+#
+killdaemon
+#
+#
+# And again with CHUNKING
+sudo rm DIR/spool/db/misc
+exim -bd -DSERVER=server -DOPT=* -oX PORT_D
+****
+#
+exim nocache@test.ex
+Subject test 4
+
+****
+exim -q
+****
+exim -DNOTDAEMON -DSERVER=server -q
+****
+#
+#
+#
+# Go for it
+exim chunking@test.ex
+Subject test 5
+
+****
+exim -q
+****
+exim -DNOTDAEMON -DSERVER=server -q
+****
+#
+killdaemon
+#
+no_msglog_check
diff --git a/test/scripts/4062-pipe-conn-openssl/4063 b/test/scripts/4062-pipe-conn-openssl/4063
new file mode 100644
index 000000000..cdf2d96a1
--- /dev/null
+++ b/test/scripts/4062-pipe-conn-openssl/4063
@@ -0,0 +1,43 @@
+# tls-on-connect
+#
+# Not attempted without a cache entry
+exim -bd -DSERVER=server -oX PORT_D
+****
+#
+exim a@test.ex
+Subject test 1
+
+****
+exim -q
+****
+exim -DNOTDAEMON -DSERVER=server -q
+****
+#
+#
+#
+# Go for it. This one should do early-pipelinng.
+exim b@test.ex
+Subject test 2
+
+****
+exim -q
+****
+exim -DNOTDAEMON -DSERVER=server -q
+****
+#
+#
+# Check that client doesn't try when not told to, even now there is
+# a cache entry
+exim c@test.ex
+Subject test 3
+
+****
+exim -DCONTROL=: -q
+****
+exim -DNOTDAEMON -DSERVER=server -q
+****
+#
+#
+killdaemon
+#
+no_msglog_check
diff --git a/test/scripts/4062-pipe-conn-openssl/REQUIRES b/test/scripts/4062-pipe-conn-openssl/REQUIRES
new file mode 100644
index 000000000..3863ae742
--- /dev/null
+++ b/test/scripts/4062-pipe-conn-openssl/REQUIRES
@@ -0,0 +1,2 @@
+support Experimental_PIPE_CONNECT
+support OpenSSL
diff --git a/test/scripts/4064-pipe-conn-gnutls-auth/4064 b/test/scripts/4064-pipe-conn-gnutls-auth/4064
new file mode 100644
index 000000000..62be1e291
--- /dev/null
+++ b/test/scripts/4064-pipe-conn-gnutls-auth/4064
@@ -0,0 +1,60 @@
+# starttls
+#
+# Not attempted without a cache entry
+exim -bd -DSERVER=server -oX PORT_D
+****
+#
+exim nocache@test.ex
+Subject test 1
+
+****
+exim -q
+****
+exim -DNOTDAEMON -DSERVER=server -q
+****
+#
+#
+#
+# Go for it. This one should do early-pipelinng.
+exim hascache@test.ex
+Subject test 2
+
+****
+exim -q
+****
+exim -DNOTDAEMON -DSERVER=server -q
+****
+#
+#
+killdaemon
+#
+#
+#
+# And again with CHUNKING
+sudo rm DIR/spool/db/misc
+exim -bd -DSERVER=server -DOPT=* -oX PORT_D
+****
+#
+exim nocache@test.ex
+Subject test 4
+
+****
+exim -q
+****
+exim -DNOTDAEMON -DSERVER=server -q
+****
+#
+#
+#
+# Go for it
+exim chunking@test.ex
+Subject test 5
+
+****
+exim -q
+****
+exim -DNOTDAEMON -DSERVER=server -q
+****
+#
+killdaemon
+no_msglog_check
diff --git a/test/scripts/4064-pipe-conn-gnutls-auth/4065 b/test/scripts/4064-pipe-conn-gnutls-auth/4065
new file mode 100644
index 000000000..819e4295a
--- /dev/null
+++ b/test/scripts/4064-pipe-conn-gnutls-auth/4065
@@ -0,0 +1,32 @@
+# tls-on-connect
+#
+# Not attempted without a cache entry
+exim -bd -DSERVER=server -oX PORT_D
+****
+#
+exim nocache@test.ex
+Subject test 1
+
+****
+exim -q
+****
+exim -DNOTDAEMON -DSERVER=server -q
+****
+#
+#
+#
+# Go for it. This one should do early-pipelinng.
+exim hascache@test.ex
+Subject test 2
+
+****
+exim -q
+****
+exim -DNOTDAEMON -DSERVER=server -q
+****
+#
+#
+#
+killdaemon
+#
+no_msglog_check
diff --git a/test/scripts/4064-pipe-conn-gnutls-auth/REQUIRES b/test/scripts/4064-pipe-conn-gnutls-auth/REQUIRES
new file mode 100644
index 000000000..fafecf0fe
--- /dev/null
+++ b/test/scripts/4064-pipe-conn-gnutls-auth/REQUIRES
@@ -0,0 +1,3 @@
+support Experimental_PIPE_CONNECT
+support GnuTLS
+authenticator plaintext
diff --git a/test/scripts/4066-pipe-conn-openssl-auth/4066 b/test/scripts/4066-pipe-conn-openssl-auth/4066
new file mode 100644
index 000000000..3dc16bbe1
--- /dev/null
+++ b/test/scripts/4066-pipe-conn-openssl-auth/4066
@@ -0,0 +1,61 @@
+# starttls
+#
+# Not attempted without a cache entry
+exim -bd -DSERVER=server -oX PORT_D
+****
+#
+exim nocache@test.ex
+Subject test 1
+
+****
+exim -q
+****
+exim -DNOTDAEMON -DSERVER=server -q
+****
+#
+#
+#
+# Go for it. This one should do early-pipelinng.
+exim hascache@test.ex
+Subject test 2
+
+****
+exim -q
+****
+exim -DNOTDAEMON -DSERVER=server -q
+****
+#
+#
+#
+killdaemon
+#
+#
+#
+# And again with CHUNKING
+sudo rm DIR/spool/db/misc
+exim -bd -DSERVER=server -DOPT=* -oX PORT_D
+****
+#
+exim nocache@test.ex
+Subject test 4
+
+****
+exim -q
+****
+exim -DNOTDAEMON -DSERVER=server -q
+****
+#
+#
+#
+# Go for it
+exim chunking@test.ex
+Subject test 5
+
+****
+exim -q
+****
+exim -DNOTDAEMON -DSERVER=server -q
+****
+#
+killdaemon
+no_msglog_check
diff --git a/test/scripts/4066-pipe-conn-openssl-auth/4067 b/test/scripts/4066-pipe-conn-openssl-auth/4067
new file mode 100644
index 000000000..819e4295a
--- /dev/null
+++ b/test/scripts/4066-pipe-conn-openssl-auth/4067
@@ -0,0 +1,32 @@
+# tls-on-connect
+#
+# Not attempted without a cache entry
+exim -bd -DSERVER=server -oX PORT_D
+****
+#
+exim nocache@test.ex
+Subject test 1
+
+****
+exim -q
+****
+exim -DNOTDAEMON -DSERVER=server -q
+****
+#
+#
+#
+# Go for it. This one should do early-pipelinng.
+exim hascache@test.ex
+Subject test 2
+
+****
+exim -q
+****
+exim -DNOTDAEMON -DSERVER=server -q
+****
+#
+#
+#
+killdaemon
+#
+no_msglog_check
diff --git a/test/scripts/4066-pipe-conn-openssl-auth/REQUIRES b/test/scripts/4066-pipe-conn-openssl-auth/REQUIRES
new file mode 100644
index 000000000..e58e0f91a
--- /dev/null
+++ b/test/scripts/4066-pipe-conn-openssl-auth/REQUIRES
@@ -0,0 +1,3 @@
+support Experimental_PIPE_CONNECT
+support OpenSSL
+authenticator plaintext
diff --git a/test/stderr/2013 b/test/stderr/2013
index d050ebd30..fb8bd4008 100644
--- a/test/stderr/2013
+++ b/test/stderr/2013
@@ -68,12 +68,12 @@ cmd buf flush ddd bytes
SMTP>> QUIT
cmd buf flush ddd bytes
SMTP(close)>>
+>>>>>>>>>>>>>>>> Exim pid=pppp (TLS proxy) terminating with rc=0 >>>>>>>>>>>>>>>>
LOG: MAIN
=> usery@test.ex R=client T=send_to_server H=127.0.0.1 [127.0.0.1]* X=TLS1.x:ke_RSA_AES_256_CBC_SHAnnn:256 CV=no C="250 OK id=10HmbC-0005vi-00"
LOG: MAIN
Completed
>>>>>>>>>>>>>>>> Exim pid=pppp (main) terminating with rc=0 >>>>>>>>>>>>>>>>
->>>>>>>>>>>>>>>> Exim pid=pppp (TLS proxy) terminating with rc=0 >>>>>>>>>>>>>>>>
LOG: queue_run MAIN
End queue run: pid=pppp -qqf
>>>>>>>>>>>>>>>> Exim pid=pppp (main) terminating with rc=0 >>>>>>>>>>>>>>>>
@@ -147,12 +147,12 @@ cmd buf flush ddd bytes
SMTP>> QUIT
cmd buf flush ddd bytes
SMTP(close)>>
+>>>>>>>>>>>>>>>> Exim pid=pppp (TLS proxy) terminating with rc=0 >>>>>>>>>>>>>>>>
LOG: MAIN
=> userb@test.ex R=cl_override T=send_to_server H=127.0.0.1 [127.0.0.1]* X=TLS1.x:ke_RSA_AES_256_CBC_SHAnnn:256 CV=no C="250 OK id=10HmbI-0005vi-00"
LOG: MAIN
Completed
>>>>>>>>>>>>>>>> Exim pid=pppp (main) terminating with rc=0 >>>>>>>>>>>>>>>>
->>>>>>>>>>>>>>>> Exim pid=pppp (TLS proxy) terminating with rc=0 >>>>>>>>>>>>>>>>
LOG: queue_run MAIN
End queue run: pid=pppp -qqf
>>>>>>>>>>>>>>>> Exim pid=pppp (main) terminating with rc=0 >>>>>>>>>>>>>>>>
diff --git a/test/stderr/2035 b/test/stderr/2035
index 4f72f35c2..c1fb86309 100644
--- a/test/stderr/2035
+++ b/test/stderr/2035
@@ -68,12 +68,12 @@ ok=1 send_quit=1 send_rset=0 continue_more=0 yield=0 first_address is NULL
SMTP>> QUIT
cmd buf flush ddd bytes
SMTP(close)>>
+>>>>>>>>>>>>>>>> Exim pid=pppp (TLS proxy) terminating with rc=0 >>>>>>>>>>>>>>>>
Leaving t1 transport
LOG: MAIN
=> userb@test.ex R=client T=t1 H=127.0.0.1 [127.0.0.1]:1225 X=TLS1.x:ke_RSA_AES_256_CBC_SHAnnn:256 CV=no C="250 OK id=10HmaY-0005vi-00"
LOG: MAIN
Completed
>>>>>>>>>>>>>>>> Exim pid=pppp (main) terminating with rc=0 >>>>>>>>>>>>>>>>
->>>>>>>>>>>>>>>> Exim pid=pppp (TLS proxy) terminating with rc=0 >>>>>>>>>>>>>>>>
******** SERVER ********
diff --git a/test/stderr/2113 b/test/stderr/2113
index 026cd28b6..8ea042eaa 100644
--- a/test/stderr/2113
+++ b/test/stderr/2113
@@ -68,12 +68,12 @@ cmd buf flush ddd bytes
SMTP>> QUIT
cmd buf flush ddd bytes
SMTP(close)>>
+>>>>>>>>>>>>>>>> Exim pid=pppp (TLS proxy) terminating with rc=0 >>>>>>>>>>>>>>>>
LOG: MAIN
=> usery@test.ex R=client T=send_to_server H=127.0.0.1 [127.0.0.1]* X=TLSv1:ke-RSA-AES256-SHA:xxx CV=no C="250 OK id=10HmbC-0005vi-00"
LOG: MAIN
Completed
>>>>>>>>>>>>>>>> Exim pid=pppp (main) terminating with rc=0 >>>>>>>>>>>>>>>>
->>>>>>>>>>>>>>>> Exim pid=pppp (TLS proxy) terminating with rc=0 >>>>>>>>>>>>>>>>
LOG: queue_run MAIN
End queue run: pid=pppp -qqf
>>>>>>>>>>>>>>>> Exim pid=pppp (main) terminating with rc=0 >>>>>>>>>>>>>>>>
@@ -147,12 +147,12 @@ cmd buf flush ddd bytes
SMTP>> QUIT
cmd buf flush ddd bytes
SMTP(close)>>
+>>>>>>>>>>>>>>>> Exim pid=pppp (TLS proxy) terminating with rc=0 >>>>>>>>>>>>>>>>
LOG: MAIN
=> userb@test.ex R=cl_override T=send_to_server H=127.0.0.1 [127.0.0.1]* X=TLSv1:ke-RSA-AES256-SHA:xxx CV=no C="250 OK id=10HmbI-0005vi-00"
LOG: MAIN
Completed
>>>>>>>>>>>>>>>> Exim pid=pppp (main) terminating with rc=0 >>>>>>>>>>>>>>>>
->>>>>>>>>>>>>>>> Exim pid=pppp (TLS proxy) terminating with rc=0 >>>>>>>>>>>>>>>>
LOG: queue_run MAIN
End queue run: pid=pppp -qqf
>>>>>>>>>>>>>>>> Exim pid=pppp (main) terminating with rc=0 >>>>>>>>>>>>>>>>
diff --git a/test/stderr/2135 b/test/stderr/2135
index db08f08a3..1e323ed1e 100644
--- a/test/stderr/2135
+++ b/test/stderr/2135
@@ -68,12 +68,12 @@ ok=1 send_quit=1 send_rset=0 continue_more=0 yield=0 first_address is NULL
SMTP>> QUIT
cmd buf flush ddd bytes
SMTP(close)>>
+>>>>>>>>>>>>>>>> Exim pid=pppp (TLS proxy) terminating with rc=0 >>>>>>>>>>>>>>>>
Leaving t1 transport
LOG: MAIN
=> userb@test.ex R=client T=t1 H=127.0.0.1 [127.0.0.1]:1225 X=TLSv1:ke-RSA-AES256-SHA:xxx CV=no C="250 OK id=10HmaY-0005vi-00"
LOG: MAIN
Completed
>>>>>>>>>>>>>>>> Exim pid=pppp (main) terminating with rc=0 >>>>>>>>>>>>>>>>
->>>>>>>>>>>>>>>> Exim pid=pppp (TLS proxy) terminating with rc=0 >>>>>>>>>>>>>>>>
******** SERVER ********
diff --git a/test/stderr/4052 b/test/stderr/4052
new file mode 100644
index 000000000..35739f170
--- /dev/null
+++ b/test/stderr/4052
@@ -0,0 +1,67 @@
+Exim version x.yz ....
+configuration file is TESTSUITE/test-config
+admin user
+LOG: MAIN
+ <= CALLER@the.local.host.name U=CALLER P=local S=sss
+Exim version x.yz ....
+configuration file is TESTSUITE/test-config
+trusted user
+admin user
+>>>>>>>>>>>>>>>> Remote deliveries >>>>>>>>>>>>>>>>
+--------> extchange@test.ex <--------
+smtp transport entered
+ extchange@test.ex
+hostlist:
+ '127.0.0.1' IP 127.0.0.1 port 1225
+checking status of 127.0.0.1
+no host retry record
+no message retry record
+127.0.0.1 [127.0.0.1]:1111 retry-status = usable
+delivering 10HmbG-0005vi-00 to 127.0.0.1 [127.0.0.1] (extchange@test.ex)
+Transport port=25 replaced by host-specific port=1225
+EHLO response bits from cache: cleartext 0x0220 crypted 0x0000
+Using cached cleartext PIPE_CONNECT
+ SMTP>> EHLO the.local.host.name
+using PIPELINING
+not using DSN
+ SMTP>> MAIL FROM:<CALLER@the.local.host.name>
+ SMTP>> RCPT TO:<extchange@test.ex>
+ SMTP>> DATA
+cmd buf flush ddd bytes
+Connecting to 127.0.0.1 [127.0.0.1]:1225 ... sending 87 nonTFO early-data
+connected
+smtp_reap_early_pipe expect banner
+ SMTP<< 220 banner
+smtp_reap_early_pipe expect ehlo
+ SMTP<< 250-esmtp-resp
+ 250-PIPELINING
+ 250-X_PIPE_CONNECT
+ 250-STARTTLS
+ 250 ok
+EHLO extensions changed, 0x0220/0x0000 -> 0x0221/0x0000
+sync_responses expect mail
+ SMTP<< 250 mail-from accepted
+sync_responses expect rcpt
+ SMTP<< 250 rcpt-to accepted
+sync_responses expect data
+ SMTP<< 354 data go-ahead
+ SMTP>> writing message and terminating "."
+cannot use sendfile for body: spoolfile not wireformat
+writing data block fd=dddd size=sss timeout=300
+ SMTP<< 250 message accepted
+ok=1 send_quit=1 send_rset=0 continue_more=0 yield=0 first_address is NULL
+transport_check_waiting entered
+ sequence=1 local_max=500 global_max=-1
+no messages waiting for 127.0.0.1
+ SMTP>> QUIT
+cmd buf flush ddd bytes
+ SMTP(close)>>
+Leaving smtp transport
+LOG: MAIN
+ => extchange@test.ex R=client T=smtp H=127.0.0.1 [127.0.0.1] L* C="250 message accepted"
+LOG: MAIN
+ Completed
+>>>>>>>>>>>>>>>> Exim pid=pppp (main) terminating with rc=0 >>>>>>>>>>>>>>>>
+>>>>>>>>>>>>>>>> Exim pid=pppp (main) terminating with rc=0 >>>>>>>>>>>>>>>>
+
+******** SERVER ********
diff --git a/test/stdout/4050 b/test/stdout/4050
new file mode 100644
index 000000000..783dfd6a2
--- /dev/null
+++ b/test/stdout/4050
@@ -0,0 +1,133 @@
+Connecting to 127.0.0.1 port 1225 ... connected
+??? 220
+<<< 220 the.local.host.name ESMTP Exim x.yz Tue, 2 Mar 1999 09:44:33 +0000
+>>> EHLO testclient
+??? 250-
+<<< 250-the.local.host.name Hello testclient [127.0.0.1]
+??? 250-SIZE
+<<< 250-SIZE 52428800
+??? 250-8BITMIME
+<<< 250-8BITMIME
+??? 250-PIPELINING
+<<< 250-PIPELINING
+??? 250 HELP
+<<< 250 HELP
+>>> QUIT
+??? 221
+<<< 221 the.local.host.name closing connection
+End of script
+Connecting to 127.0.0.1 port 1225 ... connected
+??? 220
+<<< 220 the.local.host.name ESMTP Exim x.yz Tue, 2 Mar 1999 09:44:33 +0000
+>>> EHLO testclient
+??? 250-
+<<< 250-the.local.host.name Hello testclient [127.0.0.1]
+??? 250-SIZE
+<<< 250-SIZE 52428800
+??? 250-8BITMIME
+<<< 250-8BITMIME
+??? 250-PIPELINING
+<<< 250-PIPELINING
+??? 250-X_PIPE_CONNECT
+<<< 250-X_PIPE_CONNECT
+??? 250 HELP
+<<< 250 HELP
+>>> QUIT
+??? 221
+<<< 221 the.local.host.name closing connection
+End of script
+Connecting to 127.0.0.1 port 1225 ... connected
+>>> EHLO testclient
+??? 220
+<<< 220 the.local.host.name ESMTP Exim x.yz Tue, 2 Mar 1999 09:44:33 +0000
+??? 250-
+<<< 250-the.local.host.name Hello testclient [127.0.0.1]
+??? 250-SIZE
+<<< 250-SIZE 52428800
+??? 250-8BITMIME
+<<< 250-8BITMIME
+??? 250-PIPELINING
+<<< 250-PIPELINING
+??? 250-X_PIPE_CONNECT
+<<< 250-X_PIPE_CONNECT
+??? 250 HELP
+<<< 250 HELP
+>>> QUIT
+??? 221
+<<< 221 the.local.host.name closing connection
+End of script
+Connecting to 127.0.0.1 port 1225 ... connected
+>>> EHLO testclient\r\nMAIL FROM:<a@test.ex>
+??? 220
+<<< 220 the.local.host.name ESMTP Exim x.yz Tue, 2 Mar 1999 09:44:33 +0000
+??? 250-
+<<< 250-the.local.host.name Hello testclient [127.0.0.1]
+??? 250-SIZE
+<<< 250-SIZE 52428800
+??? 250-8BITMIME
+<<< 250-8BITMIME
+??? 250-PIPELINING
+<<< 250-PIPELINING
+??? 250-X_PIPE_CONNECT
+<<< 250-X_PIPE_CONNECT
+??? 250 HELP
+<<< 250 HELP
+??? 250
+<<< 250 OK
+>>> QUIT
+??? 221
+<<< 221 the.local.host.name closing connection
+End of script
+Connecting to 127.0.0.1 port 1225 ... connected
+>>> EHLO testclient\r\nMAIL FROM:<a@test.ex>\r\nRCPT TO:<a@test.ex>
+??? 220
+<<< 220 the.local.host.name ESMTP Exim x.yz Tue, 2 Mar 1999 09:44:33 +0000
+??? 250-
+<<< 250-the.local.host.name Hello testclient [127.0.0.1]
+??? 250-SIZE
+<<< 250-SIZE 52428800
+??? 250-8BITMIME
+<<< 250-8BITMIME
+??? 250-PIPELINING
+<<< 250-PIPELINING
+??? 250-X_PIPE_CONNECT
+<<< 250-X_PIPE_CONNECT
+??? 250 HELP
+<<< 250 HELP
+??? 250
+<<< 250 OK
+??? 250
+<<< 250 Accepted
+>>> QUIT
+??? 221
+<<< 221 the.local.host.name closing connection
+End of script
+Connecting to 127.0.0.1 port 1225 ... connected
+>>> EHLO testclient\r\nMAIL FROM:<a@test.ex>\r\nRCPT TO:<a@test.ex>\r\nDATA
+??? 220
+<<< 220 the.local.host.name ESMTP Exim x.yz Tue, 2 Mar 1999 09:44:33 +0000
+??? 250-
+<<< 250-the.local.host.name Hello testclient [127.0.0.1]
+??? 250-SIZE
+<<< 250-SIZE 52428800
+??? 250-8BITMIME
+<<< 250-8BITMIME
+??? 250-PIPELINING
+<<< 250-PIPELINING
+??? 250-X_PIPE_CONNECT
+<<< 250-X_PIPE_CONNECT
+??? 250 HELP
+<<< 250 HELP
+??? 250
+<<< 250 OK
+??? 250
+<<< 250 Accepted
+??? 354
+<<< 354 Enter message, ending with "." on a line by itself
+>>> .
+??? 250
+<<< 250 OK id=10HmaX-0005vi-00
+>>> QUIT
+??? 221
+<<< 221 the.local.host.name closing connection
+End of script
diff --git a/test/stdout/4052 b/test/stdout/4052
new file mode 100644
index 000000000..0b38af6bb
--- /dev/null
+++ b/test/stdout/4052
@@ -0,0 +1,201 @@
++++++++++++++++++++++++++++
+07-Mar-2000 12:21:52 127.0.0.1:1225.EHLO
+
+******** SERVER ********
+Listening on port 1225 ...
+Connection request from [127.0.0.1]
+>220 banner
+<EHLO
+>250-esmtp-resp
+>250-PIPELINING
+>250-X_PIPE_CONNECT
+>250 ok
+<MAIL
+>250 mail-from accepted
+<RCPT
+>250 rcpt-to accepted
+<DATA
+>354 data go-ahead
+R
+>250 message accepted
+<QUIT
+>220 bye
+Expected EOF read from client
+Listening on port 1225 ...
+Connection request from [127.0.0.1]
+>220 banner
+<EHLO
+>250-esmtp-resp
+>250-PIPELINING
+>250-X_PIPE_CONNECT
+>250 ok
+<MAIL
+>250 mail-from accepted
+<RCPT
+>250 rcpt-to accepted
+<DATA
+>354 data go-ahead
+R
+>250 message accepted
+<QUIT
+>220 bye
+Expected EOF read from client
+Listening on port 1225 ...
+Connection request from [127.0.0.1]
+*sleep 2
+<EHLO
+<MAIL
+<RCPT
+<DATA
+>450 sorry no banner for you today
+>550 sync error
+>550 sync error
+>550 sync error
+>550 sync error
+<QUIT
+>>220 bye
+Expected EOF read from client
+End of script
+Listening on port 1225 ...
+Connection request from [127.0.0.1]
+>220 banner
+<EHLO
+>250-esmtp-resp
+>250-PIPELINING
+>250-X_PIPE_CONNECT
+>250 ok
+<MAIL
+>250 mail-from accepted
+<RCPT
+>250 rcpt-to accepted
+<DATA
+>354 data go-ahead
+R
+>250 message accepted
+<QUIT
+>220 bye
+Expected EOF read from client
+Listening on port 1225 ...
+Connection request from [127.0.0.1]
+>220 banner
+<EHLO
+>250-esmtp-resp
+>250-PIPELINING
+>250-X_PIPE_CONNECT
+>250 ok
+<MAIL
+>250 mail-from accepted
+<RCPT
+>250 rcpt-to accepted
+<DATA
+>354 data go-ahead
+R
+>250 message accepted
+<QUIT
+>220 bye
+Expected EOF read from client
+Listening on port 1225 ...
+Connection request from [127.0.0.1]
+*sleep 2
+<EHLO
+<MAIL
+<RCPT
+<DATA
+>220 banner
+>250-esmtp-resp
+>250-PIPELINING
+>250-X_PIPE_CONNECT
+>250 ok
+>250 mail good
+>250 rcpt good
+>550 obscure data error
+<QUIT
+>>220 bye
+Expected EOF read from client
+Listening on port 1225 ...
+Connection request from [127.0.0.1]
+*sleep 2
+<EHLO
+<MAIL
+<RCPT
+<DATA
+>220 banner
+>250-esmtp-resp
+>250-PIPELINING
+>250-X_PIPE_CONNECT
+>250 ok
+>250 mail good
+>550 rcpt refused
+>550 data cmd rejected - no valid recipient
+<QUIT
+>>220 bye
+Expected EOF read from client
+Listening on port 1225 ...
+Connection request from [127.0.0.1]
+*sleep 2
+<EHLO
+<MAIL
+<RCPT
+<DATA
+>220 banner
+>250-esmtp-resp
+>250-PIPELINING
+>250-X_PIPE_CONNECT
+>250 ok
+>550 mail cmd refused
+>550 rcpt cmd rejected - no valid mail
+>550 data cmd rejected - no valid mail
+<QUIT
+>>220 bye
+Expected EOF read from client
+Listening on port 1225 ...
+Connection request from [127.0.0.1]
+*sleep 2
+>220 banner
+<EHLO
+>250-esmtp-resp
+>250-PIPELINING
+>250-X_PIPE_CONNECT
+>250 ok
+<MAIL
+>250 mail-from accepted
+<RCPT
+>250 rcpt-to accepted
+<DATA
+>354 data go-ahead
+R
+>250 message accepted
+<QUIT
+>220 bye
+Expected EOF read from client
+End of script
+Listening on port 1225 ...
+Connection request from [127.0.0.1]
+>220 banner
+<EHLO the.local.host.name
+>250-esmtp-resp
+>250-PIPELINING
+>250-X_PIPE_CONNECT
+>250-STARTTLS
+>250 ok
+<MAIL FROM:<CALLER@the.local.host.name>
+>250 mail-from accepted
+<RCPT TO:<extchange@test.ex>
+>250 rcpt-to accepted
+<DATA
+>354 data go-ahead
+Received: from CALLER by the.local.host.name with local (Exim x.yz)
+ (envelope-from <CALLER@the.local.host.name>)
+ id 10HmbG-0005vi-00
+ for extchange@test.ex; Tue, 2 Mar 1999 09:44:33 +0000
+Message-Id: <E10HmbG-0005vi-00@the.local.host.name>
+From: CALLER_NAME <CALLER@the.local.host.name>
+Date: Tue, 2 Mar 1999 09:44:33 +0000
+
+body
+.
+>250 message accepted
+<QUIT
+>220 bye
+Expected EOF read from client
+End of script