[exim-cvs] Experimental_XCLIENT. Bug 2702

Top Page
Delete this message
Reply to this message
Author: Exim Git Commits Mailing List
Date:  
To: exim-cvs
Subject: [exim-cvs] Experimental_XCLIENT. Bug 2702
Gitweb: https://git.exim.org/exim.git/commitdiff/24cda181fb88542cf38db2beae5d0ddb37f59c5c
Commit:     24cda181fb88542cf38db2beae5d0ddb37f59c5c
Parent:     df0dc54a7666ef64b8a6681ab7b50a4836905203
Author:     Jeremy Harris <jgh146exb@???>
AuthorDate: Sat Mar 25 23:21:15 2023 +0000
Committer:  Jeremy Harris <jgh146exb@???>
CommitDate: Sat Mar 25 23:21:15 2023 +0000


    Experimental_XCLIENT.  Bug 2702
---
 doc/doc-txt/experimental-spec.txt      |  31 ++++
 src/OS/Makefile-Base                   |   4 +-
 src/scripts/MakeLinks                  |   2 +-
 src/src/auths/xtextdecode.c            |   4 +-
 src/src/config.h.defaults              |   1 +
 src/src/exim.c                         |   3 +
 src/src/functions.h                    |   5 +
 src/src/globals.c                      |  17 +-
 src/src/globals.h                      |  13 +-
 src/src/host.c                         |   4 +-
 src/src/macro_predef.c                 |   3 +
 src/src/macros.h                       |   6 +-
 src/src/readconf.c                     |   5 +-
 src/src/smtp_in.c                      |  74 +++++++-
 src/src/xclient.c                      | 299 +++++++++++++++++++++++++++++++++
 test/confs/4032                        |  41 +++++
 test/confs/4033                        |   1 +
 test/confs/4034                        |   1 +
 test/log/4032                          |  27 +++
 test/log/4034                          |  29 ++++
 test/rejectlog/4032                    |   5 +
 test/rejectlog/4034                    |   5 +
 test/runtest                           |   3 +
 test/scripts/4032-xclient/4032         | 140 +++++++++++++++
 test/scripts/4032-xclient/4033         |  62 +++++++
 test/scripts/4032-xclient/REQUIRES     |   1 +
 test/scripts/4034-xclient-tls/4034     | 179 ++++++++++++++++++++
 test/scripts/4034-xclient-tls/REQUIRES |   2 +
 test/stdout/4032                       | 199 ++++++++++++++++++++++
 test/stdout/4033                       | 108 ++++++++++++
 30 files changed, 1249 insertions(+), 25 deletions(-)


diff --git a/doc/doc-txt/experimental-spec.txt b/doc/doc-txt/experimental-spec.txt
index aac8ca77d..5bf00a7f1 100644
--- a/doc/doc-txt/experimental-spec.txt
+++ b/doc/doc-txt/experimental-spec.txt
@@ -662,6 +662,37 @@ Values advertised are only noted for TLS connections and ones for which
the server does not advertise TLS support.


