[exim-cvs] Experimental: ESMTP LIMITS extension

Inizio della pagina
Delete this message
Reply to this message
Autore: Exim Git Commits Mailing List
Data:  
To: exim-cvs
Oggetto: [exim-cvs] Experimental: ESMTP LIMITS extension
Gitweb: https://git.exim.org/exim.git/commitdiff/a1108b5118d32e969c5fe91b2110944f7483a7cb
Commit:     a1108b5118d32e969c5fe91b2110944f7483a7cb
Parent:     4022c6568fa89eb47e0d78f86f7b7cfbcfefcdc4
Author:     Jeremy Harris <jgh146exb@???>
AuthorDate: Sun Apr 18 01:51:28 2021 +0100
Committer:  Jeremy Harris <jgh146exb@???>
CommitDate: Sun Apr 18 01:51:28 2021 +0100


    Experimental: ESMTP LIMITS extension
---
 doc/doc-docbook/spec.xfpt               |  12 +-
 doc/doc-txt/experimental-spec.txt       |  36 +++
 src/src/config.h.defaults               |   1 +
 src/src/dbfn.c                          |   2 +-
 src/src/dbstuff.h                       |  10 +-
 src/src/deliver.c                       |   3 +
 src/src/exim.c                          |  16 +-
 src/src/functions.h                     |   7 +-
 src/src/globals.c                       |  12 +
 src/src/globals.h                       |  12 +
 src/src/readconf.c                      |   3 +
 src/src/smtp_in.c                       |  13 +
 src/src/transport.c                     |  36 ++-
 src/src/transports/smtp.c               | 544 +++++++++++++++++++++++---------
 src/src/transports/smtp.h               |  17 +-
 test/aux-var-src/tls_conf_prefix        |   3 +
 test/confs/0453                         |   4 +-
 test/confs/0564                         |   3 +
 test/confs/0900                         |   4 +-
 test/confs/0901                         |   3 +
 test/confs/0906                         |   3 +
 test/confs/3414                         |   3 +
 test/confs/4050                         |   3 +
 test/confs/4710                         |  36 +++
 test/confs/4711                         |  31 ++
 test/confs/4712                         |  28 ++
 test/confs/4713                         |  31 ++
 test/confs/4714                         |  36 +++
 test/confs/5670                         |   8 +-
 test/log/4710                           |   6 +
 test/log/4711                           |  24 ++
 test/log/4712                           |   8 +
 test/log/4713                           |  11 +
 test/log/4714                           |  14 +
 test/runtest                            |   6 +
 test/scripts/0000-Basic/0453            |   2 +-
 test/scripts/4710-esmtp-limits/4710     |  86 +++++
 test/scripts/4710-esmtp-limits/4711     | 138 ++++++++
 test/scripts/4710-esmtp-limits/4712     |  65 ++++
 test/scripts/4710-esmtp-limits/4713     |  64 ++++
 test/scripts/4710-esmtp-limits/4714     |  84 +++++
 test/scripts/4710-esmtp-limits/REQUIRES |   1 +
 test/scripts/5670-OCSP-GnuTLS-1.3/5670  |   8 +-
 test/stdout/4710                        |  87 +++++
 test/stdout/4711                        | 183 +++++++++++
 test/stdout/4712                        |  89 ++++++
 test/stdout/4713                        |  86 +++++
 test/stdout/4714                        | 117 +++++++
 48 files changed, 1829 insertions(+), 170 deletions(-)


diff --git a/doc/doc-docbook/spec.xfpt b/doc/doc-docbook/spec.xfpt
index 36be62f..56da191 100644
--- a/doc/doc-docbook/spec.xfpt
+++ b/doc/doc-docbook/spec.xfpt
@@ -3940,6 +3940,16 @@ This option is not intended for use by external callers. It is used internally
by Exim in conjunction with the &%-MC%& option. It signifies that a
remote host supports the ESMTP &_CHUNKING_& extension.

+.new
+.vitem &%-MCL%&
+.oindex "&%-MCL%&"
+This option is not intended for use by external callers. It is used internally
+by Exim in conjunction with the &%-MC%& option. It signifies that the server to
+which Exim is connected advertised limits on numbers of mails, recipients or
+recipient domains.
+The limits are given by the following three arguments.
+.wen
+
.vitem &%-MCP%&
.oindex "&%-MCP%&"
This option is not intended for use by external callers. It is used internally
@@ -25725,7 +25735,7 @@ has advertised support for IGNOREQUOTA in its response to the LHLO command.
This option limits the number of RCPT commands that are sent in a single
SMTP message transaction. Each set of addresses is treated independently, and
so can cause parallel connections to the same host if &%remote_max_parallel%&
-permits this.
+permits this. A value setting of zero disables the limit.


.new
diff --git a/doc/doc-txt/experimental-spec.txt b/doc/doc-txt/experimental-spec.txt
index 47cd93f..5e0b044 100644
--- a/doc/doc-txt/experimental-spec.txt
+++ b/doc/doc-txt/experimental-spec.txt
@@ -680,6 +680,42 @@ to its QUIT, not properly closing the TLS session and not properly closing
the TCP connection. Previously this resulted is an error from SSL_write
being logged.

+
+
+Limits ESMTP extension
+---------------------------------------------------------------
+Per https://datatracker.ietf.org/doc/html/draft-freed-smtp-limits-01
+
+If compiled with EXPERIMENTAL_ESMTP_LIMITS=yes :-
+
+As a server, Exim will advertise, in the EHLO response, the limit for RCPT
+commands set by the recipients_max main-section config option (if it is set),
+and the limit for MAIL commands set by the smtp_accept_max_per_connection
+option.
+
+Note that as of writing, smtp_accept_max_per_connection is expanded but
+recipients_max is not.
+
+A new main-section option "limits_advertise_hosts" controls whether
+the limits are advertised; the default for the option is "*".
+
+As a client, Exim will:
+
+ - note an advertised MAILMAX; the lower of the value given and the
+ value from the transport connection_max_messages option is used.
+
+ - note an advertised RCPTMAX; the lower of the
+ value given and the value from the transport max_rcpt option is used.
+ Parallisation of transactions is not done if due to a RCPTMAX, unlike
+ max_rcpt.
+
+ - note an advertised RCPTDOMAINMAX, and behave as if the transport
+ multi_domains option was set to false. The value advertised is ignored.
+
+Values advertised are only noted for TLS connections and ones for which
+the server does not advertise TLS support.
+
+
--------------------------------------------------------------
End of file
--------------------------------------------------------------
diff --git a/src/src/config.h.defaults b/src/src/config.h.defaults
index 02031b7..e233fb3 100644
--- a/src/src/config.h.defaults
+++ b/src/src/config.h.defaults
@@ -205,6 +205,7 @@ Do not put spaces between # and the 'define'.
#define EXPERIMENTAL_BRIGHTMAIL
#define EXPERIMENTAL_DCC
#define EXPERIMENTAL_DSN_INFO
+#define EXPERIMENTAL_ESMTP_LIMITS
#define EXPERIMENTAL_QUEUEFILE
#define EXPERIMENTAL_SRS_ALT

diff --git a/src/src/dbfn.c b/src/src/dbfn.c
index be6a47a..0f56ad5 100644
--- a/src/src/dbfn.c
+++ b/src/src/dbfn.c
@@ -342,7 +342,7 @@ return yield;


/* Read a record. If the length is not as expected then delete it, write
-an error log line and return NULL.
+an error log line, delete the record and return NULL.
Use this for fixed-size records (so not retry or wait records).

Arguments:
diff --git a/src/src/dbstuff.h b/src/src/dbstuff.h
index 5a8441d..8993415 100644
--- a/src/src/dbstuff.h
+++ b/src/src/dbstuff.h
@@ -792,13 +792,21 @@ typedef struct {

#ifndef DISABLE_PIPE_CONNECT
/* This structure records the EHLO responses, cleartext and crypted,
-for an IP, as bitmasks (cf. OPTION_TLS) */
+for an IP, as bitmasks (cf. OPTION_TLS). For LIMITS, also values
+advertised for MAILMAX, RCPTMAX and RCPTDOMAINMAX; zero meaning no
+value advertised. */

typedef struct {
unsigned short cleartext_features;
unsigned short crypted_features;
unsigned short cleartext_auths;
unsigned short crypted_auths;
+
+# ifdef EXPERIMENTAL_ESMTP_LIMITS
+ unsigned int limit_mail;
+ unsigned int limit_rcpt;
+ unsigned int limit_rcptdom;
+# endif
} ehlo_resp_precis;

typedef struct {
diff --git a/src/src/deliver.c b/src/src/deliver.c
index 0cddec7..9ebd281 100644
--- a/src/src/deliver.c
+++ b/src/src/deliver.c
@@ -8570,6 +8570,9 @@ f.tcp_fastopen_ok = TRUE;



+/* Called from a commandline, or from the daemon, to do a delivery.
+We need to regain privs; do this by exec of the exim binary. */
+
 void
 delivery_re_exec(int exec_type)
 {
diff --git a/src/src/exim.c b/src/src/exim.c
index 1244aee..8526cbb 100644
--- a/src/src/exim.c
+++ b/src/src/exim.c
@@ -1010,6 +1010,9 @@ g = string_cat(NULL, US"Support for:");
 #ifdef EXPERIMENTAL_DSN_INFO
   g = string_cat(g, US" Experimental_DSN_info");
 #endif
+#ifdef EXPERIMENTAL_ESMTP_LIMITS
+  g = string_cat(g, US" Experimental_ESMTP_Limits");
+#endif
 #ifdef EXPERIMENTAL_QUEUEFILE
   g = string_cat(g, US" Experimental_QUEUEFILE");
 #endif
@@ -2257,7 +2260,7 @@ on the second character (the one after '-'), to save some effort. */
     case 'P':


       /* -bP config: we need to setup here, because later,
-       * when list_options is checked, the config is read already */
+      when list_options is checked, the config is read already */
       if (*argrest)
         badarg = TRUE;
       else if (argv[i+1] && Ustrcmp(argv[i+1], "config") == 0)
@@ -2788,6 +2791,17 @@ on the second character (the one after '-'), to save some effort. */


     case 'K': smtp_peer_options |= OPTION_CHUNKING; break;


+#ifdef EXPERIMENTAL_ESMTP_LIMITS
+    /* -MCL: peer used LIMITS RCPTMAX and/or RCPTDOMAINMAX */
+    case 'L': if (++i < argc) continue_limit_mail = Uatoi(argv[i]);
+          else badarg = TRUE;
+          if (++i < argc) continue_limit_rcpt = Uatoi(argv[i]);
+          else badarg = TRUE;
+          if (++i < argc) continue_limit_rcptdom = Uatoi(argv[i]);
+          else badarg = TRUE;
+          break;
+#endif
+
     /* -MCP: set the smtp_use_pipelining flag; this is useful only when
     it preceded -MC (see above) */


diff --git a/src/src/functions.h b/src/src/functions.h
index 0f962b3..7d6e338 100644
--- a/src/src/functions.h
+++ b/src/src/functions.h
@@ -593,8 +593,11 @@ extern BOOL    transport_check_waiting(const uschar *, const uschar *, int, usch
 extern void    transport_init(void);
 extern void    transport_do_pass_socket(const uschar *, const uschar *,
          const uschar *, uschar *, int);
-extern BOOL    transport_pass_socket(const uschar *, const uschar *, const uschar *, uschar *,
-                 int);
+extern BOOL    transport_pass_socket(const uschar *, const uschar *, const uschar *, uschar *, int
+#ifdef EXPERIMENTAL_ESMTP_LIMITS
+            , unsigned, unsigned, unsigned
+#endif
+            );
 extern uschar *transport_rcpt_address(address_item *, BOOL);
 extern BOOL    transport_set_up_command(const uschar ***, uschar *,
          BOOL, int, address_item *, uschar *, uschar **);
diff --git a/src/src/globals.c b/src/src/globals.c
index 7ee7c38..c45e8a9 100644
--- a/src/src/globals.c
+++ b/src/src/globals.c
@@ -710,6 +710,10 @@ unsigned chunking_data_left    = 0;
 chunking_state_t chunking_state= CHUNKING_NOT_OFFERED;
 const pcre *regex_CHUNKING     = NULL;


+#ifdef EXPERIMENTAL_ESMTP_LIMITS
+const pcre *regex_LIMITS        = NULL;
+#endif
+
 uschar *client_authenticator   = NULL;
 uschar *client_authenticated_id = NULL;
 uschar *client_authenticated_sender = NULL;
@@ -742,6 +746,11 @@ uschar *continue_hostname      = NULL;
 uschar *continue_host_address  = NULL;
 int     continue_sequence      = 1;
 uschar *continue_transport     = NULL;
+#ifdef EXPERIMENTAL_ESMTP_LIMITS
+unsigned continue_limit_mail   = 0;
+unsigned continue_limit_rcpt   = 0;
+unsigned continue_limit_rcptdom= 0;
+#endif


 uschar *csa_status             = NULL;
 cut_t   cutthrough = {
@@ -1001,6 +1010,9 @@ uschar *keep_environment       = NULL;
 int     keep_malformed         = 4*24*60*60;    /* 4 days */


 uschar *eldap_dn               = NULL;
+#ifdef EXPERIMENTAL_ESMTP_LIMITS
+uschar *limits_advertise_hosts = US"*";
+#endif
 int     load_average           = -2;
 uschar *local_from_prefix      = NULL;
 uschar *local_from_suffix      = NULL;
diff --git a/src/src/globals.h b/src/src/globals.h
index 58bf3c4..ed7cffb 100644
--- a/src/src/globals.h
+++ b/src/src/globals.h
@@ -434,6 +434,12 @@ extern uschar *continue_hostname;      /* Host for continued delivery */
 extern uschar *continue_host_address;  /* IP address for ditto */
 extern int     continue_sequence;      /* Sequence num for continued delivery */
 extern uschar *continue_transport;     /* Transport for continued delivery */
+#ifdef EXPERIMENTAL_ESMTP_LIMITS
+extern unsigned continue_limit_mail;   /* Peer advertised limit */
+extern unsigned continue_limit_rcpt;
+extern unsigned continue_limit_rcptdom;
+#endif
+


 extern uschar *csa_status;             /* Client SMTP Authorization result */


@@ -652,6 +658,9 @@ extern uschar *keep_environment;       /* Whitelist for environment variables */
 extern int     keep_malformed;         /* Time to keep malformed messages */


 extern uschar *eldap_dn;               /* Where LDAP DNs are left */
+#ifdef EXPERIMENTAL_ESMTP_LIMITS
+extern uschar *limits_advertise_hosts; /* for banner/EHLO pipelining */
+#endif
 extern int     load_average;           /* Most recently read load average */
 extern BOOL    local_from_check;       /* For adding Sender: (global value) */
 extern uschar *local_from_prefix;      /* Permitted prefixes */
@@ -854,6 +863,9 @@ extern const pcre  *regex_check_dns_names; /* For DNS name checking */
 extern const pcre  *regex_From;        /* For recognizing "From_" lines */
 extern const pcre  *regex_CHUNKING;    /* For recognizing CHUNKING (RFC 3030) */
 extern const pcre  *regex_IGNOREQUOTA; /* For recognizing IGNOREQUOTA (LMTP) */
+#ifdef EXPERIMENTAL_ESMTP_LIMITS
+extern const pcre  *regex_LIMITS; /* For recognizing LIMITS */
+#endif
 extern const pcre  *regex_PIPELINING;  /* For recognizing PIPELINING */
 extern const pcre  *regex_SIZE;        /* For recognizing SIZE settings */
 #ifndef DISABLE_PIPE_CONNECT
diff --git a/src/src/readconf.c b/src/src/readconf.c
index 0ae3166..e8e310b 100644
--- a/src/src/readconf.c
+++ b/src/src/readconf.c
@@ -201,6 +201,9 @@ static optionlist optionlist_config[] = {
   { "ldap_start_tls",           opt_bool,        {&eldap_start_tls} },
   { "ldap_version",             opt_int,         {&eldap_version} },
 #endif
+#ifdef EXPERIMENTAL_ESMTP_LIMITS
+  { "limits_advertise_hosts", opt_stringptr, {&limits_advertise_hosts} },
+#endif
   { "local_from_check",         opt_bool,        {&local_from_check} },
   { "local_from_prefix",        opt_stringptr,   {&local_from_prefix} },
   { "local_from_suffix",        opt_stringptr,   {&local_from_suffix} },
diff --git a/src/src/smtp_in.c b/src/src/smtp_in.c
index 2bb15b6..d60e7d5 100644
--- a/src/src/smtp_in.c
+++ b/src/src/smtp_in.c
@@ -4346,6 +4346,19 @@ while (done <= 0)
       g = string_catn(g, US"-SIZE\r\n", 7);
       }


+#ifdef EXPERIMENTAL_ESMTP_LIMITS
+    if (  (mailmax > 0 || recipients_max)
+       && verify_check_host(&limits_advertise_hosts) == OK)
+      {
+      g = string_fmt_append(g, "%.3s-LIMITS", smtp_code);
+      if (mailmax > 0)
+        g = string_fmt_append(g, " MAILMAX=%d", mailmax);
+      if (recipients_max)
+        g = string_fmt_append(g, " RCPTMAX=%d", recipients_max);
+      g = string_catn(g, US"\r\n", 2);
+      }
+#endif
+
     /* Exim does not do protocol conversion or data conversion. It is 8-bit
     clean; if it has an 8-bit character in its hand, it just sends it. It
     cannot therefore specify 8BITMIME and remain consistent with the RFCs.
diff --git a/src/src/transport.c b/src/src/transport.c
index 49a84cc..d8fd858 100644
--- a/src/src/transport.c
+++ b/src/src/transport.c
@@ -1880,9 +1880,21 @@ void
 transport_do_pass_socket(const uschar *transport_name, const uschar *hostname,
   const uschar *hostaddress, uschar *id, int socket_fd)
 {
-int i = 27;
+int i = 13;
 const uschar **argv;


+#ifndef DISABLE_TLS
+if (smtp_peer_options & OPTION_TLS) i += 6;
+#endif
+#ifdef EXPERIMENTAL_ESMTP_LIMITS
+if (continue_limit_mail || continue_limit_rcpt || continue_limit_rcptdom)
+                    i += 4;
+#endif
+if (queue_run_pid != (pid_t)0)        i += 3;
+#ifdef SUPPORT_SOCKS
+if (proxy_session)            i += 5;
+#endif
+
 /* Set up the calling arguments; use the standard function for the basics,
 but we have a number of extras that may be added. */


@@ -1916,6 +1928,16 @@ if (smtp_peer_options & OPTION_TLS)
     argv[i++] = US"-MCT";
 #endif


+#ifdef EXPERIMENTAL_ESMTP_LIMITS
+if (continue_limit_rcpt || continue_limit_rcptdom)
+  {
+  argv[i++] = US"-MCL";
+  argv[i++] = string_sprintf("%u", continue_limit_mail);
+  argv[i++] = string_sprintf("%u", continue_limit_rcpt);
+  argv[i++] = string_sprintf("%u", continue_limit_rcptdom);
+  }
+#endif
+
 if (queue_run_pid != (pid_t)0)
   {
   argv[i++] = US"-MCQ";
@@ -1976,13 +1998,23 @@ Returns:          FALSE if fork fails; TRUE otherwise


BOOL
transport_pass_socket(const uschar *transport_name, const uschar *hostname,
- const uschar *hostaddress, uschar *id, int socket_fd)
+ const uschar *hostaddress, uschar *id, int socket_fd
+#ifdef EXPERIMENTAL_ESMTP_LIMITS
+ , unsigned peer_limit_mail, unsigned peer_limit_rcpt, unsigned peer_limit_rcptdom
+#endif
+ )
{
pid_t pid;
int status;

DEBUG(D_transport) debug_printf("transport_pass_socket entered\n");

+#ifdef EXPERIMENTAL_ESMTP_LIMITS
+continue_limit_mail = peer_limit_mail;
+continue_limit_rcpt = peer_limit_rcpt;
+continue_limit_rcptdom = peer_limit_rcptdom;
+#endif
+
if ((pid = exim_fork(US"continued-transport-interproc")) == 0)
{
/* Disconnect entirely from the parent process. If we are running in the
diff --git a/src/src/transports/smtp.c b/src/src/transports/smtp.c
index 2a2928c..5294057 100644
--- a/src/src/transports/smtp.c
+++ b/src/src/transports/smtp.c
@@ -274,6 +274,11 @@ if (!regex_IGNOREQUOTA) regex_IGNOREQUOTA =
if (!regex_EARLY_PIPE) regex_EARLY_PIPE =
regex_must_compile(US"\\n250[\\s\\-]" EARLY_PIPE_FEATURE_NAME "(\\s|\\n|$)", FALSE, TRUE);
#endif
+
+#ifdef EXPERIMENTAL_ESMTP_LIMITS
+if (!regex_LIMITS) regex_LIMITS =
+ regex_must_compile(US"\\n250[\\s\\-]LIMITS\\s", FALSE, TRUE);
+#endif
}


@@ -756,6 +761,82 @@ return TRUE;
}


+/******************************************************************************/
+
+#ifdef EXPERIMENTAL_ESMTP_LIMITS
+/* If TLS, or TLS not offered, called with the EHLO response in the buffer.
+Check it for a LIMITS keyword and parse values into the smtp context structure.
+
+We don't bother with peers that we won't talk TLS to, even though they can,
+just ignore their LIMITS advice (if any) and treat them as if they do not.
+This saves us dealing with a duplicate set of values. */
+
+static void
+ehlo_response_limits_read(smtp_context * sx)
+{
+int ovec[3];    /* results vector for a main-match only */
+
+/* matches up to just after the first space after the keyword */
+
+if (pcre_exec(regex_LIMITS, NULL, CS sx->buffer, Ustrlen(sx->buffer),
+          0, PCRE_EOPT, ovec, nelem(ovec)) >= 0)
+  for (const uschar * s = sx->buffer + ovec[1]; *s; )
+    {
+    while (isspace(*s)) s++;
+    if (*s == '\n') break;
+
+    if (strncmpic(s, US"MAILMAX=", 8) == 0)
+      {
+      sx->peer_limit_mail = atoi(CS (s += 8));
+      while (isdigit(*s)) s++;
+      }
+    else if (strncmpic(s, US"RCPTMAX=", 8) == 0)
+      {
+      sx->peer_limit_rcpt = atoi(CS (s += 8));
+      while (isdigit(*s)) s++;
+      }
+    else if (strncmpic(s, US"RCPTDOMAINMAX=", 14) == 0)
+      {
+      sx->peer_limit_rcptdom = atoi(CS (s += 14));
+      while (isdigit(*s)) s++;
+      }
+    else
+      while (*s && !isspace(*s)) s++;
+    }
+}
+
+/* Apply given values to the current connection */
+static void
+ehlo_limits_apply(smtp_context * sx,
+  unsigned limit_mail, unsigned limit_rcpt, unsigned limit_rcptdom)
+{
+if (limit_mail && limit_mail < sx->max_mail) sx->max_mail = limit_mail;
+if (limit_rcpt && limit_rcpt < sx->max_rcpt) sx->max_rcpt = limit_rcpt;
+if (limit_rcptdom)
+  {
+  DEBUG(D_transport) debug_printf("will treat as !multi_domain\n");
+  sx->single_rcpt_domain = TRUE;
+  }
+}
+
+/* Apply values from EHLO-resp to the current connection */
+static void
+ehlo_response_limits_apply(smtp_context * sx)
+{
+ehlo_limits_apply(sx, sx->peer_limit_mail, sx->peer_limit_rcpt,
+  sx->peer_limit_rcptdom);
+}
+
+/* Apply values read from cache to the current connection */
+static void
+ehlo_cache_limits_apply(smtp_context * sx)
+{
+ehlo_limits_apply(sx, sx->ehlo_resp.limit_mail, sx->ehlo_resp.limit_rcpt,
+  sx->ehlo_resp.limit_rcptdom);
+}
+#endif
+
+/******************************************************************************/


 #ifndef DISABLE_PIPE_CONNECT
 static uschar *
@@ -769,19 +850,45 @@ return Ustrchr(host->address, ':')
     host->port == PORT_NONE ? sx->port : host->port);
 }


+/* Cache EHLO-response info for use by early-pipe.
+Called
+- During a normal flow on EHLO response (either cleartext or under TLS),
+ when we are willing to do PIPE_CONNECT and it is offered
+- During an early-pipe flow on receiving the actual EHLO response and noting
+ disparity versus the cached info used, when PIPE_CONNECT is still being offered
+
+We assume that suitable values have been set in the sx.ehlo_resp structure for
+features and auths; we handle the copy of limits. */
+
static void
-write_ehlo_cache_entry(const smtp_context * sx)
+write_ehlo_cache_entry(smtp_context * sx)
{
open_db dbblock, * dbm_file;

+#ifdef EXPERIMENTAL_ESMTP_LIMITS
+sx->ehlo_resp.limit_mail = sx->peer_limit_mail;
+sx->ehlo_resp.limit_rcpt = sx->peer_limit_rcpt;
+sx->ehlo_resp.limit_rcptdom = sx->peer_limit_rcptdom;
+#endif
+
if ((dbm_file = dbfn_open(US"misc", O_RDWR, &dbblock, TRUE, TRUE)))
{
uschar * ehlo_resp_key = ehlo_cache_key(sx);
dbdata_ehlo_resp er = { .data = sx->ehlo_resp };

-  HDEBUG(D_transport) debug_printf("writing clr %04x/%04x cry %04x/%04x\n",
-    sx->ehlo_resp.cleartext_features, sx->ehlo_resp.cleartext_auths,
-    sx->ehlo_resp.crypted_features, sx->ehlo_resp.crypted_auths);
+  HDEBUG(D_transport)
+#ifdef EXPERIMENTAL_ESMTP_LIMITS
+    if (sx->ehlo_resp.limit_mail || sx->ehlo_resp.limit_rcpt || sx->ehlo_resp.limit_rcptdom)
+      debug_printf("writing clr %04x/%04x cry %04x/%04x lim %05d/%05d/%05d\n",
+    sx->ehlo_resp.cleartext_features, sx->ehlo_resp.cleartext_auths,
+    sx->ehlo_resp.crypted_features, sx->ehlo_resp.crypted_auths,
+    sx->ehlo_resp.limit_mail, sx->ehlo_resp.limit_rcpt,
+    sx->ehlo_resp.limit_rcptdom);
+    else
+#endif
+      debug_printf("writing clr %04x/%04x cry %04x/%04x\n",
+    sx->ehlo_resp.cleartext_features, sx->ehlo_resp.cleartext_auths,
+    sx->ehlo_resp.crypted_features, sx->ehlo_resp.crypted_auths);


   dbfn_write(dbm_file, ehlo_resp_key, &er, (int)sizeof(er));
   dbfn_close(dbm_file);
@@ -826,12 +933,26 @@ else
     }
   else
     {
+    DEBUG(D_transport)
+#ifdef EXPERIMENTAL_ESMTP_LIMITS
+      if (er->data.limit_mail || er->data.limit_rcpt || er->data.limit_rcptdom)
+    debug_printf("EHLO response bits from cache:"
+      " cleartext 0x%04x/0x%04x crypted 0x%04x/0x%04x lim %05d/%05d/%05d\n",
+      er->data.cleartext_features, er->data.cleartext_auths,
+      er->data.crypted_features, er->data.crypted_auths,
+      er->data.limit_mail, er->data.limit_rcpt, er->data.limit_rcptdom);
+      else
+#endif
+    debug_printf("EHLO response bits from cache:"
+      " cleartext 0x%04x/0x%04x crypted 0x%04x/0x%04x\n",
+      er->data.cleartext_features, er->data.cleartext_auths,
+      er->data.crypted_features, er->data.crypted_auths);
+
     sx->ehlo_resp = er->data;
+#ifdef EXPERIMENTAL_ESMTP_LIMITS
+    ehlo_cache_limits_apply(sx);
+#endif
     dbfn_close(dbm_file);
-    DEBUG(D_transport) debug_printf(
-    "EHLO response bits from cache: cleartext 0x%04x/0x%04x crypted 0x%04x/0x%04x\n",
-    er->data.cleartext_features, er->data.cleartext_auths,
-    er->data.crypted_features, er->data.crypted_auths);
     return TRUE;
     }
   dbfn_close(dbm_file);
@@ -933,8 +1054,9 @@ if (pending_EHLO)
     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. */
+ /* Compare the actual EHLO response extensions and AUTH methods 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;
@@ -944,6 +1066,10 @@ if (pending_EHLO)
     | OPTION_CHUNKING | OPTION_PRDR | OPTION_DSN | OPTION_PIPE | OPTION_SIZE
     | OPTION_UTF8 | OPTION_EARLY_PIPE
     );
+#ifdef EXPERIMENTAL_ESMTP_LIMITS
+  if (tls_out.active.sock >= 0 || !(peer_offered & OPTION_TLS))
+    ehlo_response_limits_read(sx);
+#endif
   if (  peer_offered != sx->peer_offered
      || (authbits = study_ehlo_auths(sx)) != *ap)
     {
@@ -951,16 +1077,44 @@ if (pending_EHLO)
       debug_printf("EHLO %s extensions changed, 0x%04x/0x%04x -> 0x%04x/0x%04x\n",
             tls_out.active.sock < 0 ? "cleartext" : "crypted",
             sx->peer_offered, *ap, peer_offered, authbits);
-    *(tls_out.active.sock < 0
-      ? &sx->ehlo_resp.cleartext_features : &sx->ehlo_resp.crypted_features) = peer_offered;
-    *ap = authbits;
     if (peer_offered & OPTION_EARLY_PIPE)
+      {
+      *(tls_out.active.sock < 0
+    ? &sx->ehlo_resp.cleartext_features : &sx->ehlo_resp.crypted_features) =
+      peer_offered;
+      *ap = authbits;
       write_ehlo_cache_entry(sx);
+      }
     else
       invalidate_ehlo_cache_entry(sx);


     return OK;        /* just carry on */
     }
+#ifdef EXPERIMENTAL_ESMTP_LIMITS
+    /* If we are handling LIMITS, compare the actual EHLO LIMITS values with the
+    cached values and invalidate cache if different.  OK to carry on with
+    connect since values are advisory. */
+    {
+    if (  (tls_out.active.sock >= 0 || !(peer_offered & OPTION_TLS))
+       && (  sx->peer_limit_mail != sx->ehlo_resp.limit_mail
+          || sx->peer_limit_rcpt != sx->ehlo_resp.limit_rcpt
+          || sx->peer_limit_rcptdom != sx->ehlo_resp.limit_rcptdom
+       )  )
+      {
+      HDEBUG(D_transport)
+    {
+    debug_printf("EHLO LIMITS changed:");
+    if (sx->peer_limit_mail != sx->ehlo_resp.limit_mail)
+      debug_printf(" MAILMAX %u -> %u\n", sx->ehlo_resp.limit_mail, sx->peer_limit_mail);
+    else if (sx->peer_limit_rcpt != sx->ehlo_resp.limit_rcpt)
+      debug_printf(" RCPTMAX %u -> %u\n", sx->ehlo_resp.limit_rcpt, sx->peer_limit_rcpt);
+    else
+      debug_printf(" RCPTDOMAINMAX %u -> %u\n", sx->ehlo_resp.limit_rcptdom, sx->peer_limit_rcptdom);
+    }
+      invalidate_ehlo_cache_entry(sx);
+      }
+    }
+#endif
   }
 return OK;


@@ -1882,7 +2036,8 @@ sx->dane_required =
/* sx->pending_BANNER = sx->pending_EHLO = sx->pending_MAIL = FALSE; */
#endif

-if ((sx->max_rcpt = sx->conn_args.tblock->max_addresses) == 0) sx->max_rcpt = 999999;
+if ((sx->max_mail = sx->conn_args.tblock->connection_max_messages) == 0) sx->max_mail = 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"";
@@ -2083,6 +2238,9 @@ if (!continue_hostname)


sx->cctx.tls_ctx = NULL;
sx->inblock.cctx = sx->outblock.cctx = &sx->cctx;
+#ifdef EXPERIMENTAL_ESMTP_LIMITS
+ sx->peer_limit_mail = sx->peer_limit_rcpt = sx->peer_limit_rcptdom =
+#endif
sx->avoid_option = sx->peer_offered = smtp_peer_options = 0;

 #ifndef DISABLE_PIPE_CONNECT
@@ -2361,6 +2519,13 @@ goto SEND_QUIT;
       )
 #endif
     );
+#ifdef EXPERIMENTAL_ESMTP_LIMITS
+      if (tls_out.active.sock >= 0 || !(sx->peer_offered & OPTION_TLS))
+    {
+    ehlo_response_limits_read(sx);
+    ehlo_response_limits_apply(sx);
+    }
+#endif
 #ifndef DISABLE_PIPE_CONNECT
       if (sx->early_pipe_ok)
     {
@@ -2415,6 +2580,13 @@ else
   sx->inblock.cctx = sx->outblock.cctx = &sx->cctx;
   smtp_command = big_buffer;
   sx->peer_offered = smtp_peer_options;
+#ifdef EXPERIMENTAL_ESMTP_LIMITS
+  /* Limits passed by cmdline over exec. */
+  ehlo_limits_apply(sx,
+            sx->peer_limit_mail = continue_limit_mail,
+            sx->peer_limit_rcpt = continue_limit_rcpt,
+            sx->peer_limit_rcptdom = continue_limit_rcptdom);
+#endif
   sx->helo_data = NULL;        /* ensure we re-expand ob->helo_data */


/* For a continued connection with TLS being proxied for us, or a
@@ -2456,7 +2628,8 @@ if ( smtp_peer_options & OPTION_TLS

#ifndef DISABLE_PIPE_CONNECT
/* If doing early-pipelining reap the banner and EHLO-response but leave
- the response for the STARTTLS we just sent alone. */
+ the response for the STARTTLS we just sent alone. On fail, assume wrong
+ cached capability and retry with the pipelining disabled. */

   if (sx->early_pipe_active && sync_responses(sx, 2, 0) != 0)
     {
@@ -2592,6 +2765,13 @@ if (tls_out.active.sock >= 0)
       DEBUG(D_transport) debug_printf("Using cached crypted PIPE_CONNECT\n");
     }
 #endif
+#ifdef EXPERIMMENTAL_ESMTP_LIMITS
+  /* As we are about to send another EHLO, forget any LIMITS received so far. */
+  sx->peer_limit_mail = sx->peer_limit_rcpt = sx->peer_limit_rcptdom = 0;
+  if ((sx->max_mail = sx->conn_args.tblock->connection_max_message) == 0) sx->max_mail = 999999;
+  if ((sx->max_rcpt = sx->conn_args.tblock->max_addresses) == 0)          sx->max_rcpt = 999999;
+  sx->single_rcpt_domain = FALSE;
+#endif


   /* For SMTPS we need to wait for the initial OK response. */
   if (sx->smtps)
@@ -2721,6 +2901,14 @@ if (   !continue_hostname
     if (tls_out.active.sock >= 0)
       sx->ehlo_resp.crypted_features = sx->peer_offered;
 #endif
+
+#ifdef EXPERIMENTAL_ESMTP_LIMITS
+    if (tls_out.active.sock >= 0 || !(sx->peer_offered & OPTION_TLS))
+      {
+      ehlo_response_limits_read(sx);
+      ehlo_response_limits_apply(sx);
+      }
+#endif
     }


     /* Set for IGNOREQUOTA if the response to LHLO specifies support and the
@@ -3054,7 +3242,7 @@ if (  sx->peer_offered & OPTION_UTF8


 /* check if all addresses have DSN-lasthop flag; do not send RET and ENVID if so */
 for (sx->dsn_all_lasthop = TRUE, addr = addrlist, address_count = 0;
-     addr && address_count < sx->max_rcpt;
+     addr && address_count < sx->max_rcpt;    /*XXX maybe also || sx->single_rcpt_domain ? */
      addr = addr->next) if (addr->transport_return == PENDING_DEFER)
   {
   address_count++;
@@ -3142,6 +3330,9 @@ int
 smtp_write_mail_and_rcpt_cmds(smtp_context * sx, int * yield)
 {
 address_item * addr;
+#ifdef EXPERIMENTAL_ESMTP_LIMITS
+address_item * restart_addr = NULL;
+#endif
 int address_count, pipe_limit;
 int rc;


@@ -3233,6 +3424,24 @@ for (addr = sx->first_addr, address_count = 0, pipe_limit = 100;
BOOL no_flush;
uschar * rcpt_addr;

+#ifdef EXPERIMENTAL_ESMTP_LIMITS
+  if (  sx->single_rcpt_domain                    /* restriction on domains */
+     && address_count > 0                    /* not first being sent */
+     && Ustrcmp(addr->domain, sx->first_addr->domain) != 0    /* dom diff from first */
+     )
+    {
+    DEBUG(D_transport) debug_printf("skipping different domain %s\n", addr->domain);
+
+    /* Ensure the smtp-response reaper does not think the address had a RCPT
+    command sent for it.  Reset to PENDING_DEFER in smtp_deliver(), where we
+    goto SEND_MESSAGE.  */
+
+    addr->transport_return = SKIP;
+    if (!restart_addr) restart_addr = addr;    /* note restart point */
+    continue;                    /* skip this one */
+    }
+#endif
+
   addr->dsn_aware = sx->peer_offered & OPTION_DSN
     ? dsn_support_yes : dsn_support_no;


@@ -3304,7 +3513,11 @@ for (addr = sx->first_addr, address_count = 0, pipe_limit = 100;
     }
   }      /* Loop for next address */


+#ifdef EXPERIMENTAL_ESMTP_LIMITS
+sx->next_addr = restart_addr ? restart_addr : addr;
+#else
sx->next_addr = addr;
+#endif
return 0;
}

@@ -3493,10 +3706,13 @@ int yield = OK;
int save_errno;
int rc;

-BOOL pass_message = FALSE;
 uschar *message = NULL;
 uschar new_message_id[MESSAGE_ID_LENGTH + 1];
 smtp_context * sx = store_get(sizeof(*sx), TRUE);    /* tainted, for the data buffers */
+BOOL pass_message = FALSE;
+#ifdef EXPERIMENTAL_ESMTP_LIMITS
+BOOL mail_limit = FALSE;
+#endif
 #ifdef SUPPORT_DANE
 BOOL dane_held;
 #endif
@@ -3516,8 +3732,8 @@ sx->conn_args.tblock = tblock;
 gettimeofday(&sx->delivery_start, NULL);
 sx->sync_addr = sx->first_addr = addrlist;


+REPEAT_CONN:
#ifdef SUPPORT_DANE
-DANE_DOMAINS:
dane_held = FALSE;
#endif

@@ -4090,7 +4306,7 @@ else
         else
           sprintf(CS sx->buffer, "%.500s\n", addr->unique);


-        DEBUG(D_deliver) debug_printf("S:journalling %s\n", sx->buffer);
+        DEBUG(D_deliver) debug_printf("S:journalling %s", sx->buffer);
         len = Ustrlen(CS sx->buffer);
         if (write(journal_fd, sx->buffer, len) != len)
           log_write(0, LOG_MAIN|LOG_PANIC, "failed to write journal for "
@@ -4335,81 +4551,95 @@ DEBUG(D_transport)
     sx->send_rset, f.continue_more, yield, sx->first_addr ? "not " : "");


 if (sx->completed_addr && sx->ok && sx->send_quit)
-  {
-  smtp_compare_t t_compare =
-    {.tblock = tblock, .current_sender_address = sender_address};
+#ifdef EXPERIMENTAL_ESMTP_LIMITS
+  if (mail_limit = continue_sequence >= sx->max_mail)
+    {
+    DEBUG(D_transport)
+      debug_printf("reached limit %u for MAILs per conn\n", sx->max_mail);
+    }
+  else
+#endif
+    {
+    smtp_compare_t t_compare =
+      {.tblock = tblock, .current_sender_address = sender_address};


-  if (  sx->first_addr            /* more addrs for this message */
-     || f.continue_more            /* more addrs for continued-host */
-     || tcw_done && tcw            /* more messages for host */
-     || (
+    if (  sx->first_addr            /* more addrs for this message */
+       || f.continue_more            /* more addrs for continued-host */
+       || tcw_done && tcw            /* more messages for host */
+       || (
 #ifndef DISABLE_TLS
-       (  tls_out.active.sock < 0  &&  !continue_proxy_cipher
-           || verify_check_given_host(CUSS &ob->hosts_nopass_tls, host) != OK
-       )
-        &&
+         (  tls_out.active.sock < 0  &&  !continue_proxy_cipher
+         || verify_check_given_host(CUSS &ob->hosts_nopass_tls, host) != OK
+         )
+      &&
 #endif
-           transport_check_waiting(tblock->name, host->name,
-             tblock->connection_max_messages, new_message_id,
-         (oicf)smtp_are_same_identities, (void*)&t_compare)
-     )  )
-    {
-    uschar *msg;
-    BOOL pass_message;
+         transport_check_waiting(tblock->name, host->name,
+           sx->max_mail, new_message_id,
+           (oicf)smtp_are_same_identities, (void*)&t_compare)
+       )  )
+      {
+      uschar *msg;
+      BOOL pass_message;


-    if (sx->send_rset)
-      if (! (sx->ok = smtp_write_command(sx, SCMD_FLUSH, "RSET\r\n") >= 0))
-        {
-        msg = US string_sprintf("send() to %s [%s] failed: %s", host->name,
-          host->address, strerror(errno));
-        sx->send_quit = FALSE;
-        }
-      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,
-          &pass_message);
-        if (!sx->send_quit)
-          {
-          DEBUG(D_transport) debug_printf("H=%s [%s] %s\n",
-        host->name, host->address, msg);
-          }
-        }
+      if (sx->send_rset)
+    if (! (sx->ok = smtp_write_command(sx, SCMD_FLUSH, "RSET\r\n") >= 0))
+      {
+      msg = US string_sprintf("send() to %s [%s] failed: %s", host->name,
+        host->address, strerror(errno));
+      sx->send_quit = FALSE;
+      }
+    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,
+        &pass_message);
+      if (!sx->send_quit)
+        {
+        DEBUG(D_transport) debug_printf("H=%s [%s] %s\n",
+          host->name, host->address, msg);
+        }
+      }


-    /* Either RSET was not needed, or it succeeded */
+      /* Either RSET was not needed, or it succeeded */


-    if (sx->ok)
-      {
+      if (sx->ok)
+    {
 #ifndef DISABLE_TLS
-      int pfd[2];
-#endif
-      int socket_fd = sx->cctx.sock;
-
-      if (sx->first_addr)        /* More addresses still to be sent */
-        {                /*   for this message              */
-        continue_sequence++;            /* for consistency */
-        clearflag(sx->first_addr, af_new_conn);
-        setflag(sx->first_addr, af_cont_conn);    /* Causes * in logging */
-    pipelining_active = sx->pipelining_used;    /* was cleared at DATA */
-        goto SEND_MESSAGE;
-        }
+    int pfd[2];
+#endif
+    int socket_fd = sx->cctx.sock;
+
+    if (sx->first_addr)        /* More addresses still to be sent */
+      {                /*   for this message              */
+#ifdef EXPERIMENTAL_ESMTP_LIMITS
+      /* Any that we marked as skipped, reset to do now */
+      for (address_item * a = sx->first_addr; a; a = a->next)
+        if (a->transport_return == SKIP)
+          a->transport_return = PENDING_DEFER;
+#endif
+      continue_sequence++;                /* for consistency */
+      clearflag(sx->first_addr, af_new_conn);
+      setflag(sx->first_addr, af_cont_conn);    /* Causes * in logging */
+      pipelining_active = sx->pipelining_used;    /* was cleared at DATA */
+      goto SEND_MESSAGE;
+      }


-      /* Unless caller said it already has more messages listed for this host,
-      pass the connection on to a new Exim process (below, the call to
-      transport_pass_socket).  If the caller has more ready, just return with
-      the connection still open. */
+    /* Unless caller said it already has more messages listed for this host,
+    pass the connection on to a new Exim process (below, the call to
+    transport_pass_socket).  If the caller has more ready, just return with
+    the connection still open. */


 #ifndef DISABLE_TLS
-      if (tls_out.active.sock >= 0)
-    if (  f.continue_more
-       || 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
-      of the SMTP session when TLS is shut down. We test for this by sending
-      a new EHLO. If we don't get a good response, we don't attempt to pass
-      the socket on. */
+    if (tls_out.active.sock >= 0)
+      if (  f.continue_more
+         || 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
+        of the SMTP session when TLS is shut down. We test for this by sending
+        a new EHLO. If we don't get a good response, we don't attempt to pass
+        the socket on. */


       tls_close(sx->cctx.tls_ctx, TLS_SHUTDOWN_WAIT);
       sx->send_tlsclose = FALSE;
@@ -4422,84 +4652,88 @@ if (sx->completed_addr && sx->ok && sx->send_quit)
         && smtp_read_response(sx, sx->buffer, sizeof(sx->buffer),
                       '2', ob->command_timeout);


-      if (sx->ok && f.continue_more)
-        goto TIDYUP;        /* More addresses for another run */
-      }
-    else
-      {
-      /* Set up a pipe for proxying TLS for the new transport process */
-
-      smtp_peer_options |= OPTION_TLS;
-      if ((sx->ok = socketpair(AF_UNIX, SOCK_STREAM, 0, pfd) == 0))
-        socket_fd = pfd[1];
+        if (sx->ok && f.continue_more)
+          goto TIDYUP;        /* More addresses for another run */
+        }
       else
-        set_errno(sx->first_addr, errno, US"internal allocation problem",
-            DEFER, FALSE, host,
+        {
+        /* Set up a pipe for proxying TLS for the new transport process */
+
+        smtp_peer_options |= OPTION_TLS;
+        if ((sx->ok = socketpair(AF_UNIX, SOCK_STREAM, 0, pfd) == 0))
+          socket_fd = pfd[1];
+        else
+          set_errno(sx->first_addr, errno, US"internal allocation problem",
+              DEFER, FALSE, host,
 # ifdef EXPERIMENTAL_DSN_INFO
-            sx->smtp_greeting, sx->helo_response,
+              sx->smtp_greeting, sx->helo_response,
 # endif
-            &sx->delivery_start);
-      }
-      else
+              &sx->delivery_start);
+        }
+    else
 #endif
-    if (f.continue_more)
-      goto TIDYUP;            /* More addresses for another run */
-
-      /* If the socket is successfully passed, we mustn't send QUIT (or
-      indeed anything!) from here. */
+      if (f.continue_more)
+        goto TIDYUP;            /* More addresses for another run */


-/*XXX DSN_INFO: assume likely to do new HELO; but for greet we'll want to
-propagate it from the initial
-*/
-      if (sx->ok && transport_pass_socket(tblock->name, host->name,
-        host->address, new_message_id, socket_fd))
-    {
-        sx->send_quit = FALSE;
+    /* If the socket is successfully passed, we mustn't send QUIT (or
+    indeed anything!) from here. */


-    /* We have passed the client socket to a fresh transport process.
-    If TLS is still active, we need to proxy it for the transport we
-    just passed the baton to.  Fork a child to to do it, and return to
-    get logging done asap.  Which way to place the work makes assumptions
-    about post-fork prioritisation which may not hold on all platforms. */
-#ifndef DISABLE_TLS
-    if (tls_out.active.sock >= 0)
+  /*XXX DSN_INFO: assume likely to do new HELO; but for greet we'll want to
+  propagate it from the initial
+  */
+    if (sx->ok && transport_pass_socket(tblock->name, host->name,
+          host->address, new_message_id, socket_fd
+#ifdef EXPERIMENTAL_ESMTP_LIMITS
+          , sx->peer_limit_mail, sx->peer_limit_rcpt, sx->peer_limit_rcptdom
+#endif
+          ))
       {
-      int pid = exim_fork(US"tls-proxy-interproc");
-      if (pid == 0)        /* child; fork again to disconnect totally */
-        {
-        /* does not return */
-        smtp_proxy_tls(sx->cctx.tls_ctx, sx->buffer, sizeof(sx->buffer), pfd,
-                ob->command_timeout);
-        }
+      sx->send_quit = FALSE;


-      if (pid > 0)        /* parent */
+      /* We have passed the client socket to a fresh transport process.
+      If TLS is still active, we need to proxy it for the transport we
+      just passed the baton to.  Fork a child to to do it, and return to
+      get logging done asap.  Which way to place the work makes assumptions
+      about post-fork prioritisation which may not hold on all platforms. */
+#ifndef DISABLE_TLS
+      if (tls_out.active.sock >= 0)
         {
-        close(pfd[0]);
-        /* tidy the inter-proc to disconn the proxy proc */
-        waitpid(pid, NULL, 0);
-        tls_close(sx->cctx.tls_ctx, TLS_NO_SHUTDOWN);
-        sx->cctx.tls_ctx = NULL;
-        (void)close(sx->cctx.sock);
-        sx->cctx.sock = -1;
-        continue_transport = NULL;
-        continue_hostname = NULL;
-        goto TIDYUP;
+        int pid = exim_fork(US"tls-proxy-interproc");
+        if (pid == 0)        /* child; fork again to disconnect totally */
+          {
+          /* does not return */
+          smtp_proxy_tls(sx->cctx.tls_ctx, sx->buffer, sizeof(sx->buffer), pfd,
+                  ob->command_timeout);
+          }
+
+        if (pid > 0)        /* parent */
+          {
+          close(pfd[0]);
+          /* tidy the inter-proc to disconn the proxy proc */
+          waitpid(pid, NULL, 0);
+          tls_close(sx->cctx.tls_ctx, TLS_NO_SHUTDOWN);
+          sx->cctx.tls_ctx = NULL;
+          (void)close(sx->cctx.sock);
+          sx->cctx.sock = -1;
+          continue_transport = NULL;
+          continue_hostname = NULL;
+          goto TIDYUP;
+          }
+        log_write(0, LOG_PANIC_DIE, "fork failed");
         }
-      log_write(0, LOG_PANIC_DIE, "fork failed");
-      }
 #endif
+      }
     }
-      }


-    /* If RSET failed and there are addresses left, they get deferred. */
-    else
-      set_errno(sx->first_addr, errno, msg, DEFER, FALSE, host,
+      /* If RSET failed and there are addresses left, they get deferred. */
+      else
+    set_errno(sx->first_addr, errno, msg, DEFER, FALSE, host,
 #ifdef EXPERIMENTAL_DSN_INFO
-          sx->smtp_greeting, sx->helo_response,
+            sx->smtp_greeting, sx->helo_response,
 #endif
-          &sx->delivery_start);
+            &sx->delivery_start);
+      }
     }
-  }


 /* End off tidily with QUIT unless the connection has died or the socket has
 been passed to another process. */