+
+XCLIENT proxy support
+---------------------------------------------------------------
+Per https://www.postfix.org/XCLIENT_README.html
+
+XCLIENT is an ESMTP extension supporting an inbound proxy.
+The only client immplementation known is in Nginx
+(https://nginx.org/en/docs/mail/ngx_mail_proxy_module.html)
+
+If compiled with EXPERIMENTAL_XCLIENT=yes :-
+
+As a server, Exim will advertise XCLIENT support (conditional on a new option
+"hosts_xclient") and service XCLIENT commands with parameters
+  ADDR
+  NAME
+  PORT
+  LOGIN
+  DESTADDR
+  DESTPORT
+A fresh HELO/EHLO is required after a succesful XCLIENT, and the usual
+values are derived from that (making the HELO and PROTO paramemters redundant).
+
+An XCLIENT command must give both ADDR and PORT parameters if no previous
+XCLIENT has succeeded in the SMTP session.
+
+After a success:
+  $proxy_session variable becomes "yes"
+  $proxy_local_address, $proxy_local_port have the proxy "inside" values
+  $proxy_external_address, $proxy_external_port have the proxy "outside" values
+  $sender_host_address, $sender_host_port have the remot client values
+
 --------------------------------------------------------------
 End of file
 --------------------------------------------------------------
diff --git a/src/OS/Makefile-Base b/src/OS/Makefile-Base
index d00ab9404..71aee4d93 100644
--- a/src/OS/Makefile-Base
+++ b/src/OS/Makefile-Base
@@ -497,7 +497,8 @@ OBJ_EXPERIMENTAL =    arc.o \
             dmarc.o \
             imap_utf7.o \
             spf.o \
-            utf8.o
+            utf8.o \
+            xclient.o


 # Targets for final binaries; the main one has a build number which is
 # updated each time. We don't bother with that for the auxiliaries.
@@ -873,6 +874,7 @@ dmarc.o:    $(HDRS) pdkim/pdkim.h dmarc.h dmarc.c
 imap_utf7.o:    $(HDRS) imap_utf7.c
 spf.o:        $(HDRS) spf.h spf.c
 utf8.o:        $(HDRS) utf8.c
+xclient.o:    $(HDRS) xclient.c


# The module containing tables of available lookups, routers, auths, and
# transports must be rebuilt if any of them are. However, because the makefiles
diff --git a/src/scripts/MakeLinks b/src/scripts/MakeLinks
index af6138063..0694af4c0 100755
--- a/src/scripts/MakeLinks
+++ b/src/scripts/MakeLinks
@@ -125,7 +125,7 @@ done

 # EXPERIMENTAL_*
 for f in  arc.c bmi_spam.c bmi_spam.h dcc.c dcc.h dane.c dane-openssl.c \
-  danessl.h imap_utf7.c spf.c spf.h srs.c srs.h utf8.c
+  danessl.h imap_utf7.c spf.c spf.h srs.c srs.h utf8.c xclient.c
 do
   ln -s ../src/$f $f
 done
diff --git a/src/src/auths/xtextdecode.c b/src/src/auths/xtextdecode.c
index b6a927194..edd2282d0 100644
--- a/src/src/auths/xtextdecode.c
+++ b/src/src/auths/xtextdecode.c
@@ -32,9 +32,9 @@ Returns:      the number of bytes in the result, excluding the final zero;
 */


int
-auth_xtextdecode(uschar *code, uschar **ptr)
+auth_xtextdecode(uschar * code, uschar ** ptr)
{
-register int x;
+int x;
uschar * result = store_get(Ustrlen(code) + 1, code);
*ptr = result;

diff --git a/src/src/config.h.defaults b/src/src/config.h.defaults
index 221705224..fb5fe3603 100644
--- a/src/src/config.h.defaults
+++ b/src/src/config.h.defaults
@@ -211,6 +211,7 @@ Do not put spaces between # and the 'define'.
#define EXPERIMENTAL_DSN_INFO
#define EXPERIMENTAL_ESMTP_LIMITS
#define EXPERIMENTAL_QUEUEFILE
+#define EXPERIMENTAL_XCLIENT


/* For developers */
diff --git a/src/src/exim.c b/src/src/exim.c
index c16beb1af..06863347d 100644
--- a/src/src/exim.c
+++ b/src/src/exim.c
@@ -1132,6 +1132,9 @@ g = string_cat(g, US"Support for:");
#ifdef EXPERIMENTAL_QUEUEFILE
g = string_cat(g, US" Experimental_QUEUEFILE");
#endif
+#ifdef EXPERIMENTAL_XCLIENT
+ g = string_cat(g, US" Experimental_XCLIENT");
+#endif
g = string_cat(g, US"\n");

 g = string_cat(g, US"Lookups (built-in):");
diff --git a/src/src/functions.h b/src/src/functions.h
index 76392f304..aa5057a83 100644
--- a/src/src/functions.h
+++ b/src/src/functions.h
@@ -686,6 +686,11 @@ extern BOOL    write_chunk(transport_ctx *, uschar *, int);
 extern ssize_t write_to_fd_buf(int, const uschar *, size_t);
 extern uschar *wrap_header(const uschar *, unsigned, unsigned, const uschar *, unsigned);


+#ifdef EXPERIMENTAL_XCLIENT
+extern uschar * xclient_smtp_command(uschar *, int *, BOOL *);
+extern gstring * xclient_smtp_advertise_str(gstring *);
+#endif
+

 /******************************************************************************/
 /* Predicate: if an address is in a tainted pool.
diff --git a/src/src/globals.c b/src/src/globals.c
index 539bae00e..9f4053937 100644
--- a/src/src/globals.c
+++ b/src/src/globals.c
@@ -995,11 +995,18 @@ uschar *host_lookup_msg        = US"";
 int     host_number            = 0;
 uschar *host_number_string     = NULL;
 uschar *host_reject_connection = NULL;
-tree_node *hostlist_anchor     = NULL;
-int     hostlist_count         = 0;
+uschar *hosts_connection_nolog = NULL;
+#ifdef SUPPORT_PROXY
+uschar *hosts_proxy            = NULL;
+#endif
 uschar *hosts_treat_as_local   = NULL;
 uschar *hosts_require_helo     = US"*";
-uschar *hosts_connection_nolog = NULL;
+#ifdef EXPERIMENTAL_XCLIENT
+uschar *hosts_xclient           = NULL;
+#endif
+tree_node *hostlist_anchor     = NULL;
+int     hostlist_count         = 0;
+


 int     ignore_bounce_errors_after = 10*7*24*60*60;  /* 10 weeks */
 uschar *ignore_fromline_hosts  = NULL;
@@ -1232,8 +1239,7 @@ int     process_info_len       = 0;
 uschar *process_log_path       = NULL;
 const uschar *process_purpose  = US"fresh-exec";


-#if defined(SUPPORT_PROXY) || defined(SUPPORT_SOCKS)
-uschar *hosts_proxy            = NULL;
+#if defined(SUPPORT_PROXY) || defined(SUPPORT_SOCKS) || defined(EXPERIMENTAL_XCLIENT)
 uschar *proxy_external_address = NULL;
 int     proxy_external_port    = 0;
 uschar *proxy_local_address    = NULL;
@@ -1660,5 +1666,4 @@ int     warning_count          = 0;
 const uschar *warnmsg_delay    = NULL;
 const uschar *warnmsg_recipients = NULL;


-
 /*  End of globals.c */
diff --git a/src/src/globals.h b/src/src/globals.h
index e216b9208..3a5513382 100644
--- a/src/src/globals.h
+++ b/src/src/globals.h
@@ -661,12 +661,16 @@ extern uschar *host_lookup_order;      /* Order of host lookup types */
 extern uschar *host_lookup_msg;        /* Text for why it failed */
 extern int     host_number;            /* For sharing spools */
 extern uschar *host_number_string;     /* For expanding */
-extern uschar *hosts_require_helo;     /* check for HELO/EHLO before MAIL */
 extern uschar *host_reject_connection; /* Reject these hosts */
-extern tree_node *hostlist_anchor;     /* Tree of defined host lists */
-extern int     hostlist_count;         /* Number defined */
 extern uschar *hosts_connection_nolog; /* Limits the logging option */
+extern uschar *hosts_require_helo;     /* check for HELO/EHLO before MAIL */
 extern uschar *hosts_treat_as_local;   /* For routing */
+#ifdef EXPERIMENTAL_XCLIENT
+extern uschar *hosts_xclient;           /* Allow XCLIENT command for specified hosts */
+#endif
+extern tree_node *hostlist_anchor;     /* Tree of defined host lists */
+extern int     hostlist_count;         /* Number defined */
+


 extern int     ignore_bounce_errors_after; /* Keep them for this time. */
 extern BOOL    ignore_fromline_local;  /* Local SMTP ignore fromline */
@@ -828,7 +832,8 @@ extern int     proxy_external_port;    /* Port on remote interface of proxy */
 extern uschar *proxy_local_address;    /* IP of local interface of proxy */
 extern int     proxy_local_port;       /* Port on local interface of proxy */
 extern int     proxy_protocol_timeout; /* Timeout for proxy negotiation */
-extern BOOL    proxy_session;          /* TRUE if receiving mail from valid proxy  */
+extern BOOL    proxy_session;          /* TRUE if receiving mail from valid proxy
+                      or sending via one */
 #endif


 extern uschar *prvscheck_address;      /* Set during prvscheck expansion item */
diff --git a/src/src/host.c b/src/src/host.c
index 8d53eb3de..136ee8953 100644
--- a/src/src/host.c
+++ b/src/src/host.c
@@ -824,9 +824,9 @@ Returns:     pointer to character string
 */


uschar *
-host_ntoa(int type, const void *arg, uschar *buffer, int *portptr)
+host_ntoa(int type, const void * arg, uschar * buffer, int * portptr)
{
-uschar *yield;
+uschar * yield;

/* The new world. It is annoying that we have to fish out the address from
different places in the block, depending on what kind of address it is. It
diff --git a/src/src/macro_predef.c b/src/src/macro_predef.c
index 0053cb245..8fade68ca 100644
--- a/src/src/macro_predef.c
+++ b/src/src/macro_predef.c
@@ -205,6 +205,9 @@ due to conflicts with other common macros. */
#ifndef DISABLE_TLS_RESUME
builtin_macro_create(US"_HAVE_TLS_RESUME");
#endif
+#ifdef EXPERIMENTAL_XCLIENT
+ builtin_macro_create(US"_HAVE_XCLIENT");
+#endif

 #ifdef LOOKUP_LSEARCH
   builtin_macro_create(US"_HAVE_LOOKUP_LSEARCH");
diff --git a/src/src/macros.h b/src/src/macros.h
index 36ed185ed..c55276332 100644
--- a/src/src/macros.h
+++ b/src/src/macros.h
@@ -822,7 +822,11 @@ most recent SMTP commands. SCH_NONE is "empty". */
 enum { SCH_NONE, SCH_AUTH, SCH_DATA, SCH_BDAT,
        SCH_EHLO, SCH_ETRN, SCH_EXPN, SCH_HELO,
        SCH_HELP, SCH_MAIL, SCH_NOOP, SCH_QUIT, SCH_RCPT, SCH_RSET, SCH_STARTTLS,
-       SCH_VRFY };
+       SCH_VRFY,
+#ifdef EXPERIMENTAL_XCLIENT
+       SCH_XCLIENT,
+#endif
+       };


/* Returns from host_find_by{name,dns}() */

diff --git a/src/src/readconf.c b/src/src/readconf.c
index 3b26e87d5..7d48f085d 100644
--- a/src/src/readconf.c
+++ b/src/src/readconf.c
@@ -187,6 +187,9 @@ static optionlist optionlist_config[] = {
 #endif
   { "hosts_require_helo",       opt_stringptr,   {&hosts_require_helo} },
   { "hosts_treat_as_local",     opt_stringptr,   {&hosts_treat_as_local} },
+#ifdef EXPERIMENTAL_XCLIENT
+  { "hosts_xclient",        opt_stringptr,     {&hosts_xclient} },
+#endif
 #ifdef LOOKUP_IBASE
   { "ibase_servers",            opt_stringptr,   {&ibase_servers} },
 #endif
@@ -399,7 +402,7 @@ static optionlist optionlist_config[] = {
   { "uucp_from_pattern",        opt_stringptr,   {&uucp_from_pattern} },
   { "uucp_from_sender",         opt_stringptr,   {&uucp_from_sender} },
   { "warn_message_file",        opt_stringptr,   {&warn_message_file} },
-  { "write_rejectlog",          opt_bool,        {&write_rejectlog} }
+  { "write_rejectlog",          opt_bool,        {&write_rejectlog} },
 };


 #ifndef MACRO_PREDEF
diff --git a/src/src/smtp_in.c b/src/src/smtp_in.c
index 7a45772ce..6f4ad9495 100644
--- a/src/src/smtp_in.c
+++ b/src/src/smtp_in.c
@@ -75,6 +75,9 @@ enum {
   ETRN_CMD,                     /* This by analogy with TURN from the RFC */
   STARTTLS_CMD,                 /* Required by the STARTTLS RFC */
   TLS_AUTH_CMD,            /* auto-command at start of SSL */
+#ifdef EXPERIMENTAL_XCLIENT
+  XCLIENT_CMD,            /* per xlexkiro implementation */
+#endif


/* This is a dummy to identify the non-sync commands when pipelining */

@@ -189,14 +192,22 @@ count of non-mail commands and possibly provoke an error.
tls_auth is a pseudo-command, never expected in input. It is activated
on TLS startup and looks for a tls authenticator. */

-enum {    CL_RSET, CL_HELO, CL_EHLO, CL_AUTH,
+enum {
+    CL_RSET = 0,
+    CL_HELO,
+    CL_EHLO,
+    CL_AUTH,
 #ifndef DISABLE_TLS
-    CL_STLS, CL_TLAU,
+    CL_STLS,
+    CL_TLAU,
+#endif
+#ifdef EXPERIMENTAL_XCLIENT
+    CL_XCLI,
 #endif
 };


 static smtp_cmd_list cmd_list[] = {
-  /* name         len                     cmd     has_arg is_mail_cmd */
+  /*             name         len                     cmd     has_arg is_mail_cmd */


   [CL_RSET] = { "rset",       sizeof("rset")-1,       RSET_CMD,    FALSE, FALSE },  /* First */
   [CL_HELO] = { "helo",       sizeof("helo")-1,       HELO_CMD, TRUE,  FALSE },
@@ -206,8 +217,9 @@ static smtp_cmd_list cmd_list[] = {
   [CL_STLS] = { "starttls",   sizeof("starttls")-1,   STARTTLS_CMD, FALSE, FALSE },
   [CL_TLAU] = { "tls_auth",   0,                      TLS_AUTH_CMD, FALSE, FALSE },
 #endif
-
-/* If you change anything above here, also fix the definitions below. */
+#ifdef EXPERIMENTAL_XCLIENT
+  [CL_XCLI] = { "xclient",    sizeof("xclient")-1,    XCLIENT_CMD, TRUE,  FALSE },
+#endif


{ "mail from:", sizeof("mail from:")-1, MAIL_CMD, TRUE, TRUE },
{ "rcpt to:", sizeof("rcpt to:")-1, RCPT_CMD, TRUE, TRUE },
@@ -241,6 +253,9 @@ uschar * smtp_names[] =
[SCH_RSET] = US"RSET",
[SCH_STARTTLS] = US"STARTTLS",
[SCH_VRFY] = US"VRFY",
+#ifdef EXPERIMENTAL_XCLIENT
+ [SCH_XCLIENT] = US"XCLIENT",
+#endif
};

static uschar *protocols_local[] = {
@@ -1260,6 +1275,7 @@ return OTHER_CMD;



+
 /*************************************************
 *          Forced closedown of call              *
 *************************************************/
@@ -1774,7 +1790,6 @@ while (done <= 0)
       bsmtp_transaction_linecount = receive_linecount;
       break;


-
     /* The MAIL FROM command requires an address as an operand. All we
     do here is to parse it for syntactic correctness. The form "<>" is
     a special case which converts into an empty string. The start/end
@@ -4178,7 +4193,13 @@ while (done <= 0)
       fl.tls_advertised = TRUE;
       }
 #endif
-
+#ifdef EXPERIMENTAL_XCLIENT
+    if (proxy_session || verify_check_host(&hosts_xclient) != FAIL)
+      {
+      g = string_catn(g, smtp_code, 3);
+      g = xclient_smtp_advertise_str(g);
+      }
+#endif
 #ifndef DISABLE_PRDR
     /* Per Recipient Data Response, draft by Eric A. Hall extending RFC */
     if (prdr_enable)
@@ -4244,6 +4265,41 @@ while (done <= 0)
       toomany = FALSE;
       break;   /* HELO/EHLO */


+#ifdef EXPERIMENTAL_XCLIENT
+    case XCLIENT_CMD:
+      {
+      BOOL fatal = fl.helo_seen;
+      uschar * errmsg;
+      int resp;
+
+      HAD(SCH_XCLIENT);
+      smtp_mailcmd_count++;
+
+      if ((errmsg = xclient_smtp_command(smtp_cmd_data, &resp, &fatal)))
+    if (fatal)
+      done = synprot_error(L_smtp_syntax_error, resp, NULL, errmsg);
+    else
+      {
+      smtp_printf("%d %s\r\n", FALSE, resp, errmsg);
+      log_write(0, LOG_MAIN|LOG_REJECT, "rejected XCLIENT from %s: %s",
+        host_and_ident(FALSE), errmsg);
+      }
+      else
+    {
+    fl.helo_seen = FALSE;            /* Require another EHLO */
+    smtp_code = string_sprintf("%d", resp);
+
+    /*XXX unclear in spec. if this needs to be an ESMTP banner,
+    nor whether we get the original client's HELO after (or a proxy fake).
+    We require that we do; the following HELO/EHLO handling will set
+    sender_helo_name as normal. */
+
+    smtp_printf("%s XCLIENT success\r\n", FALSE, smtp_code);
+    }
+      break; /* XCLIENT */
+      }
+#endif
+


     /* The MAIL command requires an address as an operand. All we do
     here is to parse it for syntactic correctness. The form "<>" is
@@ -5353,6 +5409,10 @@ while (done <= 0)
       if (acl_smtp_etrn) smtp_printf(" ETRN", TRUE);
       if (acl_smtp_expn) smtp_printf(" EXPN", TRUE);
       if (acl_smtp_vrfy) smtp_printf(" VRFY", TRUE);
+#ifdef EXPERIMENTAL_XCLIENT
+      if (proxy_session || verify_check_host(&hosts_xclient) != FAIL)
+    smtp_printf(" XCLIENT", TRUE);
+#endif
       smtp_printf("\r\n", FALSE);
       break;


diff --git a/src/src/xclient.c b/src/src/xclient.c
new file mode 100644
index 000000000..2a8be9b0e
--- /dev/null
+++ b/src/src/xclient.c
@@ -0,0 +1,299 @@
+/*************************************************
+*     Exim - an Internet mail transport agent    *
+*************************************************/
+
+/* Copyright (c) The Exim Maintainers 2023 */
+/* See the file NOTICE for conditions of use and distribution. */
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+
+#include "exim.h"
+
+#ifdef EXPERIMENTAL_XCLIENT
+
+/* From https://www.postfix.org/XCLIENT_README.html I infer two generations of
+protocol.  The more recent one obviates the utility of the HELO attribute, since
+it mandates the proxy always sending a HELO/EHLO smtp command following (a
+successful) XCLIENT command, and that will carry a NELO name (which we assume,
+though it isn't specified, will be the actual one presented to the proxy by the
+possibly-new client).  The same applies to the PROTO attribute. */
+
+# define XCLIENT_V2
+
+enum xclient_cmd_e {
+  XCLIENT_CMD_UNKNOWN,
+  XCLIENT_CMD_ADDR,
+  XCLIENT_CMD_NAME,
+  XCLIENT_CMD_PORT,
+  XCLIENT_CMD_LOGIN,
+  XCLIENT_CMD_DESTADDR,
+  XCLIENT_CMD_DESTPORT,
+# ifdef XCLIENT_V1
+  XCLIENT_CMD_HELO,
+  XCLIENT_CMD_PROTO,
+# endif
+};
+
+struct xclient_cmd {
+  const uschar *    str;
+  unsigned        len;
+} xclient_cmds[] = {
+  [XCLIENT_CMD_UNKNOWN] = { NULL },
+  [XCLIENT_CMD_ADDR] =    { US"ADDR",  4 },
+  [XCLIENT_CMD_NAME] =    { US"NAME",  4 },
+  [XCLIENT_CMD_PORT] =    { US"PORT",  4 },
+  [XCLIENT_CMD_LOGIN] =    { US"LOGIN", 5 },
+  [XCLIENT_CMD_DESTADDR] =    { US"DESTADDR", 8 },
+  [XCLIENT_CMD_DESTPORT] =    { US"DESTPORT", 8 },
+# ifdef XCLIENT_V1
+  [XCLIENT_CMD_HELO] =    { US"HELO",  4 },
+  [XCLIENT_CMD_PROTO] =    { US"PROTO", 5 },
+# endif
+};
+
+/*************************************************
+*          XCLIENT proxy implementation          *
+*************************************************/
+
+/* Arguments:
+  code        points to the coded string
+  end         points to the end of coded string
+  ptr         where to put the pointer to the result, which is in
+              dynamic store
+Returns:      the number of bytes in the result, excluding the final zero;
+              -1 if the input is malformed
+*/
+
+static int
+xclient_xtextdecode(uschar * code, uschar * end, uschar ** ptr)
+{
+return auth_xtextdecode(string_copyn(code, end-code), ptr);
+}
+
+/*************************************************
+*   Check XCLIENT line and set sender_address    *
+*************************************************/
+
+
+/* Check the format of a XCLIENT line.
+Arguments:
+  s           the data portion of the line (already past any white space)
+  resp        result: smtp respose code
+  flag        input: helo seen  output: fail is fatal
+
+Return: NULL on success, or error message
+*/
+
+# define XCLIENT_UNAVAIL     US"[UNAVAILABLE]"
+# define XCLIENT_TEMPUNAVAIL US"[TEMPUNAVAIL]"
+
+uschar *
+xclient_smtp_command(uschar * s, int * resp, BOOL * flag)
+{
+uschar * word = s;
+enum {
+  XCLIENT_READ_COMMAND = 0,
+  XCLIENT_READ_VALUE,
+  XCLIENT_SKIP_SPACES
+} state = XCLIENT_SKIP_SPACES;
+enum xclient_cmd_e cmd;
+
+if (  !flag
+   && verify_check_host(&hosts_require_helo) == OK)
+  {
+  *resp = 503;
+  *flag = FALSE;
+  return US"no HELO/EHLO given";
+  }
+
+/* If already in a proxy session, do not re-check permission.
+Strictly we should avoid doing this for a Proxy-Protocol
+session to avoid mixups. */
+
+if(!proxy_session && verify_check_host(&hosts_xclient) == FAIL)
+  {
+  *resp = 550;
+  *flag = TRUE;
+  return US"XCLIENT command used when not advertised";
+  }
+
+if (sender_address)
+  {
+  *resp = 503;
+  *flag = FALSE;
+  return US"mail transaction in progress";
+  }
+
+if (!*word)
+  {
+  s = US"XCLIENT must have at least one operand";
+  goto fatal_501;
+  }
+
+for (state = XCLIENT_SKIP_SPACES; *s; )
+  switch (state)
+    {
+    case XCLIENT_READ_COMMAND:
+      {
+      int len;
+
+      word = s;
+      while (*s && *s != '=') s++;
+      len = s - word;
+      if (!*s)
+    {
+    s = string_sprintf("XCLIENT: missing value for parameter '%.*s'",
+              len, word);
+    goto fatal_501;
+    }
+
+      DEBUG(D_transport) debug_printf(" XCLIENT: cmd %.*s\n", len, word);
+      cmd = XCLIENT_CMD_UNKNOWN;
+      for (struct xclient_cmd * x = xclient_cmds + 1;
+       x < xclient_cmds + nelem(xclient_cmds); x++)
+    if (len == x->len && strncmpic(word, x->str, len) == 0)
+      {
+      cmd = x - xclient_cmds;
+      break;
+      }
+      if (cmd == XCLIENT_CMD_UNKNOWN)
+    {
+    s = string_sprintf("XCLIENT: unrecognised parameter '%.*s'",
+              len, word);
+    goto fatal_501;
+    }
+      state = XCLIENT_READ_VALUE;
+      }
+      break;
+
+    case XCLIENT_READ_VALUE:
+      {
+      int old_pool = store_pool;
+      int len;
+      uschar * val;
+
+      word = ++s;            /* skip the = */
+      while (*s && !isspace(*s)) s++;
+      len = s - word;
+
+      DEBUG(D_transport) debug_printf(" XCLIENT: \tvalue %.*s\n", len, word);
+      if (len == 0)
+    { s = US"XCLIENT: zero-length value for param"; goto fatal_501; }
+
+      if (  len == 13
+     && (  strncmpic(word, XCLIENT_UNAVAIL, 13) == 0
+        || strncmpic(word, XCLIENT_TEMPUNAVAIL, 13) == 0
+     )  )
+    val = NULL;
+
+      else if ((len = xclient_xtextdecode(word, s, &val)) == -1)
+    {
+    s = string_sprintf("failed xtext decode for XCLIENT: '%.*s'", len, word);
+    goto fatal_501;
+    }
+
+      store_pool = POOL_PERM;
+      switch (cmd)
+    {
+    case XCLIENT_CMD_ADDR:
+      proxy_local_address = sender_host_address;
+      sender_host_address = val ? string_copyn(val, len) : NULL;
+      break;
+    case XCLIENT_CMD_NAME:
+      sender_host_name = val ? string_copyn(val, len) : NULL;
+      break;
+    case XCLIENT_CMD_PORT:
+      proxy_local_port = sender_host_port;
+      sender_host_port = val ? Uatoi(val) : 0;
+      break;
+    case XCLIENT_CMD_DESTADDR:
+      proxy_external_address = val ? string_copyn(val, len) : NULL;
+      break;
+    case XCLIENT_CMD_DESTPORT:
+      proxy_external_port = val ? Uatoi(val) : 0;
+      break;
+
+    case XCLIENT_CMD_LOGIN:
+      if (val)
+        {
+        authenticated_id = string_copyn(val, len);
+        sender_host_authenticated = US"xclient";
+        authentication_failed = FALSE;
+        }
+      else
+        {
+        authenticated_id = NULL;
+        sender_host_authenticated = NULL;
+        }
+      break;
+
+# ifdef XCLIENT_V1
+    case XCLIENT_CMD_HELO:
+      sender_helo_name = val ? string_copyn(val, len) : NULL;
+      break;
+    case XCLIENT_CMD_PROTO:
+      if (!val)
+        { store_pool = old_pool; s = US"missing proto for XCLIENT"; goto fatal_501; }
+      else if (len == 4 && strncmpic(val, US"SMTP", 4) == 0)
+        *esmtpflag = FALSE;    /* function arg */
+      else if (len == 5 && strncmpic(val, US"ESMTP", 5) == 0)
+        *esmtpflag = TRUE;
+      else
+        { store_pool = old_pool; s = US"bad proto for XCLIENT"; goto fatal_501; }
+      break;
+# endif
+    }
+      store_pool = old_pool;
+      state = XCLIENT_SKIP_SPACES;
+      break;
+      }
+
+    case XCLIENT_SKIP_SPACES:
+      while (*s && isspace (*s)) s++;
+      state = XCLIENT_READ_COMMAND;
+      break;
+
+    default:
+      s = US"unhandled XCLIENT parameter type";
+      goto fatal_501;
+    }
+
+if (!proxy_local_address)
+  { s = US"missing ADDR for XCLIENT"; goto fatal_501; }
+if (!proxy_local_port)
+  { s = US"missing PORT for XCLIENT"; goto fatal_501; }
+if (state != XCLIENT_SKIP_SPACES)
+  { s = US"bad state parsing XCLIENT parameters"; goto fatal_501; }
+
+host_build_sender_fullhost();
+proxy_session = TRUE;
+*resp = 220;
+return NULL;
+
+fatal_501:
+  *flag = TRUE;
+  *resp = 501;
+  return s;
+}
+
+# undef XCLIENT_UNAVAIL
+# undef XCLIENT_TEMPUNAVAIL
+
+
+gstring *
+xclient_smtp_advertise_str(gstring * g)
+{
+g = string_catn(g, US"-XCLIENT ", 8);
+for (int i = 1; i < nelem(xclient_cmds); i++)
+  {
+  g = string_catn(g, US" ", 1);
+  g = string_cat(g, xclient_cmds[i].str);
+  }
+return string_catn(g, US"\r\n", 2);
+}
+
+
+#endif    /*EXPERIMENTAL_XCLIENT*/
+
+/* vi: aw ai sw=2
+*/
+/* End of xclient.c */
diff --git a/test/confs/4032 b/test/confs/4032
new file mode 100644
index 000000000..9dbe36b9c
--- /dev/null
+++ b/test/confs/4032
@@ -0,0 +1,41 @@
+# Exim test configuration 4032
+# XCLIENT proxy
+
+.ifdef OPTION
+.include DIR/aux-var/tls_conf_prefix
+.else
+.include DIR/aux-var/std_conf_prefix
+.endif
+
+primary_hostname = myhost.test.ex
+hosts_xclient = HOSTIPV4
+queue_only
+
+# ----- Main settings -----
+
+log_selector = +proxy +incoming_port
+
+acl_smtp_rcpt = r_acl
+
+
+begin acl
+
+r_acl:
+  accept
+    logwrite = tls session:   ${if def:tls_in_cipher {yes}{no}}
+    logwrite = proxy session: $proxy_session
+    logwrite = local          [$received_ip_address]:$received_port
+    logwrite = proxy internal [$proxy_local_address]:$proxy_local_port
+    logwrite = proxy external [$proxy_external_address]:$proxy_external_port
+    logwrite = remote         [$sender_host_address]:$sender_host_port
+
+
+# ----- Routers -----
+
+begin routers
+
+dump:
+  driver = redirect
+  data = :blackhole:
+
+# End
diff --git a/test/confs/4033 b/test/confs/4033
new file mode 120000
index 000000000..06b9789ad
--- /dev/null
+++ b/test/confs/4033
@@ -0,0 +1 @@
+4032
\ No newline at end of file
diff --git a/test/confs/4034 b/test/confs/4034
new file mode 120000
index 000000000..06b9789ad
--- /dev/null
+++ b/test/confs/4034
@@ -0,0 +1 @@
+4032
\ No newline at end of file
diff --git a/test/log/4032 b/test/log/4032
new file mode 100644
index 000000000..b02c0c263
--- /dev/null
+++ b/test/log/4032
@@ -0,0 +1,27 @@
+
+******** SERVER ********
+1999-03-02 09:44:33 exim x.yz daemon started: pid=p1234, no queue runs, listening for SMTP on port PORT_D
+1999-03-02 09:44:33 tls session:   no
+1999-03-02 09:44:33 proxy session: no
+1999-03-02 09:44:33 local          [127.0.0.1]:1113
+1999-03-02 09:44:33 proxy internal []:0
+1999-03-02 09:44:33 proxy external []:0
+1999-03-02 09:44:33 remote         [127.0.0.1]:1114
+1999-03-02 09:44:33 10HmaX-0005vi-00 <= a@??? H=(plainclient) [127.0.0.1]:1114 P=esmtp S=sss
+1999-03-02 09:44:33 tls session:   no
+1999-03-02 09:44:33 proxy session: yes
+1999-03-02 09:44:33 local          [ip4.ip4.ip4.ip4]:1113
+1999-03-02 09:44:33 proxy internal [ip4.ip4.ip4.ip4]:1115
+1999-03-02 09:44:33 proxy external [10.42.42.42]:1116
+1999-03-02 09:44:33 remote         [127.0.0.2]:1117
+1999-03-02 09:44:33 10HmaY-0005vi-00 <= c@??? H=proxylookedupname.net (clienthelo) [127.0.0.2]:1117 P=esmtpa A=xclient:hisloginname PRX=ip4.ip4.ip4.ip4 S=sss
+1999-03-02 09:44:33 tls session:   no
+1999-03-02 09:44:33 proxy session: yes
+1999-03-02 09:44:33 local          [ip4.ip4.ip4.ip4]:1113
+1999-03-02 09:44:33 proxy internal [127.0.0.2]:1117
+1999-03-02 09:44:33 proxy external [10.42.42.42]:1116
+1999-03-02 09:44:33 remote         [127.0.0.3]:1111
+1999-03-02 09:44:33 10HmaZ-0005vi-00 <= c2@??? H=(anotherhelo) [127.0.0.3]:1111 P=esmtp PRX=127.0.0.2 S=sss
+1999-03-02 09:44:33 rejected XCLIENT from (anotherhelo) [127.0.0.3]:1111: mail transaction in progress
+1999-03-02 09:44:33 rejected MAIL from miss.ehlo.after.xclient (anotherhelo) [127.0.0.3]:1111: no HELO/EHLO given
+1999-03-02 09:44:33 SMTP call from (xclientproxy) [ip4.ip4.ip4.ip4]:1112 dropped: too many syntax or protocol errors (last command was "XCLIENT SIXSIX=",  C=EHLO,XCLIENT,XCLIENT,XCLIENT,XCLIENT)
diff --git a/test/log/4034 b/test/log/4034
new file mode 100644
index 000000000..9ec307501
--- /dev/null
+++ b/test/log/4034
@@ -0,0 +1,29 @@
+
+******** SERVER ********
+1999-03-02 09:44:33 Warning: No server certificate defined; will use a selfsigned one.
+ Suggested action: either install a certificate or change tls_advertise_hosts option
+1999-03-02 09:44:33 exim x.yz daemon started: pid=p1234, no queue runs, listening for SMTP on port PORT_D
+1999-03-02 09:44:33 tls session:   yes
+1999-03-02 09:44:33 proxy session: no
+1999-03-02 09:44:33 local          [127.0.0.1]:1113
+1999-03-02 09:44:33 proxy internal []:0
+1999-03-02 09:44:33 proxy external []:0
+1999-03-02 09:44:33 remote         [127.0.0.1]:1114
+1999-03-02 09:44:33 10HmaX-0005vi-00 <= a@??? H=(plainclient) [127.0.0.1]:1114 P=esmtps X=TLS1.x:ke-RSA-AES256-SHAnnn:xxx CV=no S=sss
+1999-03-02 09:44:33 tls session:   yes
+1999-03-02 09:44:33 proxy session: yes
+1999-03-02 09:44:33 local          [ip4.ip4.ip4.ip4]:1113
+1999-03-02 09:44:33 proxy internal [ip4.ip4.ip4.ip4]:1115
+1999-03-02 09:44:33 proxy external [10.42.42.42]:1116
+1999-03-02 09:44:33 remote         [127.0.0.2]:1117
+1999-03-02 09:44:33 10HmaY-0005vi-00 <= c@??? H=proxylookedupname.net (clienthelo) [127.0.0.2]:1117 P=esmtpsa X=TLS1.x:ke-RSA-AES256-SHAnnn:xxx CV=no A=xclient:hisloginname PRX=ip4.ip4.ip4.ip4 S=sss
+1999-03-02 09:44:33 tls session:   yes
+1999-03-02 09:44:33 proxy session: yes
+1999-03-02 09:44:33 local          [ip4.ip4.ip4.ip4]:1113
+1999-03-02 09:44:33 proxy internal [127.0.0.2]:1117
+1999-03-02 09:44:33 proxy external [10.42.42.42]:1116
+1999-03-02 09:44:33 remote         [127.0.0.3]:1111
+1999-03-02 09:44:33 10HmaZ-0005vi-00 <= c2@??? H=(anotherhelo) [127.0.0.3]:1111 P=esmtps X=TLS1.x:ke-RSA-AES256-SHAnnn:xxx CV=no PRX=127.0.0.2 S=sss
+1999-03-02 09:44:33 rejected XCLIENT from (anotherhelo) [127.0.0.3]:1111: mail transaction in progress
+1999-03-02 09:44:33 rejected MAIL from miss.ehlo.after.xclient (anotherhelo) [127.0.0.3]:1111: no HELO/EHLO given
+1999-03-02 09:44:33 SMTP call from (xclientproxy) [ip4.ip4.ip4.ip4]:1112 dropped: too many syntax or protocol errors (last command was "XCLIENT SIXSIX=",  C=EHLO,STARTTLS,EHLO,XCLIENT,XCLIENT,XCLIENT,XCLIENT)
diff --git a/test/rejectlog/4032 b/test/rejectlog/4032
new file mode 100644
index 000000000..96c50b75e
--- /dev/null
+++ b/test/rejectlog/4032
@@ -0,0 +1,5 @@
+
+******** SERVER ********
+1999-03-02 09:44:33 rejected XCLIENT from (anotherhelo) [127.0.0.3]:1111: mail transaction in progress
+1999-03-02 09:44:33 rejected MAIL from miss.ehlo.after.xclient (anotherhelo) [127.0.0.3]:1111: no HELO/EHLO given
+1999-03-02 09:44:33 SMTP call from (xclientproxy) [ip4.ip4.ip4.ip4]:1112 dropped: too many syntax or protocol errors (last command was "XCLIENT SIXSIX=",  C=EHLO,XCLIENT,XCLIENT,XCLIENT,XCLIENT)
diff --git a/test/rejectlog/4034 b/test/rejectlog/4034
new file mode 100644
index 000000000..de477a533
--- /dev/null
+++ b/test/rejectlog/4034
@@ -0,0 +1,5 @@
+
+******** SERVER ********
+1999-03-02 09:44:33 rejected XCLIENT from (anotherhelo) [127.0.0.3]:1111: mail transaction in progress
+1999-03-02 09:44:33 rejected MAIL from miss.ehlo.after.xclient (anotherhelo) [127.0.0.3]:1111: no HELO/EHLO given
+1999-03-02 09:44:33 SMTP call from (xclientproxy) [ip4.ip4.ip4.ip4]:1112 dropped: too many syntax or protocol errors (last command was "XCLIENT SIXSIX=",  C=EHLO,STARTTLS,EHLO,XCLIENT,XCLIENT,XCLIENT,XCLIENT)
diff --git a/test/runtest b/test/runtest
index 8d96e13bd..53e12d412 100755
--- a/test/runtest
+++ b/test/runtest
@@ -1387,6 +1387,9 @@ RESET_AFTER_EXTRA_LINE_READ:
       }
     next if / in limits_advertise_hosts?\? no \(matched "!\*"\)/;


+    # Experimental_XCLIENT
+    next if / in hosts_xclient?\? no \(option unset\)/;
+
     # TCP Fast Open
     next if /^(ppppp )?setsockopt FASTOPEN: Network Error/;


diff --git a/test/scripts/4032-xclient/4032 b/test/scripts/4032-xclient/4032
new file mode 100644
index 000000000..fa0d0b8c3
--- /dev/null
+++ b/test/scripts/4032-xclient/4032
@@ -0,0 +1,140 @@
+# XCLIENT proxy on inbound smtp
+#
+munge loopback
+#
+exim -bd -DSERVER=server -oX PORT_D
+****
+#
+### (1) non-prox plain receive (not advertised) (2) XCLIENT refules when not advertised
+client 127.0.0.1 PORT_D
+??? 220
+EHLO plainclient
+??? 250-
+??? 250-SIZE
+??? 250-8BITMIME
+??? 250-PIPELINING
+??? 250 HELP
+MAIL FROM:<a@???>
+??? 250
+RCPT TO:<b@???>
+??? 250
+DATA
+??? 354
+Subject: test
+
+body
+.
+??? 250
+XCLIENT NAME=proxylookedupname.net ADDR=127.0.0.2 PORT=4242
+??? 550
+QUIT
+??? 221
+****
+#
+### receive, (1) fully loaded (2) new conn (3) bad: transaction in progress (4) bad: missing EHLO after XCLIENT
+client HOSTIPV4 PORT_D
+??? 220
+EHLO xclientproxy
+??? 250-
+??? 250-SIZE
+??? 250-8BITMIME
+??? 250-PIPELINING
+??? 250-XCLIENT
+??? 250 HELP
+XCLIENT NAME=proxylookedupname.net ADDR=127.0.0.2 PORT=4242 DESTADDR=10.42.42.42 DESTPORT=25 LOGIN=hisloginname
+??? 220
+EHLO clienthelo
+??? 250-
+??? 250-SIZE
+??? 250-8BITMIME
+??? 250-PIPELINING
+??? 250-XCLIENT
+??? 250 HELP
+MAIL FROM:<c@???>
+??? 250
+RCPT TO:<d@???>
+??? 250
+DATA
+??? 354
+Subject: test
+
+body
+.
+??? 250
+XCLIENT NAME=[TEMPUNAVAIL] ADDR=127.0.0.3 PORT=4243 LOGIN=[UNAVAILABLE]
+??? 220
+EHLO anotherhelo
+??? 250-
+??? 250-SIZE
+??? 250-8BITMIME
+??? 250-PIPELINING
+??? 250-XCLIENT
+??? 250 HELP
+MAIL FROM:<c2@???>
+??? 250
+RCPT TO:<d2@???>
+??? 250
+DATA
+??? 354
+Subject: test
+
+body
+.
+??? 250
+MAIL FROM:<c2@???>
+??? 250
+XCLIENT NAME=bad.time.for.xclient
+??? 503
+RSET
+??? 250
+XCLIENT NAME=miss.ehlo.after.xclient
+??? 220
+MAIL FROM:<bad@???>
+??? 503
+QUIT
+??? 221
+****
+#
+###          (5) no operands to XCLIENT (6,7) unrecognised operands
+client HOSTIPV4 PORT_D
+??? 220
+EHLO xclientproxy
+??? 250-
+??? 250-SIZE
+??? 250-8BITMIME
+??? 250-PIPELINING
+??? 250-XCLIENT
+??? 250 HELP
+XCLIENT
+??? 501
+XCLIENT NONO=
+??? 501
+XCLIENT NAMEfoobar=
+??? 501
+XCLIENT SIXSIX=
+??? 501-
+??? 501 Too many
+???*
+****
+#
+###          (7) operand with zero-len value (8) operand with no value
+client HOSTIPV4 PORT_D
+??? 220
+EHLO xclientproxy
+??? 250-
+??? 250-SIZE
+??? 250-8BITMIME
+??? 250-PIPELINING
+??? 250-XCLIENT
+??? 250 HELP
+XCLIENT NAME=
+??? 501
+XCLIENT NAME
+??? 501
+****
+#
+#
+killdaemon
+no_msglog_check
+no_stdout_check
+no_stderr_check
diff --git a/test/scripts/4032-xclient/4033 b/test/scripts/4032-xclient/4033
new file mode 100644
index 000000000..f3a4ecdeb
--- /dev/null
+++ b/test/scripts/4032-xclient/4033
@@ -0,0 +1,62 @@
+# XCLIENT proxy on inbound -bh
+#
+### (1) non-prox plain receive (not advertised) (2) XCLIENT refules when not advertised
+exim -bh 127.0.0.1.4241 -oMi 127.0.0.1
+EHLO plainclient
+MAIL FROM:<a@???>
+RCPT TO:<b@???>
+DATA
+Subject: test
+
+body
+.
+XCLIENT NAME=proxylookedupname.net ADDR=127.0.0.2 PORT=4242
+QUIT
+****
+#
+### receive, (1) fully loaded (2) new conn (3) bad: transaction in progress
+exim -bh HOSTIPV4.4241 -oMi HOSTIPV4
+EHLO xclientproxy
+XCLIENT NAME=proxylookedupname.net ADDR=127.0.0.2 PORT=4242 DESTADDR=10.42.42.42 DESTPORT=25 LOGIN=hisloginname
+EHLO clienthelo
+MAIL FROM:<c@???>
+RCPT TO:<d@???>
+DATA
+Subject: test
+
+body
+.
+XCLIENT NAME=[TEMPUNAVAIL] ADDR=127.0.0.3 PORT=4243 LOGIN=[UNAVAILABLE]
+EHLO anotherhelo
+MAIL FROM:<c2@???>
+RCPT TO:<d2@???>
+DATA
+Subject: test
+
+body
+.
+MAIL FROM:<c2@???>
+XCLIENT NAME=bad.time.for.xclient
+RSET
+XCLIENT NAME=miss.ehlo.after.xclient
+MAIL FROM:<bad@???>
+QUIT
+****
+#
+###          (4) no operands to XCLIENT (5,6) unrecognised operands
+exim -bh HOSTIPV4.4241 -oMi HOSTIPV4
+EHLO xclientproxy
+XCLIENT
+XCLIENT NONO=
+XCLIENT NAMEfoobar=
+XCLIENT SIXSIX=
+****
+#
+###          (7) operand with zero-len value (8) operand with no value
+exim -bh HOSTIPV4.4241 -oMi HOSTIPV4
+EHLO xclientproxy
+XCLIENT NAME=
+XCLIENT NAME
+****
+#
+no_stderr_check
diff --git a/test/scripts/4032-xclient/REQUIRES b/test/scripts/4032-xclient/REQUIRES
new file mode 100644
index 000000000..5f4d76eed
--- /dev/null
+++ b/test/scripts/4032-xclient/REQUIRES
@@ -0,0 +1 @@
+support Experimental_XCLIENT
diff --git a/test/scripts/4034-xclient-tls/4034 b/test/scripts/4034-xclient-tls/4034
new file mode 100644
index 000000000..c8a4f10c4
--- /dev/null
+++ b/test/scripts/4034-xclient-tls/4034
@@ -0,0 +1,179 @@
+# XCLIENT under TLS
+#
+munge loopback
+#
+exim -bd -DSERVER=server -DOPTION -oX PORT_D
+****
+#
+### (1) non-prox plain receive (not advertised) (2) XCLIENT refusal when not advertised
+client-anytls 127.0.0.1 PORT_D
+??? 220
+EHLO plainclient
+??? 250-
+??? 250-SIZE
+??? 250-8BITMIME
+??? 250-PIPELINING
+??? 250-STARTTLS
+??? 250 HELP
+STARTTLS
+??? 220
+EHLO plainclient
+??? 250-
+??? 250-SIZE
+??? 250-8BITMIME
+??? 250-PIPELINING
+??? 250 HELP
+MAIL FROM:<a@???>
+??? 250
+RCPT TO:<b@???>
+??? 250
+DATA
+??? 354
+Subject: test
+
+body
+.
+??? 250
+XCLIENT NAME=proxylookedupname.net ADDR=127.0.0.2 PORT=4242
+??? 550
+QUIT
+??? 221
+****
+#
+### receive, (1) fully loaded (2) new conn (3) bad: transaction in progress (4) bad: missing EHLO after XCLIENT
+client-anytls HOSTIPV4 PORT_D
+??? 220
+EHLO xclientproxy
+??? 250-
+??? 250-SIZE
+??? 250-8BITMIME
+??? 250-PIPELINING
+??? 250-STARTTLS
+??? 250-XCLIENT
+??? 250 HELP
+STARTTLS
+??? 220
+EHLO xclientproxy
+??? 250-
+??? 250-SIZE
+??? 250-8BITMIME
+??? 250-PIPELINING
+??? 250-XCLIENT
+??? 250 HELP
+XCLIENT NAME=proxylookedupname.net ADDR=127.0.0.2 PORT=4242 DESTADDR=10.42.42.42 DESTPORT=25 LOGIN=hisloginname
+??? 220
+EHLO clienthelo
+??? 250-
+??? 250-SIZE
+??? 250-8BITMIME
+??? 250-PIPELINING
+??? 250-XCLIENT
+??? 250 HELP
+MAIL FROM:<c@???>
+??? 250
+RCPT TO:<d@???>
+??? 250
+DATA
+??? 354
+Subject: test
+
+body
+.
+??? 250
+XCLIENT NAME=[TEMPUNAVAIL] ADDR=127.0.0.3 PORT=4243 LOGIN=[UNAVAILABLE]
+??? 220
+EHLO anotherhelo
+??? 250-
+??? 250-SIZE
+??? 250-8BITMIME
+??? 250-PIPELINING
+??? 250-XCLIENT
+??? 250 HELP
+MAIL FROM:<c2@???>
+??? 250
+RCPT TO:<d2@???>
+??? 250
+DATA
+??? 354
+Subject: test
+
+body
+.
+??? 250
+MAIL FROM:<c2@???>
+??? 250
+XCLIENT NAME=bad.time.for.xclient
+??? 503
+RSET
+??? 250
+XCLIENT NAME=miss.ehlo.after.xclient
+??? 220
+MAIL FROM:<bad@???>
+??? 503
+QUIT
+??? 221
+****
+#
+###          (5) no operands to XCLIENT (6,7) unrecognised operands
+client-anytls HOSTIPV4 PORT_D
+??? 220
+EHLO xclientproxy
+??? 250-
+??? 250-SIZE
+??? 250-8BITMIME
+??? 250-PIPELINING
+??? 250-STARTTLS
+??? 250-XCLIENT
+??? 250 HELP
+STARTTLS
+??? 220
+EHLO xclientproxy
+??? 250-
+??? 250-SIZE
+??? 250-8BITMIME
+??? 250-PIPELINING
+??? 250-XCLIENT
+??? 250 HELP
+XCLIENT
+??? 501
+XCLIENT NONO=
+??? 501
+XCLIENT NAMEfoobar=
+??? 501
+XCLIENT SIXSIX=
+??? 501-
+??? 501 Too many
+???*
+****
+#
+###          (7) operand with zero-len value (8) operand with no value
+client-anytls HOSTIPV4 PORT_D
+??? 220
+EHLO xclientproxy
+??? 250-
+??? 250-SIZE
+??? 250-8BITMIME
+??? 250-PIPELINING
+??? 250-STARTTLS
+??? 250-XCLIENT
+??? 250 HELP
+STARTTLS
+??? 220
+EHLO xclientproxy
+??? 250-
+??? 250-SIZE
+??? 250-8BITMIME
+??? 250-PIPELINING
+??? 250-XCLIENT
+??? 250 HELP
+XCLIENT NAME=
+??? 501
+XCLIENT NAME
+??? 501
+****
+#
+#
+killdaemon
+no_msglog_check
+no_stdout_check
+no_stderr_check
diff --git a/test/scripts/4034-xclient-tls/REQUIRES b/test/scripts/4034-xclient-tls/REQUIRES
new file mode 100644
index 000000000..4361afb13
--- /dev/null
+++ b/test/scripts/4034-xclient-tls/REQUIRES
@@ -0,0 +1,2 @@
+support Experimental_XCLIENT
+feature _HAVE_TLS
diff --git a/test/stdout/4032 b/test/stdout/4032
new file mode 100644
index 000000000..41c916c0e
--- /dev/null
+++ b/test/stdout/4032
@@ -0,0 +1,199 @@
+### (1) non-prox plain receive (not advertised) (2) XCLIENT refules when not advertised
+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 plainclient
+??? 250-
+<<< 250-myhost.test.ex Hello plainclient [IP_LOOPBACK_ADDR]
+??? 250-SIZE
+<<< 250-SIZE 52428800
+??? 250-8BITMIME
+<<< 250-8BITMIME
+??? 250-PIPELINING
+<<< 250-PIPELINING
+??? 250 HELP
+<<< 250 HELP
+>>> MAIL FROM:<a@???>
+??? 250
+<<< 250 OK
+>>> RCPT TO:<b@???>
+??? 250
+<<< 250 Accepted
+>>> DATA
+??? 354
+<<< 354 Enter message, ending with "." on a line by itself
+>>> Subject: test
+>>> 
+>>> body
+>>> .
+??? 250
+<<< 250 OK id=10HmaX-0005vi-00
+>>> XCLIENT NAME=proxylookedupname.net ADDR=127.0.0.2 PORT=4242
+??? 550
+<<< 550 XCLIENT command used when not advertised
+>>> QUIT
+??? 221
+<<< 221 myhost.test.ex closing connection
+End of script
+### receive, (1) fully loaded (2) new conn (3) bad: transaction in progress (4) bad: missing EHLO after XCLIENT
+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 xclientproxy
+??? 250-
+<<< 250-myhost.test.ex Hello xclientproxy [ip4.ip4.ip4.ip4]
+??? 250-SIZE
+<<< 250-SIZE 52428800
+??? 250-8BITMIME
+<<< 250-8BITMIME
+??? 250-PIPELINING
+<<< 250-PIPELINING
+??? 250-XCLIENT
+<<< 250-XCLIENT ADDR NAME PORT LOGIN DESTADDR DESTPORT
+??? 250 HELP
+<<< 250 HELP
+>>> XCLIENT NAME=proxylookedupname.net ADDR=127.0.0.2 PORT=4242 DESTADDR=10.42.42.42 DESTPORT=25 LOGIN=hisloginname
+??? 220
+<<< 220 XCLIENT success
+>>> EHLO clienthelo
+??? 250-
+<<< 250-myhost.test.ex Hello proxylookedupname.net [127.0.0.2]
+??? 250-SIZE
+<<< 250-SIZE 52428800
+??? 250-8BITMIME
+<<< 250-8BITMIME
+??? 250-PIPELINING
+<<< 250-PIPELINING
+??? 250-XCLIENT
+<<< 250-XCLIENT ADDR NAME PORT LOGIN DESTADDR DESTPORT
+??? 250 HELP
+<<< 250 HELP
+>>> MAIL FROM:<c@???>
+??? 250
+<<< 250 OK
+>>> RCPT TO:<d@???>
+??? 250
+<<< 250 Accepted
+>>> DATA
+??? 354
+<<< 354 Enter message, ending with "." on a line by itself
+>>> Subject: test
+>>> 
+>>> body
+>>> .
+??? 250
+<<< 250 OK id=10HmaY-0005vi-00
+>>> XCLIENT NAME=[TEMPUNAVAIL] ADDR=127.0.0.3 PORT=4243 LOGIN=[UNAVAILABLE]
+??? 220
+<<< 220 XCLIENT success
+>>> EHLO anotherhelo
+??? 250-
+<<< 250-myhost.test.ex Hello anotherhelo [127.0.0.3]
+??? 250-SIZE
+<<< 250-SIZE 52428800
+??? 250-8BITMIME
+<<< 250-8BITMIME
+??? 250-PIPELINING
+<<< 250-PIPELINING
+??? 250-XCLIENT
+<<< 250-XCLIENT ADDR NAME PORT LOGIN DESTADDR DESTPORT
+??? 250 HELP
+<<< 250 HELP
+>>> MAIL FROM:<c2@???>
+??? 250
+<<< 250 OK
+>>> RCPT TO:<d2@???>
+??? 250
+<<< 250 Accepted
+>>> DATA
+??? 354
+<<< 354 Enter message, ending with "." on a line by itself
+>>> Subject: test
+>>> 
+>>> body
+>>> .
+??? 250
+<<< 250 OK id=10HmaZ-0005vi-00
+>>> MAIL FROM:<c2@???>
+??? 250
+<<< 250 OK
+>>> XCLIENT NAME=bad.time.for.xclient
+??? 503
+<<< 503 mail transaction in progress
+>>> RSET
+??? 250
+<<< 250 Reset OK
+>>> XCLIENT NAME=miss.ehlo.after.xclient
+??? 220
+<<< 220 XCLIENT success
+>>> MAIL FROM:<bad@???>
+??? 503
+<<< 503 HELO or EHLO required
+>>> QUIT
+??? 221
+<<< 221 myhost.test.ex closing connection
+End of script
+###          (5) no operands to XCLIENT (6,7) unrecognised operands
+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 xclientproxy
+??? 250-
+<<< 250-myhost.test.ex Hello xclientproxy [ip4.ip4.ip4.ip4]
+??? 250-SIZE
+<<< 250-SIZE 52428800
+??? 250-8BITMIME
+<<< 250-8BITMIME
+??? 250-PIPELINING
+<<< 250-PIPELINING
+??? 250-XCLIENT
+<<< 250-XCLIENT ADDR NAME PORT LOGIN DESTADDR DESTPORT
+??? 250 HELP
+<<< 250 HELP
+>>> XCLIENT
+??? 501
+<<< 501 XCLIENT must have at least one operand
+>>> XCLIENT NONO=
+??? 501
+<<< 501 XCLIENT: unrecognised parameter 'NONO'
+>>> XCLIENT NAMEfoobar=
+??? 501
+<<< 501 XCLIENT: unrecognised parameter 'NAMEfoobar'
+>>> XCLIENT SIXSIX=
+??? 501-
+<<< 501-XCLIENT: unrecognised parameter 'SIXSIX'
+??? 501 Too many
+<<< 501 Too many syntax or protocol errors
+???*
+Expected EOF read
+End of script
+###          (7) operand with zero-len value (8) operand with no value
+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 xclientproxy
+??? 250-
+<<< 250-myhost.test.ex Hello xclientproxy [ip4.ip4.ip4.ip4]
+??? 250-SIZE
+<<< 250-SIZE 52428800
+??? 250-8BITMIME
+<<< 250-8BITMIME
+??? 250-PIPELINING
+<<< 250-PIPELINING
+??? 250-XCLIENT
+<<< 250-XCLIENT ADDR NAME PORT LOGIN DESTADDR DESTPORT
+??? 250 HELP
+<<< 250 HELP
+>>> XCLIENT NAME=
+??? 501
+<<< 501 XCLIENT: zero-length value for param
+>>> XCLIENT NAME
+??? 501
+<<< 501 XCLIENT: missing value for parameter 'NAME'
+End of script
+
+******** SERVER ********
+### (1) non-prox plain receive (not advertised) (2) XCLIENT refules when not advertised
+### receive, (1) fully loaded (2) new conn (3) bad: transaction in progress (4) bad: missing EHLO after XCLIENT
+###          (5) no operands to XCLIENT (6,7) unrecognised operands
+###          (7) operand with zero-len value (8) operand with no value
diff --git a/test/stdout/4033 b/test/stdout/4033
new file mode 100644
index 000000000..546ca8b1a
--- /dev/null
+++ b/test/stdout/4033
@@ -0,0 +1,108 @@
+### (1) non-prox plain receive (not advertised) (2) XCLIENT refules when not advertised
+
+**** SMTP testing session as if from host 127.0.0.1
+**** but without any ident (RFC 1413) callback.
+**** This is not for real!
+
+220 myhost.test.ex ESMTP Exim x.yz Tue, 2 Mar 1999 09:44:33 +0000
+250-myhost.test.ex Hello plainclient [127.0.0.1]
+250-SIZE 52428800
+250-8BITMIME
+250-PIPELINING
+250 HELP
+250 OK
+250 Accepted
+354 Enter message, ending with "." on a line by itself
+250 OK id=10HmaX-0005vi-00
+
+**** SMTP testing: that is not a real message id!
+
+550 XCLIENT command used when not advertised
+221 myhost.test.ex closing connection
+### receive, (1) fully loaded (2) new conn (3) bad: transaction in progress
+
+**** SMTP testing session as if from host ip4.ip4.ip4.ip4
+**** but without any ident (RFC 1413) callback.
+**** This is not for real!
+
+220 myhost.test.ex ESMTP Exim x.yz Tue, 2 Mar 1999 09:44:33 +0000
+250-myhost.test.ex Hello xclientproxy [ip4.ip4.ip4.ip4]
+250-SIZE 52428800
+250-8BITMIME
+250-PIPELINING
+250-XCLIENT ADDR NAME PORT LOGIN DESTADDR DESTPORT
+250 HELP
+220 XCLIENT success
+250-myhost.test.ex Hello proxylookedupname.net [127.0.0.2]
+250-SIZE 52428800
+250-8BITMIME
+250-PIPELINING
+250-XCLIENT ADDR NAME PORT LOGIN DESTADDR DESTPORT
+250 HELP
+250 OK
+250 Accepted
+354 Enter message, ending with "." on a line by itself
+250 OK id=10HmaY-0005vi-00
+
+**** SMTP testing: that is not a real message id!
+
+220 XCLIENT success
+250-myhost.test.ex Hello anotherhelo [127.0.0.3]
+250-SIZE 52428800
+250-8BITMIME
+250-PIPELINING
+250-XCLIENT ADDR NAME PORT LOGIN DESTADDR DESTPORT
+250 HELP
+250 OK
+250 Accepted
+354 Enter message, ending with "." on a line by itself
+250 OK id=10HmaZ-0005vi-00
+
+**** SMTP testing: that is not a real message id!
+
+250 OK
+503 mail transaction in progress
+250 Reset OK
+220 XCLIENT success
+503 HELO or EHLO required
+221 myhost.test.ex closing connection
+###          (4) no operands to XCLIENT (5,6) unrecognised operands
+
+**** SMTP testing session as if from host ip4.ip4.ip4.ip4
+**** but without any ident (RFC 1413) callback.
+**** This is not for real!
+
+220 myhost.test.ex ESMTP Exim x.yz Tue, 2 Mar 1999 09:44:33 +0000
+250-myhost.test.ex Hello xclientproxy [ip4.ip4.ip4.ip4]
+250-SIZE 52428800
+250-8BITMIME
+250-PIPELINING
+250-XCLIENT ADDR NAME PORT LOGIN DESTADDR DESTPORT
+250 HELP
+501 XCLIENT must have at least one operand
+501 XCLIENT: unrecognised parameter 'NONO'
+501 XCLIENT: unrecognised parameter 'NAMEfoobar'
+501-XCLIENT: unrecognised parameter 'SIXSIX'
+501 Too many syntax or protocol errors
+###          (7) operand with zero-len value (8) operand with no value
+
+**** SMTP testing session as if from host ip4.ip4.ip4.ip4
+**** but without any ident (RFC 1413) callback.
+**** This is not for real!
+
+220 myhost.test.ex ESMTP Exim x.yz Tue, 2 Mar 1999 09:44:33 +0000
+250-myhost.test.ex Hello xclientproxy [ip4.ip4.ip4.ip4]
+250-SIZE 52428800
+250-8BITMIME
+250-PIPELINING
+250-XCLIENT ADDR NAME PORT LOGIN DESTADDR DESTPORT
+250 HELP
+501 XCLIENT: zero-length value for param
+501 XCLIENT: missing value for parameter 'NAME'
+421 myhost.test.ex lost input connection
+
+******** SERVER ********
+### (1) non-prox plain receive (not advertised) (2) XCLIENT refules when not advertised
+### receive, (1) fully loaded (2) new conn (3) bad: transaction in progress
+###          (4) no operands to XCLIENT (5,6) unrecognised operands
+###          (7) operand with zero-len value (8) operand with no value