@@ -4613,12 +4847,24 @@ if (dane_held)
     }
       }
   continue_sequence = 1;            /* for consistency */
-  goto DANE_DOMAINS;
+  goto REPEAT_CONN;
+  }
+#endif
+
+#ifdef EXPERIMENTAL_ESMTP_LIMITS
+if (mail_limit && sx->first_addr)
+  {
+  /* Reset the sequence count since we closed the connection.  This is flagged
+  on the pipe back to the delivery process so that a non-continued-conn delivery
+  is logged. */
+
+  continue_sequence = 1;            /* for consistency */
+  clearflag(sx->first_addr, af_cont_conn);
+  setflag(sx->first_addr, af_new_conn);        /* clear  * from logging */
+  goto REPEAT_CONN;
   }
 #endif


-continue_transport = NULL;
-continue_hostname = NULL;
return yield;

TIDYUP:
diff --git a/src/src/transports/smtp.h b/src/src/transports/smtp.h
index 1ea2a4d..aff3f54 100644
--- a/src/src/transports/smtp.h
+++ b/src/src/transports/smtp.h
@@ -171,15 +171,25 @@ typedef struct {
BOOL pending_BDAT:1;
BOOL RCPT_452:1;
BOOL good_RCPT:1;
+#ifdef EXPERIMENTAL_ESMTP_LIMITS
+ BOOL single_rcpt_domain:1;
+#endif
BOOL completed_addr:1;
BOOL send_rset:1;
BOOL send_quit:1;
BOOL send_tlsclose:1;

+  unsigned    peer_offered;
+#ifdef EXPERIMENTAL_ESMTP_LIMITS
+  unsigned    peer_limit_mail;
+  unsigned    peer_limit_rcpt;
+  unsigned    peer_limit_rcptdom;
+#endif
+
+  unsigned    max_mail;
   int        max_rcpt;
   int        cmd_count;


-  unsigned    peer_offered;
   unsigned    avoid_option;
   uschar *    igquotstr;
   uschar *    helo_data;
@@ -188,6 +198,11 @@ typedef struct {
   uschar *    helo_response;
 #endif
 #ifndef DISABLE_PIPE_CONNECT
+  /* Info about the EHLO response stored to / retrieved from cache.  When
+  operating early-pipe, we use the cached values.  For each of plaintext and
+  crypted we store bitmaps for ESMTP features and AUTH methods.  If the LIMITS
+  extension is built and usable them at least one of the limits values cached
+  is nonzero, and we use the values to constrain the connection. */
   ehlo_resp_precis    ehlo_resp;
 #endif


diff --git a/test/aux-var-src/tls_conf_prefix b/test/aux-var-src/tls_conf_prefix
index 5418176..e3f09b9 100644
--- a/test/aux-var-src/tls_conf_prefix
+++ b/test/aux-var-src/tls_conf_prefix
@@ -24,3 +24,6 @@ pipelining_connect_advertise_hosts = :
.ifdef _HAVE_DMARC
dmarc_tld_file =
.endif
+.ifdef _EXP_LIMITS
+limits_advertise_hosts = !*
+.endif
diff --git a/test/confs/0453 b/test/confs/0453
index 3ecc865..c611d4a 100644
--- a/test/confs/0453
+++ b/test/confs/0453
@@ -1,6 +1,6 @@
# Exim test configuration 0453

-LIMIT=
+ERROR_DETAILS=

.include DIR/aux-var/std_conf_prefix

@@ -11,7 +11,7 @@ primary_hostname = myhost.test.ex

qualify_domain = test.ex

-LIMIT
+ERROR_DETAILS


# End
diff --git a/test/confs/0564 b/test/confs/0564
index de71325..fd38c62 100644
--- a/test/confs/0564
+++ b/test/confs/0564
@@ -15,6 +15,9 @@ pipelining_connect_advertise_hosts =
.ifdef _HAVE_DMARC
dmarc_tld_file =
.endif
+.ifdef _OPT_MAIN_LIMITS_ADVERTISE_HOSTS
+limits_advertise_hosts = !*
+.endif

# ----- Main settings -----

diff --git a/test/confs/0900 b/test/confs/0900
index 7775fc4..a56ec0e 100644
--- a/test/confs/0900
+++ b/test/confs/0900
@@ -23,7 +23,9 @@ pipelining_connect_advertise_hosts = :
.ifdef _HAVE_DMARC
dmarc_tld_file =
.endif
-
+.ifdef _OPT_MAIN_LIMITS_ADVERTISE_HOSTS
+limits_advertise_hosts = !*
+.endif

# ----- Main settings -----

diff --git a/test/confs/0901 b/test/confs/0901
index a1f3916..361a9bf 100644
--- a/test/confs/0901
+++ b/test/confs/0901
@@ -17,6 +17,9 @@ tls_advertise_hosts = ${if eq {SRV}{tls} {*}}
.ifdef _HAVE_DMARC
dmarc_tld_file =
.endif
+.ifdef _OPT_MAIN_LIMITS_ADVERTISE_HOSTS
+limits_advertise_hosts = !*
+.endif

pipelining_advertise_hosts = :

diff --git a/test/confs/0906 b/test/confs/0906
index 5bd5f2a..57f359f 100644
--- a/test/confs/0906
+++ b/test/confs/0906
@@ -16,6 +16,9 @@ pipelining_connect_advertise_hosts =
.ifdef _HAVE_DMARC
dmarc_tld_file =
.endif
+.ifdef _EXP_LIMITS
+limits_advertise_hosts = !*
+.endif

# ----- Main settings -----

diff --git a/test/confs/3414 b/test/confs/3414
index 5d0f93c..25205a4 100644
--- a/test/confs/3414
+++ b/test/confs/3414
@@ -13,6 +13,9 @@ gecos_pattern = ""
gecos_name = CALLER_NAME
chunking_advertise_hosts =
tls_advertise_hosts =
+.ifdef _EXP_LIMITS
+limits_advertise_hosts = !*
+.endif

# ----- Main settings -----

diff --git a/test/confs/4050 b/test/confs/4050
index c26b7a9..fd3d7db 100644
--- a/test/confs/4050
+++ b/test/confs/4050
@@ -25,6 +25,9 @@ tls_advertise_hosts =
.ifdef _HAVE_DMARC
dmarc_tld_file =
.endif
+.ifdef _OPT_MAIN_LIMITS_ADVERTISE_HOSTS
+limits_advertise_hosts = !*
+.endif

 pipelining_connect_advertise_hosts = CONNECTCOND
 retry_data_expire = RETRY
diff --git a/test/confs/4710 b/test/confs/4710
new file mode 100644
index 0000000..017fb56
--- /dev/null
+++ b/test/confs/4710
@@ -0,0 +1,36 @@
+# Exim test configuration 4710
+#
+
+exim_path = EXIM_PATH
+keep_environment = USER
+host_lookup_order = bydns
+primary_hostname = myhost.test.ex
+spool_directory = DIR/spool
+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
+.ifdef _HAVE_DMARC
+dmarc_tld_file =
+.endif
+
+# ----- Main settings -----
+
+.ifdef CONTROL
+limits_advertise_hosts = :
+.endif
+.ifdef MAXNM
+smtp_accept_max_per_connection = ${if eq {127.0.0.1}{$sender_host_address} {MAXNM}{44}}
+.endif
+.ifdef RCPT_MSG
+recipients_max = RCPT_MSG
+.endif
+
+# ----- ACL -----
+
+begin acl
+
+# End
diff --git a/test/confs/4711 b/test/confs/4711
new file mode 100644
index 0000000..23f141d
--- /dev/null
+++ b/test/confs/4711
@@ -0,0 +1,31 @@
+# Exim test configuration 4711
+
+.include DIR/aux-var/std_conf_prefix
+
+
+# ----- Main settings -----
+
+acl_smtp_rcpt = accept
+
+# ----- Routers -----
+
+begin routers
+
+send_to_server:
+  driver =    accept
+  transport =    to_server
+
+# ----- Transports -----
+
+begin transports
+
+to_server:
+  driver =    smtp
+  allow_localhost
+  hosts =    127.0.0.1
+  port =    PORT_D
+.ifdef RCPT_MSG
+  max_rcpt =    RCPT_MSG
+.endif
+
+# End
diff --git a/test/confs/4712 b/test/confs/4712
new file mode 100644
index 0000000..c48be50
--- /dev/null
+++ b/test/confs/4712
@@ -0,0 +1,28 @@
+# Exim test configuration 4712
+
+.include DIR/aux-var/std_conf_prefix
+
+
+# ----- Main settings -----
+
+acl_smtp_rcpt = accept
+
+# ----- Routers -----
+
+begin routers
+
+send_to_server:
+  driver =    accept
+  transport =    to_server
+
+# ----- Transports -----
+
+begin transports
+
+to_server:
+  driver =    smtp
+  allow_localhost
+  hosts =    127.0.0.1
+  port =    PORT_D
+
+# End
diff --git a/test/confs/4713 b/test/confs/4713
new file mode 100644
index 0000000..c5394fb
--- /dev/null
+++ b/test/confs/4713
@@ -0,0 +1,31 @@
+# Exim test configuration 4711
+
+.include DIR/aux-var/std_conf_prefix
+
+
+# ----- Main settings -----
+
+acl_smtp_rcpt = accept
+
+# ----- Routers -----
+
+begin routers
+
+send_to_server:
+  driver =    accept
+  transport =    to_server
+
+# ----- Transports -----
+
+begin transports
+
+to_server:
+  driver =    smtp
+  allow_localhost
+  hosts =    127.0.0.1
+  port =    PORT_D
+.ifdef CONTROL
+  multi_domain =    false
+.endif
+
+# End
diff --git a/test/confs/4714 b/test/confs/4714
new file mode 100644
index 0000000..dd06bd8
--- /dev/null
+++ b/test/confs/4714
@@ -0,0 +1,36 @@
+# Exim test configuration 4714
+
+.include DIR/aux-var/std_conf_prefix
+
+
+# ----- Main settings -----
+
+log_selector = +received_recipients
+queue_run_in_order
+
+acl_smtp_rcpt = accept
+
+# ----- Routers -----
+
+begin routers
+
+send_to_server:
+  driver =    accept
+  transport =    to_server
+
+# ----- Transports -----
+
+begin transports
+
+to_server:
+  driver =    smtp
+  allow_localhost
+  hosts =    127.0.0.1
+  port =    PORT_D
+
+# ----- Retry -----
+
+begin retry
+* * F,5d,10s
+
+# End
diff --git a/test/confs/5670 b/test/confs/5670
index d4bedac..f425266 100644
--- a/test/confs/5670
+++ b/test/confs/5670
@@ -32,11 +32,11 @@ tls_ocsp_file =   PEM DIR/tmp/ocsp/double_r.ocsp.pem



.ifdef _HAVE_GNUTLS
-tls_require_ciphers = ${if eq {LIMIT}{TLS1.2} {NORMAL:!VERS-ALL:+VERS-TLS1.2} {}}
+tls_require_ciphers = ${if eq {TRUSTED}{TLS1.2} {NORMAL:!VERS-ALL:+VERS-TLS1.2} {}}
.endif
.ifdef _HAVE_OPENSSL
-.ifdef LIMIT
-openssl_options = ${if eq {LIMIT}{TLS1.2} {+no_tlsv1_3} {}}
+.ifdef TRUSTED
+openssl_options = ${if eq {TRUSTED}{TLS1.2} {+no_tlsv1_3} {}}
.endif
.endif

@@ -81,7 +81,7 @@ remote_delivery:
   hosts_require_tls =        *


 .ifdef _HAVE_GNUTLS
-  tls_require_ciphers =        ${if eq {LIMIT}{TLS1.2} \
+  tls_require_ciphers =        ${if eq {TRUSTED}{TLS1.2} \
                   {NONE:\
                       +SIGN-RSA-SHA256:+VERS-TLS-ALL:+ECDHE-RSA:+DHE-RSA:+RSA\
                   :+CIPHER-ALL:+MAC-ALL:+COMP-NULL:+CURVE-ALL:+CTYPE-X509} \
diff --git a/test/log/4710 b/test/log/4710
new file mode 100644
index 0000000..6e70d69
--- /dev/null
+++ b/test/log/4710
@@ -0,0 +1,6 @@
+1999-03-02 09:44:33 exim x.yz daemon started: pid=pppp, no queue runs, listening for SMTP on port PORT_D
+1999-03-02 09:44:33 exim x.yz daemon started: pid=pppp, no queue runs, listening for SMTP on port PORT_D
+1999-03-02 09:44:33 exim x.yz daemon started: pid=pppp, no queue runs, listening for SMTP on port PORT_D
+1999-03-02 09:44:33 exim x.yz daemon started: pid=pppp, no queue runs, listening for SMTP on port PORT_D
+1999-03-02 09:44:33 exim x.yz daemon started: pid=pppp, no queue runs, listening for SMTP on port PORT_D
+1999-03-02 09:44:33 exim x.yz daemon started: pid=pppp, no queue runs, listening for SMTP on port PORT_D
diff --git a/test/log/4711 b/test/log/4711
new file mode 100644
index 0000000..520ad1f
--- /dev/null
+++ b/test/log/4711
@@ -0,0 +1,24 @@
+1999-03-02 09:44:33 10HmaX-0005vi-00 <= CALLER@??? U=CALLER P=local S=sss
+1999-03-02 09:44:33 10HmaX-0005vi-00 => a@??? R=send_to_server T=to_server H=127.0.0.1 [127.0.0.1] C="250 message received"
+1999-03-02 09:44:33 10HmaX-0005vi-00 -> b@??? R=send_to_server T=to_server H=127.0.0.1 [127.0.0.1] C="250 message received"
+1999-03-02 09:44:33 10HmaX-0005vi-00 -> c@??? R=send_to_server T=to_server H=127.0.0.1 [127.0.0.1] C="250 message received"
+1999-03-02 09:44:33 10HmaX-0005vi-00 -> d@??? R=send_to_server T=to_server H=127.0.0.1 [127.0.0.1] C="250 message received"
+1999-03-02 09:44:33 10HmaX-0005vi-00 -> e@??? R=send_to_server T=to_server H=127.0.0.1 [127.0.0.1] C="250 message received"
+1999-03-02 09:44:33 10HmaX-0005vi-00 Completed
+1999-03-02 09:44:33 10HmaY-0005vi-00 <= CALLER@??? U=CALLER P=local S=sss
+1999-03-02 09:44:33 10HmaY-0005vi-00 => a@??? R=send_to_server T=to_server H=127.0.0.1 [127.0.0.1] C="250 message received"
+1999-03-02 09:44:33 10HmaY-0005vi-00 -> b@??? R=send_to_server T=to_server H=127.0.0.1 [127.0.0.1] C="250 message received"
+1999-03-02 09:44:33 10HmaY-0005vi-00 => c@??? R=send_to_server T=to_server H=127.0.0.1 [127.0.0.1]* C="250 message received"
+1999-03-02 09:44:33 10HmaY-0005vi-00 -> d@??? R=send_to_server T=to_server H=127.0.0.1 [127.0.0.1]* C="250 message received"
+1999-03-02 09:44:33 10HmaY-0005vi-00 => e@??? R=send_to_server T=to_server H=127.0.0.1 [127.0.0.1]* C="250 message received"
+1999-03-02 09:44:33 10HmaY-0005vi-00 Completed
+1999-03-02 09:44:33 10HmaZ-0005vi-00 <= CALLER@??? U=CALLER P=local S=sss
+1999-03-02 09:44:33 10HmaZ-0005vi-00 => a@??? R=send_to_server T=to_server H=127.0.0.1 [127.0.0.1] C="250 message received"
+1999-03-02 09:44:33 10HmaZ-0005vi-00 -> b@??? R=send_to_server T=to_server H=127.0.0.1 [127.0.0.1] C="250 message received"
+1999-03-02 09:44:33 10HmaZ-0005vi-00 => c@??? R=send_to_server T=to_server H=127.0.0.1 [127.0.0.1]* C="250 message received"
+1999-03-02 09:44:33 10HmaZ-0005vi-00 Completed
+1999-03-02 09:44:33 10HmbA-0005vi-00 <= CALLER@??? U=CALLER P=local S=sss
+1999-03-02 09:44:33 10HmbA-0005vi-00 => a@??? R=send_to_server T=to_server H=127.0.0.1 [127.0.0.1] C="250 message received"
+1999-03-02 09:44:33 10HmbA-0005vi-00 -> b@??? R=send_to_server T=to_server H=127.0.0.1 [127.0.0.1] C="250 message received"
+1999-03-02 09:44:33 10HmbA-0005vi-00 => c@??? R=send_to_server T=to_server H=127.0.0.1 [127.0.0.1] C="250 message received"
+1999-03-02 09:44:33 10HmbA-0005vi-00 Completed
diff --git a/test/log/4712 b/test/log/4712
new file mode 100644
index 0000000..8479516
--- /dev/null
+++ b/test/log/4712
@@ -0,0 +1,8 @@
+1999-03-02 09:44:33 10HmaX-0005vi-00 <= CALLER@??? U=CALLER P=local S=sss
+1999-03-02 09:44:33 10HmaX-0005vi-00 => a@??? R=send_to_server T=to_server H=127.0.0.1 [127.0.0.1] C="250 message received"
+1999-03-02 09:44:33 10HmaX-0005vi-00 => b@??? R=send_to_server T=to_server H=127.0.0.1 [127.0.0.1]* C="250 message received"
+1999-03-02 09:44:33 10HmaX-0005vi-00 Completed
+1999-03-02 09:44:33 10HmaY-0005vi-00 <= CALLER@??? U=CALLER P=local S=sss
+1999-03-02 09:44:33 10HmaY-0005vi-00 => a@??? R=send_to_server T=to_server H=127.0.0.1 [127.0.0.1] C="250 message received"
+1999-03-02 09:44:33 10HmaY-0005vi-00 => b@??? R=send_to_server T=to_server H=127.0.0.1 [127.0.0.1] C="250 message received"
+1999-03-02 09:44:33 10HmaY-0005vi-00 Completed
diff --git a/test/log/4713 b/test/log/4713
new file mode 100644
index 0000000..09a56f3
--- /dev/null
+++ b/test/log/4713
@@ -0,0 +1,11 @@
+1999-03-02 09:44:33 10HmaX-0005vi-00 <= CALLER@??? U=CALLER P=local S=sss
+1999-03-02 09:44:33 10HmaX-0005vi-00 => a@??? R=send_to_server T=to_server H=127.0.0.1 [127.0.0.1] C="250 message received"
+1999-03-02 09:44:33 10HmaX-0005vi-00 -> b@??? R=send_to_server T=to_server H=127.0.0.1 [127.0.0.1] C="250 message received"
+1999-03-02 09:44:33 10HmaX-0005vi-00 Completed
+1999-03-02 09:44:33 10HmaY-0005vi-00 <= CALLER@??? U=CALLER P=local S=sss
+1999-03-02 09:44:33 10HmaY-0005vi-00 => a@??? R=send_to_server T=to_server H=127.0.0.1 [127.0.0.1] C="250 message received"
+1999-03-02 09:44:33 10HmaY-0005vi-00 => b@??? R=send_to_server T=to_server H=127.0.0.1 [127.0.0.1]* C="250 second message received"
+1999-03-02 09:44:33 10HmaY-0005vi-00 => c@??? R=send_to_server T=to_server H=127.0.0.1 [127.0.0.1]* C="250 third message received"
+1999-03-02 09:44:33 10HmaY-0005vi-00 -> a2@??? R=send_to_server T=to_server H=127.0.0.1 [127.0.0.1]* C="250 message received"
+1999-03-02 09:44:33 10HmaY-0005vi-00 -> b2@??? R=send_to_server T=to_server H=127.0.0.1 [127.0.0.1]* C="250 second message received"
+1999-03-02 09:44:33 10HmaY-0005vi-00 Completed
diff --git a/test/log/4714 b/test/log/4714
new file mode 100644
index 0000000..f97d89f
--- /dev/null
+++ b/test/log/4714
@@ -0,0 +1,14 @@
+1999-03-02 09:44:33 10HmaX-0005vi-00 <= CALLER@??? U=CALLER P=local S=sss for r1_1.test.ex r1_2.test.ex
+1999-03-02 09:44:33 10HmaY-0005vi-00 <= CALLER@??? U=CALLER P=local S=sss for r2_1.test.ex r2_2.test.ex
+1999-03-02 09:44:33 10HmaZ-0005vi-00 <= CALLER@??? U=CALLER P=local S=sss for r3_1.test.ex r3_2.test.ex
+1999-03-02 09:44:33 Start queue run: pid=pppp -qq
+1999-03-02 09:44:33 10HmaX-0005vi-00 => r1_1.test.ex@??? R=send_to_server T=to_server H=127.0.0.1 [127.0.0.1] C="250 message 1 received"
+1999-03-02 09:44:33 10HmaX-0005vi-00 => r1_2.test.ex@??? R=send_to_server T=to_server H=127.0.0.1 [127.0.0.1]* C="250 message 2 received"
+1999-03-02 09:44:33 10HmaX-0005vi-00 Completed
+1999-03-02 09:44:33 10HmaZ-0005vi-00 => r3_1.test.ex@??? R=send_to_server T=to_server H=127.0.0.1 [127.0.0.1]* C="250 message 3 received"
+1999-03-02 09:44:33 10HmaZ-0005vi-00 => r3_2.test.ex@??? R=send_to_server T=to_server H=127.0.0.1 [127.0.0.1]* C="250 message 4 received"
+1999-03-02 09:44:33 10HmaZ-0005vi-00 Completed
+1999-03-02 09:44:33 10HmaY-0005vi-00 => r2_1.test.ex@??? R=send_to_server T=to_server H=127.0.0.1 [127.0.0.1]* C="250 message 5 received"
+1999-03-02 09:44:33 10HmaY-0005vi-00 => r2_2.test.ex@??? R=send_to_server T=to_server H=127.0.0.1 [127.0.0.1] C="250 message 6 received"
+1999-03-02 09:44:33 10HmaY-0005vi-00 Completed
+1999-03-02 09:44:33 End queue run: pid=pppp -qq
diff --git a/test/runtest b/test/runtest
index 8ebba50..8f49b26 100755
--- a/test/runtest
+++ b/test/runtest
@@ -1020,6 +1020,9 @@ RESET_AFTER_EXTRA_LINE_READ:
     # ARC is not always supported by the build
     next if /^arc_sign =/;


+    # LIMITS is not always supported by the build
+    next if /^limits_advertise_hosts =/;
+
     # TLS resumption is not always supported by the build
     next if /^tls_resumption_hosts =/;
     next if /^-tls_resumption/;
@@ -1285,6 +1288,9 @@ RESET_AFTER_EXTRA_LINE_READ:
     # Experimental_REQUIRETLS
     next if / in tls_advertise_requiretls?\? no \(end of list\)/;


+    # Experimental_LIMITS
+    next if / in limits_advertise_hosts?\? no \(matched "!\*"\)/;
+
     # TCP Fast Open
     next if /^(ppppp )?setsockopt FASTOPEN: Network Error/;


diff --git a/test/scripts/0000-Basic/0453 b/test/scripts/0000-Basic/0453
index 8c199fe..b07a712 100644
--- a/test/scripts/0000-Basic/0453
+++ b/test/scripts/0000-Basic/0453
@@ -9,7 +9,7 @@ helo
helo
****
1
-exim -DLIMIT=smtp_max_synprot_errors=1 -bs
+exim -DERROR_DETAILS=smtp_max_synprot_errors=1 -bs
mail from:<>
mail from:<>
mail from:<>
diff --git a/test/scripts/4710-esmtp-limits/4710 b/test/scripts/4710-esmtp-limits/4710
new file mode 100644
index 0000000..875613c
--- /dev/null
+++ b/test/scripts/4710-esmtp-limits/4710
@@ -0,0 +1,86 @@
+# ESMTP LIMITS extension, server
+#
+# Baseline: advertised by default
+exim -DSERVER=server -bd -oX PORT_D
+****
+client 127.0.0.1 PORT_D
+??? 220
+EHLO tester
+??? 250-
+??? 250-SIZE
+??? 250-LIMITS MAILMAX=1000
+??? 250
+****
+killdaemon
+#
+# not advertised when disabled
+exim -DSERVER=server -DCONTROL=disable -bd -oX PORT_D
+****
+client 127.0.0.1 PORT_D
+??? 220
+EHLO tester
+??? 250-
+??? 250-SIZE
+??? 250-8BITMIME
+****
+killdaemon
+#
+# smtp_accept_max_per_connection controls the MAILMAX value advertised, and is expanded
+exim -DSERVER=server -DMAXNM=42 -bd -oX PORT_D
+****
+client 127.0.0.1 PORT_D
+??? 220
+EHLO tester
+??? 250-
+??? 250-SIZE
+??? 250-LIMITS MAILMAX=42
+??? 250
+****
+client HOSTIPV4 PORT_D
+??? 220
+EHLO tester
+??? 250-
+??? 250-SIZE
+??? 250-LIMITS MAILMAX=44
+??? 250
+****
+killdaemon
+#
+#
+# not advertised when zero and no RCPTMAX
+exim -DSERVER=server -DMAXNM=0 -bd -oX PORT_D
+****
+client 127.0.0.1 PORT_D
+??? 220
+EHLO tester
+??? 250-
+??? 250-SIZE
+??? 250
+****
+killdaemon
+#
+# reeipients_max controls an advertised RCPTMAX
+exim -DSERVER=server -DRCPT_MSG=5 -bd -oX PORT_D
+****
+client 127.0.0.1 PORT_D
+??? 220
+EHLO tester
+??? 250-
+??? 250-SIZE
+??? 250-LIMITS MAILMAX=1000 RCPTMAX=5
+??? 250
+****
+killdaemon
+#
+# RCPTMAX can appear on its own
+exim -DSERVER=server -DMAXNM=0 -DRCPT_MSG=5 -bd -oX PORT_D
+****
+client 127.0.0.1 PORT_D
+??? 220
+EHLO tester
+??? 250-
+??? 250-SIZE
+??? 250-LIMITS RCPTMAX=5
+??? 250
+****
+killdaemon
diff --git a/test/scripts/4710-esmtp-limits/4711 b/test/scripts/4710-esmtp-limits/4711
new file mode 100644
index 0000000..ffbfa07
--- /dev/null
+++ b/test/scripts/4710-esmtp-limits/4711
@@ -0,0 +1,138 @@
+# ESMTP LIMITS extension, client RCPTMAX
+#
+# Baseline: no RCPTMAX advertised, can send 5 RCPT commands
+server PORT_D
+220 Hi there
+EHLO
+250-yeah mate
+250 LIMITS MAILMAX=10
+MAIL FROM
+250 mail cmd good
+RCPT TO
+250 rcpt cmd 1 good
+RCPT TO
+250 rcpt cmd 2 good
+RCPT TO
+250 rcpt cmd 3 good
+RCPT TO
+250 rcpt cmd 4 good
+RCPT TO
+250 rcpt cmd 5 good
+DATA
+352 go ahead
+.
+250 message received
+QUIT
+220 bye
+****
+exim -odi a@??? b@??? c@??? d@??? e@???
+****
+#
+# RCPTMAX advertised, limits RCPT commands
+# Client should immediate-retry fusther MAIL transaction for remaning rcpts
+server PORT_D
+220 Hi there
+EHLO
+250-yeah mate
+250 LIMITS RCPTMAX=2
+MAIL FROM
+250 mail cmd good
+RCPT TO
+250 rcpt cmd 1 good
+RCPT TO
+250 rcpt cmd 2 good
+DATA
+352 go ahead
+.
+250 message received
+MAIL FROM
+250 mail cmd good
+RCPT TO
+250 rcpt cmd 3 good
+RCPT TO
+250 rcpt cmd 4 good
+DATA
+352 go ahead
+.
+250 message received
+MAIL FROM
+250 mail cmd good
+RCPT TO
+250 rcpt cmd 5 good
+DATA
+352 go ahead
+.
+250 message received
+QUIT
+220 bye
+****
+exim -odi a@??? b@??? c@??? d@??? e@???
+****
+#
+# RCPTMAX advertised, overrides larger tpt max_rcpt and limits RCPT commands
+server PORT_D
+220 Hi there
+EHLO
+250-yeah mate
+250 LIMITS RCPTMAX=2
+MAIL FROM
+250 mail cmd good
+RCPT TO
+250 rcpt cmd 1 good
+RCPT TO
+250 rcpt cmd 2 good
+DATA
+352 go ahead
+.
+250 message received
+MAIL FROM
+250 mail cmd good
+RCPT TO
+250 rcpt cmd 3 good
+DATA
+352 go ahead
+.
+250 message received
+QUIT
+220 bye
+****
+exim -odi -DRCPT_MSG=3 a@??? b@??? c@???
+****
+#
+# RCPTMAX advertised, does not override smaller tpt max_rcpt which limits RCPT commands
+# Client make a separate conn for the second transaction
+server PORT_D 2
+220 Hi there
+EHLO
+250-yeah mate
+250 LIMITS RCPTMAX=3
+MAIL FROM
+250 mail cmd good
+RCPT TO
+250 rcpt cmd 1 good
+RCPT TO
+250 rcpt cmd 2 good
+DATA
+352 go ahead
+.
+250 message received
+QUIT
+220 bye
+*eof
+220 Hi there
+EHLO
+250-yeah mate
+250 LIMITS RCPTMAX=3
+MAIL FROM
+250 mail cmd good
+RCPT TO
+250 rcpt cmd 3 good
+DATA
+352 go ahead
+.
+250 message received
+QUIT
+220 bye
+****
+exim -odi -DRCPT_MSG=2 a@??? b@??? c@???
+****
diff --git a/test/scripts/4710-esmtp-limits/4712 b/test/scripts/4710-esmtp-limits/4712
new file mode 100644
index 0000000..c5d1cab
--- /dev/null
+++ b/test/scripts/4710-esmtp-limits/4712
@@ -0,0 +1,65 @@
+# ESMTP LIMITS extension, client MAILMAX
+#
+# Baseline: no MAILMAX advertised, can send 2 messages
+# - limiting the RCPT to 1 is convenient to get the multiple messages
+server PORT_D
+220 Hi there
+EHLO
+250-yeah mate
+250 LIMITS RCPTMAX=1
+MAIL FROM
+250 mail cmd 1 good
+RCPT TO
+250 rcpt cmd good
+DATA
+352 go ahead
+.
+250 message received
+MAIL FROM
+250 mail cmd 2 good
+RCPT TO
+250 rcpt cmd good
+DATA
+352 go ahead
+.
+250 message received
+QUIT
+220 bye
+****
+exim -odi a@??? b@???
+****
+#
+# limited to one MAIL per conn. Client should immediate-retry a second one.
+server PORT_D 2
+220 Hi there
+EHLO
+250-yeah mate
+250 LIMITS RCPTMAX=1 MAILMAX=1
+MAIL FROM
+250 mail cmd 1 good
+RCPT TO
+250 rcpt cmd good
+DATA
+352 go ahead
+.
+250 message received
+QUIT
+220 bye
+*eof
+220 Hi there
+EHLO
+250-yeah mate
+250 LIMITS RCPTMAX=1 MAILMAX=1
+MAIL FROM
+250 mail cmd 2 good
+RCPT TO
+250 rcpt cmd good
+DATA
+352 go ahead
+.
+250 message received
+QUIT
+220 bye
+****
+exim -odi a@??? b@???
+****
diff --git a/test/scripts/4710-esmtp-limits/4713 b/test/scripts/4710-esmtp-limits/4713
new file mode 100644
index 0000000..6003f27
--- /dev/null
+++ b/test/scripts/4710-esmtp-limits/4713
@@ -0,0 +1,64 @@
+# ESMTP LIMITS extension, client RCPTDOMAINMAX Limit
+#
+# Baseline: no RCPTDOMAINMAX Limit advertised, can send RCPT commands with distinct domains
+server PORT_D
+220 Hi there
+EHLO
+250-yeah mate
+250 LIMITS MAILMAX=10
+MAIL FROM
+250 mail cmd good
+RCPT TO
+250 rcpt cmd 1 good
+RCPT TO
+250 rcpt cmd 2 good
+DATA
+352 go ahead
+.
+250 message received
+QUIT
+220 bye
+****
+exim -odi a@??? b@???
+****
+#
+# RCPTDOMAINMAX Limit advertised, second domain temp-rejected
+# Client should immediate-retry further MAIL transactions for remaining rcpts
+server PORT_D
+220 Hi there
+EHLO
+250-yeah mate
+250 LIMITS MAILMAX=10 RCPTDOMAINMAX=100
+MAIL FROM
+250 mail cmd good
+RCPT TO:<a@???>
+250 rcpt cmd 1 good
+RCPT TO:<a2@???>
+250 rcpt cmd 2 good
+DATA
+352 go ahead
+.
+250 message received
+MAIL FROM
+250 second mail cmd good
+RCPT TO:<b@???>
+250 rcpt cmd 1 good
+RCPT TO
+250 rcpt cmd 2 good
+DATA
+352 go ahead
+.
+250 second message received
+MAIL FROM
+250 third mail cmd good
+RCPT TO:<c@???>
+250 rcpt cmd 1 good
+DATA
+352 go ahead
+.
+250 third message received
+QUIT
+220 bye
+****
+exim -odi a@??? b@??? c@??? a2@??? b2@???
+****
diff --git a/test/scripts/4710-esmtp-limits/4714 b/test/scripts/4710-esmtp-limits/4714
new file mode 100644
index 0000000..f97d989
--- /dev/null
+++ b/test/scripts/4710-esmtp-limits/4714
@@ -0,0 +1,84 @@
+# ESMTP LIMITS extension, client continued-connection
+#
+# queue up 3 messages each with 2 recipients
+exim -odq r1_1.test.ex r1_2.test.ex
+Subject: message 1
+****
+exim -odq r2_1.test.ex r2_2.test.ex
+Subject: message 2
+****
+exim -odq r3_1.test.ex r3_2.test.ex
+Subject: message 3
+****
+#
+# Handed limits of 5 MAIL, 1 RCPT, expect to use 5 transactions in a one connection
+# when the client does a 2-phase queue run, followed by one transaction in one connection
+# from the same queue run.
+# The second pair and third initial should be from continued-connection trasports, flagged by the log lines.
+server PORT_D 2
+220 Hi there
+EHLO
+250-yeah mate
+250 LIMITS MAILMAX=5 RCPTMAX=1
+MAIL FROM
+250 mail cmd 1 good
+RCPT TO
+250 rcpt cmd good
+DATA
+352 go ahead
+.
+250 message 1 received
+MAIL FROM
+250 mail cmd 2 good
+RCPT TO
+250 rcpt cmd good
+DATA
+352 go ahead
+.
+250 message 2 received
+MAIL FROM
+250 mail cmd 3 good
+RCPT TO
+250 rcpt cmd good
+DATA
+352 go ahead
+.
+250 message 3 received
+MAIL FROM
+250 mail cmd 4 good
+RCPT TO
+250 rcpt cmd good
+DATA
+352 go ahead
+.
+250 message 4 received
+MAIL FROM
+250 mail cmd 5 good
+RCPT TO
+250 rcpt cmd good
+DATA
+352 go ahead
+.
+250 message 5 received
+QUIT
+221 bye
+*eof
+220 Hi there
+EHLO
+250-yeah mate
+250
+MAIL FROM
+250 mail cmd 1 good
+RCPT TO
+250 rcpt cmd good
+DATA
+352 go ahead
+.
+250 message 6 received
+QUIT
+221 bye
+*eof
+****
+#
+exim -qq
+****
diff --git a/test/scripts/4710-esmtp-limits/REQUIRES b/test/scripts/4710-esmtp-limits/REQUIRES
new file mode 100644
index 0000000..4817265
--- /dev/null
+++ b/test/scripts/4710-esmtp-limits/REQUIRES
@@ -0,0 +1 @@
+support Experimental_ESMTP_Limits
diff --git a/test/scripts/5670-OCSP-GnuTLS-1.3/5670 b/test/scripts/5670-OCSP-GnuTLS-1.3/5670
index 1df75fb..1ff6d81 100644
--- a/test/scripts/5670-OCSP-GnuTLS-1.3/5670
+++ b/test/scripts/5670-OCSP-GnuTLS-1.3/5670
@@ -14,10 +14,10 @@ system 'cat server1.example.com/server1.example.com.ocsp.signernocert.good.resp.
exim -z '1: TLS1.2 Server sends good leaf-staple on request, to client requiring RSA auth'
****
#
-sudo exim -bd -oX PORT_D -DSERVER=server -DLIMIT=TLS1.2
+sudo exim -bd -oX PORT_D -DSERVER=server -DTRUSTED=TLS1.2
****
#
-exim -odf -DOPT=rsa -DLIMIT=TLS1.2 rsa.auth@???
+exim -odf -DOPT=rsa -DTRUSTED=TLS1.2 rsa.auth@???
Subject: test

 .
@@ -29,7 +29,7 @@ exim -z '2: TLS1.3 Server sends good 3-element staple on request, to client requ
 ****
 #
 # Prefix with sudo to get SSLKEYLOGFILE to work.  Only works on the server.
-exim -bd -oX PORT_D -DSERVER=server -DLIMIT=TLS1.3
+exim -bd -oX PORT_D -DSERVER=server -DTRUSTED=TLS1.3
 ****
 exim -odf -DOPT=rsa rsa.auth@???
 Subject: test
@@ -43,7 +43,7 @@ killdaemon
 exim -z '3: TLS1.3 Server sends bad nonleaf staple, client detects it'
 ****
 #
-EXIM_TESTHARNESS_DISABLE_OCSPVALIDITYCHECK=y exim -bd -oX PORT_D -DSERVER=server -DLIMIT=TLS1.3 -DCONTROL=bad
+EXIM_TESTHARNESS_DISABLE_OCSPVALIDITYCHECK=y exim -bd -oX PORT_D -DSERVER=server -DTRUSTED=TLS1.3 -DCONTROL=bad
 ****
 exim -odf -DOPT=rsa rsa.auth@???
 Subject: test
diff --git a/test/stdout/4710 b/test/stdout/4710
new file mode 100644
index 0000000..35a51c4
--- /dev/null
+++ b/test/stdout/4710
@@ -0,0 +1,87 @@
+Connecting to 127.0.0.1 port 1225 ... connected
+??? 220
+<<< 220 myhost.test.ex ESMTP Exim x.yz Tue, 2 Mar 1999 09:44:33 +0000
+>>> EHLO tester
+??? 250-
+<<< 250-myhost.test.ex Hello tester [127.0.0.1]
+??? 250-SIZE
+<<< 250-SIZE 52428800
+??? 250-LIMITS MAILMAX=1000
+<<< 250-LIMITS MAILMAX=1000
+??? 250
+<<< 250-8BITMIME
+End of script
+Connecting to 127.0.0.1 port 1225 ... connected
+??? 220
+<<< 220 myhost.test.ex ESMTP Exim x.yz Tue, 2 Mar 1999 09:44:33 +0000
+>>> EHLO tester
+??? 250-
+<<< 250-myhost.test.ex Hello tester [127.0.0.1]
+??? 250-SIZE
+<<< 250-SIZE 52428800
+??? 250-8BITMIME
+<<< 250-8BITMIME
+End of script
+Connecting to 127.0.0.1 port 1225 ... connected
+??? 220
+<<< 220 myhost.test.ex ESMTP Exim x.yz Tue, 2 Mar 1999 09:44:33 +0000
+>>> EHLO tester
+??? 250-
+<<< 250-myhost.test.ex Hello tester [127.0.0.1]
+??? 250-SIZE
+<<< 250-SIZE 52428800
+??? 250-LIMITS MAILMAX=42
+<<< 250-LIMITS MAILMAX=42
+??? 250
+<<< 250-8BITMIME
+End of script
+Connecting to ip4.ip4.ip4.ip4 port 1225 ... connected
+??? 220
+<<< 220 myhost.test.ex ESMTP Exim x.yz Tue, 2 Mar 1999 09:44:33 +0000
+>>> EHLO tester
+??? 250-
+<<< 250-myhost.test.ex Hello tester [ip4.ip4.ip4.ip4]
+??? 250-SIZE
+<<< 250-SIZE 52428800
+??? 250-LIMITS MAILMAX=44
+<<< 250-LIMITS MAILMAX=44
+??? 250
+<<< 250-8BITMIME
+End of script
+Connecting to 127.0.0.1 port 1225 ... connected
+??? 220
+<<< 220 myhost.test.ex ESMTP Exim x.yz Tue, 2 Mar 1999 09:44:33 +0000
+>>> EHLO tester
+??? 250-
+<<< 250-myhost.test.ex Hello tester [127.0.0.1]
+??? 250-SIZE
+<<< 250-SIZE 52428800
+??? 250
+<<< 250-8BITMIME
+End of script
+Connecting to 127.0.0.1 port 1225 ... connected
+??? 220
+<<< 220 myhost.test.ex ESMTP Exim x.yz Tue, 2 Mar 1999 09:44:33 +0000
+>>> EHLO tester
+??? 250-
+<<< 250-myhost.test.ex Hello tester [127.0.0.1]
+??? 250-SIZE
+<<< 250-SIZE 52428800
+??? 250-LIMITS MAILMAX=1000 RCPTMAX=5
+<<< 250-LIMITS MAILMAX=1000 RCPTMAX=5
+??? 250
+<<< 250-8BITMIME
+End of script
+Connecting to 127.0.0.1 port 1225 ... connected
+??? 220
+<<< 220 myhost.test.ex ESMTP Exim x.yz Tue, 2 Mar 1999 09:44:33 +0000
+>>> EHLO tester
+??? 250-
+<<< 250-myhost.test.ex Hello tester [127.0.0.1]
+??? 250-SIZE
+<<< 250-SIZE 52428800
+??? 250-LIMITS RCPTMAX=5
+<<< 250-LIMITS RCPTMAX=5
+??? 250
+<<< 250-8BITMIME
+End of script
diff --git a/test/stdout/4711 b/test/stdout/4711
new file mode 100644
index 0000000..78b5203
--- /dev/null
+++ b/test/stdout/4711
@@ -0,0 +1,183 @@
+
+******** SERVER ********
+Listening on port 1225 ... 
+Connection request from [127.0.0.1]
+220 Hi there
+EHLO the.local.host.name
+250-yeah mate
+250 LIMITS MAILMAX=10
+MAIL FROM:<CALLER@???>
+250 mail cmd good
+RCPT TO:<a@???>
+250 rcpt cmd 1 good
+RCPT TO:<b@???>
+250 rcpt cmd 2 good
+RCPT TO:<c@???>
+250 rcpt cmd 3 good
+RCPT TO:<d@???>
+250 rcpt cmd 4 good
+RCPT TO:<e@???>
+250 rcpt cmd 5 good
+DATA
+352 go ahead
+Received: from CALLER by the.local.host.name with local (Exim x.yz)
+    (envelope-from <CALLER@???>)
+    id 10HmaX-0005vi-00; Tue, 2 Mar 1999 09:44:33 +0000
+Message-Id: <E10HmaX-0005vi-00@???>
+From: CALLER_NAME <CALLER@???>
+Date: Tue, 2 Mar 1999 09:44:33 +0000
+
+.
+250 message received
+QUIT
+220 bye
+End of script
+Listening on port 1225 ... 
+Connection request from [127.0.0.1]
+220 Hi there
+EHLO the.local.host.name
+250-yeah mate
+250 LIMITS RCPTMAX=2
+MAIL FROM:<CALLER@???>
+250 mail cmd good
+RCPT TO:<a@???>
+250 rcpt cmd 1 good
+RCPT TO:<b@???>
+250 rcpt cmd 2 good
+DATA
+352 go ahead
+Received: from CALLER by the.local.host.name with local (Exim x.yz)
+    (envelope-from <CALLER@???>)
+    id 10HmaY-0005vi-00; Tue, 2 Mar 1999 09:44:33 +0000
+Message-Id: <E10HmaY-0005vi-00@???>
+From: CALLER_NAME <CALLER@???>
+Date: Tue, 2 Mar 1999 09:44:33 +0000
+
+.
+250 message received
+MAIL FROM:<CALLER@???>
+250 mail cmd good
+RCPT TO:<c@???>
+250 rcpt cmd 3 good
+RCPT TO:<d@???>
+250 rcpt cmd 4 good
+DATA
+352 go ahead
+Received: from CALLER by the.local.host.name with local (Exim x.yz)
+    (envelope-from <CALLER@???>)
+    id 10HmaY-0005vi-00; Tue, 2 Mar 1999 09:44:33 +0000
+Message-Id: <E10HmaY-0005vi-00@???>
+From: CALLER_NAME <CALLER@???>
+Date: Tue, 2 Mar 1999 09:44:33 +0000
+
+.
+250 message received
+MAIL FROM:<CALLER@???>
+250 mail cmd good
+RCPT TO:<e@???>
+250 rcpt cmd 5 good
+DATA
+352 go ahead
+Received: from CALLER by the.local.host.name with local (Exim x.yz)
+    (envelope-from <CALLER@???>)
+    id 10HmaY-0005vi-00; Tue, 2 Mar 1999 09:44:33 +0000
+Message-Id: <E10HmaY-0005vi-00@???>
+From: CALLER_NAME <CALLER@???>
+Date: Tue, 2 Mar 1999 09:44:33 +0000
+
+.
+250 message received
+QUIT
+220 bye
+End of script
+Listening on port 1225 ... 
+Connection request from [127.0.0.1]
+220 Hi there
+EHLO the.local.host.name
+250-yeah mate
+250 LIMITS RCPTMAX=2
+MAIL FROM:<CALLER@???>
+250 mail cmd good
+RCPT TO:<a@???>
+250 rcpt cmd 1 good
+RCPT TO:<b@???>
+250 rcpt cmd 2 good
+DATA
+352 go ahead
+Received: from CALLER by the.local.host.name with local (Exim x.yz)
+    (envelope-from <CALLER@???>)
+    id 10HmaZ-0005vi-00; Tue, 2 Mar 1999 09:44:33 +0000
+Message-Id: <E10HmaZ-0005vi-00@???>
+From: CALLER_NAME <CALLER@???>
+Date: Tue, 2 Mar 1999 09:44:33 +0000
+
+.
+250 message received
+MAIL FROM:<CALLER@???>
+250 mail cmd good
+RCPT TO:<c@???>
+250 rcpt cmd 3 good
+DATA
+352 go ahead
+Received: from CALLER by the.local.host.name with local (Exim x.yz)
+    (envelope-from <CALLER@???>)
+    id 10HmaZ-0005vi-00; Tue, 2 Mar 1999 09:44:33 +0000
+Message-Id: <E10HmaZ-0005vi-00@???>
+From: CALLER_NAME <CALLER@???>
+Date: Tue, 2 Mar 1999 09:44:33 +0000
+
+.
+250 message received
+QUIT
+220 bye
+End of script
+Listening on port 1225 ... 
+Connection request from [127.0.0.1]
+220 Hi there
+EHLO the.local.host.name
+250-yeah mate
+250 LIMITS RCPTMAX=3
+MAIL FROM:<CALLER@???>
+250 mail cmd good
+RCPT TO:<a@???>
+250 rcpt cmd 1 good
+RCPT TO:<b@???>
+250 rcpt cmd 2 good
+DATA
+352 go ahead
+Received: from CALLER by the.local.host.name with local (Exim x.yz)
+    (envelope-from <CALLER@???>)
+    id 10HmbA-0005vi-00; Tue, 2 Mar 1999 09:44:33 +0000
+Message-Id: <E10HmbA-0005vi-00@???>
+From: CALLER_NAME <CALLER@???>
+Date: Tue, 2 Mar 1999 09:44:33 +0000
+
+.
+250 message received
+QUIT
+220 bye
+Expected EOF read from client
+Listening on port 1225 ... 
+Connection request from [127.0.0.1]
+220 Hi there
+EHLO the.local.host.name
+250-yeah mate
+250 LIMITS RCPTMAX=3
+MAIL FROM:<CALLER@???>
+250 mail cmd good
+RCPT TO:<c@???>
+250 rcpt cmd 3 good
+DATA
+352 go ahead
+Received: from CALLER by the.local.host.name with local (Exim x.yz)
+    (envelope-from <CALLER@???>)
+    id 10HmbA-0005vi-00; Tue, 2 Mar 1999 09:44:33 +0000
+Message-Id: <E10HmbA-0005vi-00@???>
+From: CALLER_NAME <CALLER@???>
+Date: Tue, 2 Mar 1999 09:44:33 +0000
+
+.
+250 message received
+QUIT
+220 bye
+End of script
diff --git a/test/stdout/4712 b/test/stdout/4712
new file mode 100644
index 0000000..69be8af
--- /dev/null
+++ b/test/stdout/4712
@@ -0,0 +1,89 @@
+
+******** SERVER ********
+Listening on port 1225 ... 
+Connection request from [127.0.0.1]
+220 Hi there
+EHLO the.local.host.name
+250-yeah mate
+250 LIMITS RCPTMAX=1
+MAIL FROM:<CALLER@???>
+250 mail cmd 1 good
+RCPT TO:<a@???>
+250 rcpt cmd good
+DATA
+352 go ahead
+Received: from CALLER by the.local.host.name with local (Exim x.yz)
+    (envelope-from <CALLER@???>)
+    id 10HmaX-0005vi-00; Tue, 2 Mar 1999 09:44:33 +0000
+Message-Id: <E10HmaX-0005vi-00@???>
+From: CALLER_NAME <CALLER@???>
+Date: Tue, 2 Mar 1999 09:44:33 +0000
+
+.
+250 message received
+MAIL FROM:<CALLER@???>
+250 mail cmd 2 good
+RCPT TO:<b@???>
+250 rcpt cmd good
+DATA
+352 go ahead
+Received: from CALLER by the.local.host.name with local (Exim x.yz)
+    (envelope-from <CALLER@???>)
+    id 10HmaX-0005vi-00; Tue, 2 Mar 1999 09:44:33 +0000
+Message-Id: <E10HmaX-0005vi-00@???>
+From: CALLER_NAME <CALLER@???>
+Date: Tue, 2 Mar 1999 09:44:33 +0000
+
+.
+250 message received
+QUIT
+220 bye
+End of script
+Listening on port 1225 ... 
+Connection request from [127.0.0.1]
+220 Hi there
+EHLO the.local.host.name
+250-yeah mate
+250 LIMITS RCPTMAX=1 MAILMAX=1
+MAIL FROM:<CALLER@???>
+250 mail cmd 1 good
+RCPT TO:<a@???>
+250 rcpt cmd good
+DATA
+352 go ahead
+Received: from CALLER by the.local.host.name with local (Exim x.yz)
+    (envelope-from <CALLER@???>)
+    id 10HmaY-0005vi-00; Tue, 2 Mar 1999 09:44:33 +0000
+Message-Id: <E10HmaY-0005vi-00@???>
+From: CALLER_NAME <CALLER@???>
+Date: Tue, 2 Mar 1999 09:44:33 +0000
+
+.
+250 message received
+QUIT
+220 bye
+Expected EOF read from client
+Listening on port 1225 ... 
+Connection request from [127.0.0.1]
+220 Hi there
+EHLO the.local.host.name
+250-yeah mate
+250 LIMITS RCPTMAX=1 MAILMAX=1
+MAIL FROM:<CALLER@???>
+250 mail cmd 2 good
+RCPT TO:<b@???>
+250 rcpt cmd good
+DATA
+352 go ahead
+Received: from CALLER by the.local.host.name with local (Exim x.yz)
+    (envelope-from <CALLER@???>)
+    id 10HmaY-0005vi-00; Tue, 2 Mar 1999 09:44:33 +0000
+Message-Id: <E10HmaY-0005vi-00@???>
+From: CALLER_NAME <CALLER@???>
+Date: Tue, 2 Mar 1999 09:44:33 +0000
+
+.
+250 message received
+QUIT
+220 bye
+End of script
diff --git a/test/stdout/4713 b/test/stdout/4713
new file mode 100644
index 0000000..02b10ea
--- /dev/null
+++ b/test/stdout/4713
@@ -0,0 +1,86 @@
+
+******** SERVER ********
+Listening on port 1225 ... 
+Connection request from [127.0.0.1]
+220 Hi there
+EHLO the.local.host.name
+250-yeah mate
+250 LIMITS MAILMAX=10
+MAIL FROM:<CALLER@???>
+250 mail cmd good
+RCPT TO:<a@???>
+250 rcpt cmd 1 good
+RCPT TO:<b@???>
+250 rcpt cmd 2 good
+DATA
+352 go ahead
+Received: from CALLER by the.local.host.name with local (Exim x.yz)
+    (envelope-from <CALLER@???>)
+    id 10HmaX-0005vi-00; Tue, 2 Mar 1999 09:44:33 +0000
+Message-Id: <E10HmaX-0005vi-00@???>
+From: CALLER_NAME <CALLER@???>
+Date: Tue, 2 Mar 1999 09:44:33 +0000
+
+.
+250 message received
+QUIT
+220 bye
+End of script
+Listening on port 1225 ... 
+Connection request from [127.0.0.1]
+220 Hi there
+EHLO the.local.host.name
+250-yeah mate
+250 LIMITS MAILMAX=10 RCPTDOMAINMAX=100
+MAIL FROM:<CALLER@???>
+250 mail cmd good
+RCPT TO:<a@???>
+250 rcpt cmd 1 good
+RCPT TO:<a2@???>
+250 rcpt cmd 2 good
+DATA
+352 go ahead
+Received: from CALLER by the.local.host.name with local (Exim x.yz)
+    (envelope-from <CALLER@???>)
+    id 10HmaY-0005vi-00; Tue, 2 Mar 1999 09:44:33 +0000
+Message-Id: <E10HmaY-0005vi-00@???>
+From: CALLER_NAME <CALLER@???>
+Date: Tue, 2 Mar 1999 09:44:33 +0000
+
+.
+250 message received
+MAIL FROM:<CALLER@???>
+250 second mail cmd good
+RCPT TO:<b@???>
+250 rcpt cmd 1 good
+RCPT TO:<b2@???>
+250 rcpt cmd 2 good
+DATA
+352 go ahead
+Received: from CALLER by the.local.host.name with local (Exim x.yz)
+    (envelope-from <CALLER@???>)
+    id 10HmaY-0005vi-00; Tue, 2 Mar 1999 09:44:33 +0000
+Message-Id: <E10HmaY-0005vi-00@???>
+From: CALLER_NAME <CALLER@???>
+Date: Tue, 2 Mar 1999 09:44:33 +0000
+
+.
+250 second message received
+MAIL FROM:<CALLER@???>
+250 third mail cmd good
+RCPT TO:<c@???>
+250 rcpt cmd 1 good
+DATA
+352 go ahead
+Received: from CALLER by the.local.host.name with local (Exim x.yz)
+    (envelope-from <CALLER@???>)
+    id 10HmaY-0005vi-00; Tue, 2 Mar 1999 09:44:33 +0000
+Message-Id: <E10HmaY-0005vi-00@???>
+From: CALLER_NAME <CALLER@???>
+Date: Tue, 2 Mar 1999 09:44:33 +0000
+
+.
+250 third message received
+QUIT
+220 bye
+End of script
diff --git a/test/stdout/4714 b/test/stdout/4714
new file mode 100644
index 0000000..f7d8e22
--- /dev/null
+++ b/test/stdout/4714
@@ -0,0 +1,117 @@
+
+******** SERVER ********
+Listening on port 1225 ... 
+Connection request from [127.0.0.1]
+220 Hi there
+EHLO the.local.host.name
+250-yeah mate
+250 LIMITS MAILMAX=5 RCPTMAX=1
+MAIL FROM:<CALLER@???>
+250 mail cmd 1 good
+RCPT TO:<r1_1.test.ex@???>
+250 rcpt cmd good
+DATA
+352 go ahead
+Received: from CALLER by the.local.host.name with local (Exim x.yz)
+    (envelope-from <CALLER@???>)
+    id 10HmaX-0005vi-00; Tue, 2 Mar 1999 09:44:33 +0000
+Subject: message 1
+Message-Id: <E10HmaX-0005vi-00@???>
+From: CALLER_NAME <CALLER@???>
+Date: Tue, 2 Mar 1999 09:44:33 +0000
+
+.
+250 message 1 received
+MAIL FROM:<CALLER@???>
+250 mail cmd 2 good
+RCPT TO:<r1_2.test.ex@???>
+250 rcpt cmd good
+DATA
+352 go ahead
+Received: from CALLER by the.local.host.name with local (Exim x.yz)
+    (envelope-from <CALLER@???>)
+    id 10HmaX-0005vi-00; Tue, 2 Mar 1999 09:44:33 +0000
+Subject: message 1
+Message-Id: <E10HmaX-0005vi-00@???>
+From: CALLER_NAME <CALLER@???>
+Date: Tue, 2 Mar 1999 09:44:33 +0000
+
+.
+250 message 2 received
+MAIL FROM:<CALLER@???>
+250 mail cmd 3 good
+RCPT TO:<r3_1.test.ex@???>
+250 rcpt cmd good
+DATA
+352 go ahead
+Received: from CALLER by the.local.host.name with local (Exim x.yz)
+    (envelope-from <CALLER@???>)
+    id 10HmaZ-0005vi-00; Tue, 2 Mar 1999 09:44:33 +0000
+Subject: message 3
+Message-Id: <E10HmaZ-0005vi-00@???>
+From: CALLER_NAME <CALLER@???>
+Date: Tue, 2 Mar 1999 09:44:33 +0000
+
+.
+250 message 3 received
+MAIL FROM:<CALLER@???>
+250 mail cmd 4 good
+RCPT TO:<r3_2.test.ex@???>
+250 rcpt cmd good
+DATA
+352 go ahead
+Received: from CALLER by the.local.host.name with local (Exim x.yz)
+    (envelope-from <CALLER@???>)
+    id 10HmaZ-0005vi-00; Tue, 2 Mar 1999 09:44:33 +0000
+Subject: message 3
+Message-Id: <E10HmaZ-0005vi-00@???>
+From: CALLER_NAME <CALLER@???>
+Date: Tue, 2 Mar 1999 09:44:33 +0000
+
+.
+250 message 4 received
+MAIL FROM:<CALLER@???>
+250 mail cmd 5 good
+RCPT TO:<r2_1.test.ex@???>
+250 rcpt cmd good
+DATA
+352 go ahead
+Received: from CALLER by the.local.host.name with local (Exim x.yz)
+    (envelope-from <CALLER@???>)
+    id 10HmaY-0005vi-00; Tue, 2 Mar 1999 09:44:33 +0000
+Subject: message 2
+Message-Id: <E10HmaY-0005vi-00@???>
+From: CALLER_NAME <CALLER@???>
+Date: Tue, 2 Mar 1999 09:44:33 +0000
+
+.
+250 message 5 received
+QUIT
+221 bye
+Expected EOF read from client
+Listening on port 1225 ... 
+Connection request from [127.0.0.1]
+220 Hi there
+EHLO the.local.host.name
+250-yeah mate
+250
+MAIL FROM:<CALLER@???>
+250 mail cmd 1 good
+RCPT TO:<r2_2.test.ex@???>
+250 rcpt cmd good
+DATA
+352 go ahead
+Received: from CALLER by the.local.host.name with local (Exim x.yz)
+    (envelope-from <CALLER@???>)
+    id 10HmaY-0005vi-00; Tue, 2 Mar 1999 09:44:33 +0000
+Subject: message 2
+Message-Id: <E10HmaY-0005vi-00@???>
+From: CALLER_NAME <CALLER@???>
+Date: Tue, 2 Mar 1999 09:44:33 +0000
+
+.
+250 message 6 received
+QUIT
+221 bye
+Expected EOF read from client
+End of script