Experimental_XCLIENT. Bug 2702
[exim.git] / src / src / smtp_in.c
index 6880e3c091a89025c92936cb07cc9c502b3dd459..6f4ad94956c8ae707f6279161d7dfe4e1bf0003a 100644 (file)
@@ -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,19 +192,34 @@ 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 = 0,
+       CL_HELO,
+       CL_EHLO,
+       CL_AUTH,
+#ifndef DISABLE_TLS
+       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 */
 
-  { "rset",       sizeof("rset")-1,       RSET_CMD, FALSE, FALSE },  /* First */
-  { "helo",       sizeof("helo")-1,       HELO_CMD, TRUE,  FALSE },
-  { "ehlo",       sizeof("ehlo")-1,       EHLO_CMD, TRUE,  FALSE },
-  { "auth",       sizeof("auth")-1,       AUTH_CMD, TRUE,  TRUE  },
+  [CL_RSET] = { "rset",       sizeof("rset")-1,       RSET_CMD,        FALSE, FALSE },  /* First */
+  [CL_HELO] = { "helo",       sizeof("helo")-1,       HELO_CMD, TRUE,  FALSE },
+  [CL_EHLO] = { "ehlo",       sizeof("ehlo")-1,       EHLO_CMD, TRUE,  FALSE },
+  [CL_AUTH] = { "auth",       sizeof("auth")-1,       AUTH_CMD,     TRUE,  TRUE  },
 #ifndef DISABLE_TLS
-  { "starttls",   sizeof("starttls")-1,   STARTTLS_CMD, FALSE, FALSE },
-  { "tls_auth",   0,                      TLS_AUTH_CMD, FALSE, FALSE },
+  [CL_STLS] = { "starttls",   sizeof("starttls")-1,   STARTTLS_CMD, FALSE, FALSE },
+  [CL_TLAU] = { "tls_auth",   0,                      TLS_AUTH_CMD, FALSE, FALSE },
+#endif
+#ifdef EXPERIMENTAL_XCLIENT
+  [CL_XCLI] = { "xclient",    sizeof("xclient")-1,    XCLIENT_CMD, TRUE,  FALSE },
 #endif
-
-/* If you change anything above here, also fix the definitions below. */
 
   { "mail from:", sizeof("mail from:")-1, MAIL_CMD, TRUE,  TRUE  },
   { "rcpt to:",   sizeof("rcpt to:")-1,   RCPT_CMD, TRUE,  TRUE  },
@@ -215,24 +233,30 @@ static smtp_cmd_list cmd_list[] = {
   { "help",       sizeof("help")-1,       HELP_CMD, TRUE,  FALSE }
 };
 
-static smtp_cmd_list *cmd_list_end =
-  cmd_list + sizeof(cmd_list)/sizeof(smtp_cmd_list);
-
-#define CMD_LIST_RSET      0
-#define CMD_LIST_HELO      1
-#define CMD_LIST_EHLO      2
-#define CMD_LIST_AUTH      3
-#define CMD_LIST_STARTTLS  4
-#define CMD_LIST_TLS_AUTH  5
-
-/* This list of names is used for performing the smtp_no_mail logging action.
-It must be kept in step with the SCH_xxx enumerations. */
+/* This list of names is used for performing the smtp_no_mail logging action. */
 
 uschar * smtp_names[] =
   {
-  US"NONE", US"AUTH", US"DATA", US"BDAT", US"EHLO", US"ETRN", US"EXPN",
-  US"HELO", US"HELP", US"MAIL", US"NOOP", US"QUIT", US"RCPT", US"RSET",
-  US"STARTTLS", US"VRFY" };
+  [SCH_NONE] = US"NONE",
+  [SCH_AUTH] = US"AUTH",
+  [SCH_DATA] = US"DATA",
+  [SCH_BDAT] = US"BDAT",
+  [SCH_EHLO] = US"EHLO",
+  [SCH_ETRN] = US"ETRN",
+  [SCH_EXPN] = US"EXPN",
+  [SCH_HELO] = US"HELO",
+  [SCH_HELP] = US"HELP",
+  [SCH_MAIL] = US"MAIL",
+  [SCH_NOOP] = US"NOOP",
+  [SCH_QUIT] = US"QUIT",
+  [SCH_RCPT] = US"RCPT",
+  [SCH_RSET] = US"RSET",
+  [SCH_STARTTLS] = US"STARTTLS",
+  [SCH_VRFY] = US"VRFY",
+#ifdef EXPERIMENTAL_XCLIENT
+  [SCH_XCLIENT] = US"XCLIENT",
+#endif
+  };
 
 static uschar *protocols_local[] = {
   US"local-smtp",        /* HELO */
@@ -333,7 +357,7 @@ Returns:    nothing
 */
 
 static void
-incomplete_transaction_log(uschar *what)
+incomplete_transaction_log(uschar * what)
 {
 if (!sender_address                            /* No transaction in progress */
    || !LOGGING(smtp_incomplete_transaction))
@@ -355,13 +379,21 @@ log_write(L_smtp_incomplete_transaction, LOG_MAIN|LOG_SENDER|LOG_RECIPIENTS,
 
 
 
+static void
+log_close_event(const uschar * reason)
+{
+log_write(L_smtp_connection, LOG_MAIN, "%s D=%s closed %s",
+  smtp_get_connection_info(), string_timesince(&smtp_connection_start), reason);
+}
+
 
 void
 smtp_command_timeout_exit(void)
 {
 log_write(L_lost_incoming_connection,
-         LOG_MAIN, "SMTP command timeout on%s connection from %s",
-         tls_in.active.sock >= 0 ? " TLS" : "", host_and_ident(FALSE));
+         LOG_MAIN, "SMTP command timeout on%s connection from %s D=%s",
+         tls_in.active.sock >= 0 ? " TLS" : "", host_and_ident(FALSE),
+         string_timesince(&smtp_connection_start));
 if (smtp_batched_input)
   moan_smtp_batch(NULL, "421 SMTP command timeout"); /* Does not return */
 smtp_notquit_exit(US"command-timeout", US"421",
@@ -373,7 +405,7 @@ exim_exit(EXIT_FAILURE);
 void
 smtp_command_sigterm_exit(void)
 {
-log_write(0, LOG_MAIN, "%s closed after SIGTERM", smtp_get_connection_info());
+log_close_event(US"after SIGTERM");
 if (smtp_batched_input)
   moan_smtp_batch(NULL, "421 SIGTERM received");  /* Does not return */
 smtp_notquit_exit(US"signal-exit", US"421",
@@ -384,9 +416,10 @@ exim_exit(EXIT_FAILURE);
 void
 smtp_data_timeout_exit(void)
 {
-log_write(L_lost_incoming_connection,
-  LOG_MAIN, "SMTP data timeout (message abandoned) on connection from %s F=<%s>",
-  sender_fullhost ? sender_fullhost : US"local process", sender_address);
+log_write(L_lost_incoming_connection, LOG_MAIN,
+  "SMTP data timeout (message abandoned) on connection from %s F=<%s> D=%s",
+  sender_fullhost ? sender_fullhost : US"local process", sender_address,
+  string_timesince(&smtp_connection_start));
 receive_bomb_out(US"data-timeout", US"SMTP incoming data timeout");
 /* Does not return */
 }
@@ -394,8 +427,7 @@ receive_bomb_out(US"data-timeout", US"SMTP incoming data timeout");
 void
 smtp_data_sigint_exit(void)
 {
-log_write(0, LOG_MAIN, "%s closed after %s",
-  smtp_get_connection_info(), had_data_sigint == SIGTERM ? "SIGTERM":"SIGINT");
+log_close_event(had_data_sigint == SIGTERM ? US"SIGTERM":US"SIGINT");
 receive_bomb_out(US"signal-exit",
   US"Service not available - SIGTERM or SIGINT received");
 /* Does not return */
@@ -1093,503 +1125,6 @@ had_command_sigterm = sig;
 
 
 
-#ifdef SUPPORT_PROXY
-/*************************************************
-*       Check if host is required proxy host     *
-*************************************************/
-/* The function determines if inbound host will be a regular smtp host
-or if it is configured that it must use Proxy Protocol.  A local
-connection cannot.
-
-Arguments: none
-Returns:   bool
-*/
-
-static BOOL
-check_proxy_protocol_host()
-{
-int rc;
-
-if (  sender_host_address
-   && (rc = verify_check_this_host(CUSS &hosts_proxy, NULL, NULL,
-                           sender_host_address, NULL)) == OK)
-  {
-  DEBUG(D_receive)
-    debug_printf("Detected proxy protocol configured host\n");
-  proxy_session = TRUE;
-  }
-return proxy_session;
-}
-
-
-/*************************************************
-*    Read data until newline or end of buffer    *
-*************************************************/
-/* While SMTP is server-speaks-first, TLS is client-speaks-first, so we can't
-read an entire buffer and assume there will be nothing past a proxy protocol
-header.  Our approach normally is to use stdio, but again that relies upon
-"STARTTLS\r\n" and a server response before the client starts TLS handshake, or
-reading _nothing_ before client TLS handshake.  So we don't want to use the
-usual buffering reads which may read enough to block TLS starting.
-
-So unfortunately we're down to "read one byte at a time, with a syscall each,
-and expect a little overhead", for all proxy-opened connections which are v1,
-just to handle the TLS-on-connect case.  Since SSL functions wrap the
-underlying fd, we can't assume that we can feed them any already-read content.
-
-We need to know where to read to, the max capacity, and we'll read until we
-get a CR and one more character.  Let the caller scream if it's CR+!LF.
-
-Return the amount read.
-*/
-
-static int
-swallow_until_crlf(int fd, uschar *base, int already, int capacity)
-{
-uschar *to = base + already;
-uschar *cr;
-int have = 0;
-int ret;
-int last = 0;
-
-/* For "PROXY UNKNOWN\r\n" we, at time of writing, expect to have read
-up through the \r; for the _normal_ case, we haven't yet seen the \r. */
-
-cr = memchr(base, '\r', already);
-if (cr != NULL)
-  {
-  if ((cr - base) < already - 1)
-    {
-    /* \r and presumed \n already within what we have; probably not
-    actually proxy protocol, but abort cleanly. */
-    return 0;
-    }
-  /* \r is last character read, just need one more. */
-  last = 1;
-  }
-
-while (capacity > 0)
-  {
-  do { ret = read(fd, to, 1); } while (ret == -1 && errno == EINTR && !had_command_timeout);
-  if (ret == -1)
-    return -1;
-  have++;
-  if (last)
-    return have;
-  if (*to == '\r')
-    last = 1;
-  capacity--;
-  to++;
-  }
-
-/* reached end without having room for a final newline, abort */
-errno = EOVERFLOW;
-return -1;
-}
-
-/*************************************************
-*         Setup host for proxy protocol          *
-*************************************************/
-/* The function configures the connection based on a header from the
-inbound host to use Proxy Protocol. The specification is very exact
-so exit with an error if do not find the exact required pieces. This
-includes an incorrect number of spaces separating args.
-
-Arguments: none
-Returns:   Boolean success
-*/
-
-static void
-setup_proxy_protocol_host()
-{
-union {
-  struct {
-    uschar line[108];
-  } v1;
-  struct {
-    uschar sig[12];
-    uint8_t ver_cmd;
-    uint8_t fam;
-    uint16_t len;
-    union {
-      struct { /* TCP/UDP over IPv4, len = 12 */
-        uint32_t src_addr;
-        uint32_t dst_addr;
-        uint16_t src_port;
-        uint16_t dst_port;
-      } ip4;
-      struct { /* TCP/UDP over IPv6, len = 36 */
-        uint8_t  src_addr[16];
-        uint8_t  dst_addr[16];
-        uint16_t src_port;
-        uint16_t dst_port;
-      } ip6;
-      struct { /* AF_UNIX sockets, len = 216 */
-        uschar   src_addr[108];
-        uschar   dst_addr[108];
-      } unx;
-    } addr;
-  } v2;
-} hdr;
-
-/* Temp variables used in PPv2 address:port parsing */
-uint16_t tmpport;
-char tmpip[INET_ADDRSTRLEN];
-struct sockaddr_in tmpaddr;
-char tmpip6[INET6_ADDRSTRLEN];
-struct sockaddr_in6 tmpaddr6;
-
-/* We can't read "all data until end" because while SMTP is
-server-speaks-first, the TLS handshake is client-speaks-first, so for
-TLS-on-connect ports the proxy protocol header will usually be immediately
-followed by a TLS handshake, and with N TLS libraries, we can't reliably
-reinject data for reading by those.  So instead we first read "enough to be
-safely read within the header, and figure out how much more to read".
-For v1 we will later read to the end-of-line, for v2 we will read based upon
-the stated length.
-
-The v2 sig is 12 octets, and another 4 gets us the length, so we know how much
-data is needed total.  For v1, where the line looks like:
-PROXY TCPn L3src L3dest SrcPort DestPort \r\n
-
-However, for v1 there's also `PROXY UNKNOWN\r\n` which is only 15 octets.
-We seem to support that.  So, if we read 14 octets then we can tell if we're
-v2 or v1.  If we're v1, we can continue reading as normal.
-
-If we're v2, we can't slurp up the entire header.  We need the length in the
-15th & 16th octets, then to read everything after that.
-
-So to safely handle v1 and v2, with client-sent-first supported correctly,
-we have to do a minimum of 3 read calls, not 1.  Eww.
-*/
-
-#define PROXY_INITIAL_READ 14
-#define PROXY_V2_HEADER_SIZE 16
-#if PROXY_INITIAL_READ > PROXY_V2_HEADER_SIZE
-# error Code bug in sizes of data to read for proxy usage
-#endif
-
-int get_ok = 0;
-int size, ret;
-int fd = fileno(smtp_in);
-const char v2sig[12] = "\x0D\x0A\x0D\x0A\x00\x0D\x0A\x51\x55\x49\x54\x0A";
-uschar * iptype;  /* To display debug info */
-socklen_t vslen = sizeof(struct timeval);
-BOOL yield = FALSE;
-
-os_non_restarting_signal(SIGALRM, command_timeout_handler);
-ALARM(proxy_protocol_timeout);
-
-do
-  {
-  /* The inbound host was declared to be a Proxy Protocol host, so
-  don't do a PEEK into the data, actually slurp up enough to be
-  "safe". Can't take it all because TLS-on-connect clients follow
-  immediately with TLS handshake. */
-  ret = read(fd, &hdr, PROXY_INITIAL_READ);
-  }
-  while (ret == -1 && errno == EINTR && !had_command_timeout);
-
-if (ret == -1)
-  goto proxyfail;
-
-/* For v2, handle reading the length, and then the rest. */
-if ((ret == PROXY_INITIAL_READ) && (memcmp(&hdr.v2, v2sig, sizeof(v2sig)) == 0))
-  {
-  int retmore;
-  uint8_t ver;
-
-  /* First get the length fields. */
-  do
-    {
-    retmore = read(fd, (uschar*)&hdr + ret, PROXY_V2_HEADER_SIZE - PROXY_INITIAL_READ);
-    } while (retmore == -1 && errno == EINTR && !had_command_timeout);
-  if (retmore == -1)
-    goto proxyfail;
-  ret += retmore;
-
-  ver = (hdr.v2.ver_cmd & 0xf0) >> 4;
-
-  /* May 2014: haproxy combined the version and command into one byte to
-  allow two full bytes for the length field in order to proxy SSL
-  connections.  SSL Proxy is not supported in this version of Exim, but
-  must still separate values here. */
-
-  if (ver != 0x02)
-    {
-    DEBUG(D_receive) debug_printf("Invalid Proxy Protocol version: %d\n", ver);
-    goto proxyfail;
-    }
-
-  /* The v2 header will always be 16 bytes per the spec. */
-  size = 16 + ntohs(hdr.v2.len);
-  DEBUG(D_receive) debug_printf("Detected PROXYv2 header, size %d (limit %d)\n",
-      size, (int)sizeof(hdr));
-
-  /* We should now have 16 octets (PROXY_V2_HEADER_SIZE), and we know the total
-  amount that we need.  Double-check that the size is not unreasonable, then
-  get the rest. */
-  if (size > sizeof(hdr))
-    {
-    DEBUG(D_receive) debug_printf("PROXYv2 header size unreasonably large; security attack?\n");
-    goto proxyfail;
-    }
-
-  do
-    {
-    do
-      {
-      retmore = read(fd, (uschar*)&hdr + ret, size-ret);
-      } while (retmore == -1 && errno == EINTR && !had_command_timeout);
-    if (retmore == -1)
-      goto proxyfail;
-    ret += retmore;
-    DEBUG(D_receive) debug_printf("PROXYv2: have %d/%d required octets\n", ret, size);
-    } while (ret < size);
-
-  } /* end scope for getting rest of data for v2 */
-
-/* At this point: if PROXYv2, we've read the exact size required for all data;
-if PROXYv1 then we've read "less than required for any valid line" and should
-read the rest". */
-
-if (ret >= 16 && memcmp(&hdr.v2, v2sig, 12) == 0)
-  {
-  uint8_t cmd = (hdr.v2.ver_cmd & 0x0f);
-
-  switch (cmd)
-    {
-    case 0x01: /* PROXY command */
-      switch (hdr.v2.fam)
-        {
-        case 0x11:  /* TCPv4 address type */
-          iptype = US"IPv4";
-          tmpaddr.sin_addr.s_addr = hdr.v2.addr.ip4.src_addr;
-          inet_ntop(AF_INET, &tmpaddr.sin_addr, CS &tmpip, sizeof(tmpip));
-          if (!string_is_ip_address(US tmpip, NULL))
-            {
-            DEBUG(D_receive) debug_printf("Invalid %s source IP\n", iptype);
-            goto proxyfail;
-            }
-          proxy_local_address = sender_host_address;
-          sender_host_address = string_copy(US tmpip);
-          tmpport             = ntohs(hdr.v2.addr.ip4.src_port);
-          proxy_local_port    = sender_host_port;
-          sender_host_port    = tmpport;
-          /* Save dest ip/port */
-          tmpaddr.sin_addr.s_addr = hdr.v2.addr.ip4.dst_addr;
-          inet_ntop(AF_INET, &tmpaddr.sin_addr, CS &tmpip, sizeof(tmpip));
-          if (!string_is_ip_address(US tmpip, NULL))
-            {
-            DEBUG(D_receive) debug_printf("Invalid %s dest port\n", iptype);
-            goto proxyfail;
-            }
-          proxy_external_address = string_copy(US tmpip);
-          tmpport              = ntohs(hdr.v2.addr.ip4.dst_port);
-          proxy_external_port  = tmpport;
-          goto done;
-        case 0x21:  /* TCPv6 address type */
-          iptype = US"IPv6";
-          memmove(tmpaddr6.sin6_addr.s6_addr, hdr.v2.addr.ip6.src_addr, 16);
-          inet_ntop(AF_INET6, &tmpaddr6.sin6_addr, CS &tmpip6, sizeof(tmpip6));
-          if (!string_is_ip_address(US tmpip6, NULL))
-            {
-            DEBUG(D_receive) debug_printf("Invalid %s source IP\n", iptype);
-            goto proxyfail;
-            }
-          proxy_local_address = sender_host_address;
-          sender_host_address = string_copy(US tmpip6);
-          tmpport             = ntohs(hdr.v2.addr.ip6.src_port);
-          proxy_local_port    = sender_host_port;
-          sender_host_port    = tmpport;
-          /* Save dest ip/port */
-          memmove(tmpaddr6.sin6_addr.s6_addr, hdr.v2.addr.ip6.dst_addr, 16);
-          inet_ntop(AF_INET6, &tmpaddr6.sin6_addr, CS &tmpip6, sizeof(tmpip6));
-          if (!string_is_ip_address(US tmpip6, NULL))
-            {
-            DEBUG(D_receive) debug_printf("Invalid %s dest port\n", iptype);
-            goto proxyfail;
-            }
-          proxy_external_address = string_copy(US tmpip6);
-          tmpport              = ntohs(hdr.v2.addr.ip6.dst_port);
-          proxy_external_port  = tmpport;
-          goto done;
-        default:
-          DEBUG(D_receive)
-            debug_printf("Unsupported PROXYv2 connection type: 0x%02x\n",
-                         hdr.v2.fam);
-          goto proxyfail;
-        }
-      /* Unsupported protocol, keep local connection address */
-      break;
-    case 0x00: /* LOCAL command */
-      /* Keep local connection address for LOCAL */
-      iptype = US"local";
-      break;
-    default:
-      DEBUG(D_receive)
-        debug_printf("Unsupported PROXYv2 command: 0x%x\n", cmd);
-      goto proxyfail;
-    }
-  }
-else if (ret >= 8 && memcmp(hdr.v1.line, "PROXY", 5) == 0)
-  {
-  uschar *p;
-  uschar *end;
-  uschar *sp;     /* Utility variables follow */
-  int     tmp_port;
-  int     r2;
-  char   *endc;
-
-  /* get the rest of the line */
-  r2 = swallow_until_crlf(fd, (uschar*)&hdr, ret, sizeof(hdr)-ret);
-  if (r2 == -1)
-    goto proxyfail;
-  ret += r2;
-
-  p = string_copy(hdr.v1.line);
-  end = memchr(p, '\r', ret - 1);
-
-  if (!end || (end == (uschar*)&hdr + ret) || end[1] != '\n')
-    {
-    DEBUG(D_receive) debug_printf("Partial or invalid PROXY header\n");
-    goto proxyfail;
-    }
-  *end = '\0'; /* Terminate the string */
-  size = end + 2 - p; /* Skip header + CRLF */
-  DEBUG(D_receive) debug_printf("Detected PROXYv1 header\n");
-  DEBUG(D_receive) debug_printf("Bytes read not within PROXY header: %d\n", ret - size);
-  /* Step through the string looking for the required fields. Ensure
-  strict adherence to required formatting, exit for any error. */
-  p += 5;
-  if (!isspace(*(p++)))
-    {
-    DEBUG(D_receive) debug_printf("Missing space after PROXY command\n");
-    goto proxyfail;
-    }
-  if (!Ustrncmp(p, CCS"TCP4", 4))
-    iptype = US"IPv4";
-  else if (!Ustrncmp(p,CCS"TCP6", 4))
-    iptype = US"IPv6";
-  else if (!Ustrncmp(p,CCS"UNKNOWN", 7))
-    {
-    iptype = US"Unknown";
-    goto done;
-    }
-  else
-    {
-    DEBUG(D_receive) debug_printf("Invalid TCP type\n");
-    goto proxyfail;
-    }
-
-  p += Ustrlen(iptype);
-  if (!isspace(*(p++)))
-    {
-    DEBUG(D_receive) debug_printf("Missing space after TCP4/6 command\n");
-    goto proxyfail;
-    }
-  /* Find the end of the arg */
-  if ((sp = Ustrchr(p, ' ')) == NULL)
-    {
-    DEBUG(D_receive)
-      debug_printf("Did not find proxied src %s\n", iptype);
-    goto proxyfail;
-    }
-  *sp = '\0';
-  if(!string_is_ip_address(p, NULL))
-    {
-    DEBUG(D_receive)
-      debug_printf("Proxied src arg is not an %s address\n", iptype);
-    goto proxyfail;
-    }
-  proxy_local_address = sender_host_address;
-  sender_host_address = p;
-  p = sp + 1;
-  if ((sp = Ustrchr(p, ' ')) == NULL)
-    {
-    DEBUG(D_receive)
-      debug_printf("Did not find proxy dest %s\n", iptype);
-    goto proxyfail;
-    }
-  *sp = '\0';
-  if(!string_is_ip_address(p, NULL))
-    {
-    DEBUG(D_receive)
-      debug_printf("Proxy dest arg is not an %s address\n", iptype);
-    goto proxyfail;
-    }
-  proxy_external_address = p;
-  p = sp + 1;
-  if ((sp = Ustrchr(p, ' ')) == NULL)
-    {
-    DEBUG(D_receive) debug_printf("Did not find proxied src port\n");
-    goto proxyfail;
-    }
-  *sp = '\0';
-  tmp_port = strtol(CCS p, &endc, 10);
-  if (*endc || tmp_port == 0)
-    {
-    DEBUG(D_receive)
-      debug_printf("Proxied src port '%s' not an integer\n", p);
-    goto proxyfail;
-    }
-  proxy_local_port = sender_host_port;
-  sender_host_port = tmp_port;
-  p = sp + 1;
-  if ((sp = Ustrchr(p, '\0')) == NULL)
-    {
-    DEBUG(D_receive) debug_printf("Did not find proxy dest port\n");
-    goto proxyfail;
-    }
-  tmp_port = strtol(CCS p, &endc, 10);
-  if (*endc || tmp_port == 0)
-    {
-    DEBUG(D_receive)
-      debug_printf("Proxy dest port '%s' not an integer\n", p);
-    goto proxyfail;
-    }
-  proxy_external_port = tmp_port;
-  /* Already checked for /r /n above. Good V1 header received. */
-  }
-else
-  {
-  /* Wrong protocol */
-  DEBUG(D_receive) debug_printf("Invalid proxy protocol version negotiation\n");
-  (void) swallow_until_crlf(fd, (uschar*)&hdr, ret, sizeof(hdr)-ret);
-  goto proxyfail;
-  }
-
-done:
-  DEBUG(D_receive)
-    debug_printf("Valid %s sender from Proxy Protocol header\n", iptype);
-  yield = proxy_session;
-
-/* Don't flush any potential buffer contents. Any input on proxyfail
-should cause a synchronization failure */
-
-proxyfail:
-  DEBUG(D_receive) if (had_command_timeout)
-    debug_printf("Timeout while reading proxy header\n");
-
-bad:
-  if (yield)
-    {
-    sender_host_name = NULL;
-    (void) host_name_lookup();
-    host_build_sender_fullhost();
-    }
-  else
-    {
-    f.proxy_session_failed = TRUE;
-    DEBUG(D_receive)
-      debug_printf("Failure to extract proxied host, only QUIT allowed\n");
-    }
-
-ALARM(0);
-return;
-}
-#endif
-
 /*************************************************
 *           Read one command line                *
 *************************************************/
@@ -1662,7 +1197,7 @@ if (hadnull) return BADCHAR_CMD;
 to the start of the actual data characters. Check for SMTP synchronization
 if required. */
 
-for (smtp_cmd_list * p = cmd_list; p < cmd_list_end; p++)
+for (smtp_cmd_list * p = cmd_list; p < cmd_list + nelem(cmd_list); p++)
   {
 #ifdef SUPPORT_PROXY
   /* Only allow QUIT command if Proxy Protocol parsing failed */
@@ -1740,6 +1275,7 @@ return OTHER_CMD;
 
 
 
+
 /*************************************************
 *          Forced closedown of call              *
 *************************************************/
@@ -2254,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
@@ -2998,14 +2533,20 @@ if (!f.sender_host_unknown)
 
 if (smtp_batched_input) return TRUE;
 
+#if defined(SUPPORT_PROXY) || defined(SUPPORT_SOCKS) || defined(EXPERIMETAL_XCLIENT)
+proxy_session = FALSE;
+#endif
+
+#ifdef SUPPORT_PROXY
 /* If valid Proxy Protocol source is connecting, set up session.
 Failure will not allow any SMTP function other than QUIT. */
 
-#ifdef SUPPORT_PROXY
-proxy_session = FALSE;
 f.proxy_session_failed = FALSE;
-if (check_proxy_protocol_host())
-  setup_proxy_protocol_host();
+if (proxy_protocol_host())
+  {
+  os_non_restarting_signal(SIGALRM, command_timeout_handler);
+  proxy_protocol_setup();
+  }
 #endif
 
 /* Run the connect ACL if it exists */
@@ -3035,7 +2576,7 @@ if (tls_in.on_connect)
   {
   if (tls_server_start(&user_msg) != OK)
     return smtp_log_tls_fail(user_msg);
-  cmd_list[CMD_LIST_TLS_AUTH].is_mail_cmd = TRUE;
+  cmd_list[CL_TLAU].is_mail_cmd = TRUE;
   }
 #endif
 
@@ -3557,8 +3098,7 @@ if (log_reject_target != 0)
 
 if (!drop) return 0;
 
-log_write(L_smtp_connection, LOG_MAIN, "%s closed by DROP in ACL",
-  smtp_get_connection_info());
+log_close_event(US"by DROP in ACL");
 
 /* Run the not-quit ACL, but without any custom messages. This should not be a
 problem, because we get here only if some other ACL has issued "drop", and
@@ -3984,16 +3524,14 @@ else
 tls_close(NULL, TLS_SHUTDOWN_NOWAIT);
 # endif
 
-log_write(L_smtp_connection, LOG_MAIN, "%s closed by QUIT",
-  smtp_get_connection_info());
+log_close_event(US"by QUIT");
 #else
 
 # ifndef DISABLE_TLS
 tls_close(NULL, TLS_SHUTDOWN_WAIT);
 # endif
 
-log_write(L_smtp_connection, LOG_MAIN, "%s closed by QUIT",
-  smtp_get_connection_info());
+log_close_event(US"by QUIT");
 
 /* Pause, hoping client will FIN first so that they get the TIME_WAIT.
 The socket should become readble (though with no data) */
@@ -4009,7 +3547,7 @@ smtp_rset_handler(void)
 HAD(SCH_RSET);
 incomplete_transaction_log(US"RSET");
 smtp_printf("250 Reset OK\r\n", FALSE);
-cmd_list[CMD_LIST_RSET].is_mail_cmd = FALSE;
+cmd_list[CL_RSET].is_mail_cmd = FALSE;
 if (chunking_state > CHUNKING_OFFERED)
   chunking_state = CHUNKING_OFFERED;
 }
@@ -4071,11 +3609,11 @@ message_ended = END_NOTSTARTED;
 
 chunking_state = f.chunking_offered ? CHUNKING_OFFERED : CHUNKING_NOT_OFFERED;
 
-cmd_list[CMD_LIST_RSET].is_mail_cmd = TRUE;
-cmd_list[CMD_LIST_HELO].is_mail_cmd = TRUE;
-cmd_list[CMD_LIST_EHLO].is_mail_cmd = TRUE;
+cmd_list[CL_RSET].is_mail_cmd = TRUE;
+cmd_list[CL_HELO].is_mail_cmd = TRUE;
+cmd_list[CL_EHLO].is_mail_cmd = TRUE;
 #ifndef DISABLE_TLS
-cmd_list[CMD_LIST_STARTTLS].is_mail_cmd = TRUE;
+cmd_list[CL_STLS].is_mail_cmd = TRUE;
 #endif
 
 if (lwr_receive_getc != NULL)
@@ -4131,10 +3669,10 @@ while (done <= 0)
   if (  tls_in.active.sock >= 0
      && tls_in.peercert
      && tls_in.certificate_verified
-     && cmd_list[CMD_LIST_TLS_AUTH].is_mail_cmd
+     && cmd_list[CL_TLAU].is_mail_cmd
      )
     {
-    cmd_list[CMD_LIST_TLS_AUTH].is_mail_cmd = FALSE;
+    cmd_list[CL_TLAU].is_mail_cmd = FALSE;
 
     for (auth_instance * au = auths; au; au = au->next)
       if (strcmpic(US"tls", au->driver_name) == 0)
@@ -4194,7 +3732,7 @@ while (done <= 0)
     case AUTH_CMD:
       HAD(SCH_AUTH);
       authentication_failed = TRUE;
-      cmd_list[CMD_LIST_AUTH].is_mail_cmd = FALSE;
+      cmd_list[CL_AUTH].is_mail_cmd = FALSE;
 
       if (!fl.auth_advertised && !f.allow_auth_unadvertised)
        {
@@ -4316,8 +3854,8 @@ while (done <= 0)
       fl.esmtp = TRUE;
 
     HELO_EHLO:      /* Common code for HELO and EHLO */
-      cmd_list[CMD_LIST_HELO].is_mail_cmd = FALSE;
-      cmd_list[CMD_LIST_EHLO].is_mail_cmd = FALSE;
+      cmd_list[CL_HELO].is_mail_cmd = FALSE;
+      cmd_list[CL_EHLO].is_mail_cmd = FALSE;
 
       /* Reject the HELO if its argument was invalid or non-existent. A
       successful check causes the argument to be saved in malloc store. */
@@ -4488,7 +4026,7 @@ while (done <= 0)
 
       if (fl.esmtp)
        {
-       g->s[3] = '-';
+       g->s[3] = '-';  /* overwrite the space after the SMTP response code */
 
        /* I'm not entirely happy with this, as an MTA is supposed to check
        that it has enough room to accept a message of maximum size before
@@ -4619,9 +4157,9 @@ while (done <= 0)
                  first = FALSE;
                  fl.auth_advertised = TRUE;
                  }
-               saveptr = g->ptr;
+               saveptr = gstring_length(g);
                g = string_catn(g, US" ", 1);
-               g = string_cat (g, au->public_name);
+               g = string_cat(g, au->public_name);
                while (++saveptr < g->ptr) g->s[saveptr] = toupper(g->s[saveptr]);
                au->advertised = TRUE;
                }
@@ -4655,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)
@@ -4684,25 +4228,29 @@ while (done <= 0)
       /* Terminate the string (for debug), write it, and note that HELO/EHLO
       has been seen. */
 
+       {
+       uschar * ehlo_resp;
+       int len = len_string_from_gstring(g, &ehlo_resp);
 #ifndef DISABLE_TLS
-      if (tls_in.active.sock >= 0)
-       (void)tls_write(NULL, g->s, g->ptr,
+       if (tls_in.active.sock >= 0)
+         (void) tls_write(NULL, ehlo_resp, len,
 # ifndef DISABLE_PIPE_CONNECT
-                       fl.pipe_connect_acceptable && pipeline_connect_sends());
+                         fl.pipe_connect_acceptable && pipeline_connect_sends());
 # else
-                       FALSE);
+                         FALSE);
 # endif
-      else
+       else
 #endif
-       (void) fwrite(g->s, 1, g->ptr, smtp_out);
+         (void) fwrite(ehlo_resp, 1, len, smtp_out);
 
-      DEBUG(D_receive) for (const uschar * t, * s = string_from_gstring(g);
-                           s && (t = Ustrchr(s, '\r'));
-                           s = t + 2)                          /* \r\n */
-         debug_printf("%s %.*s\n",
-                       s == g->s ? "SMTP>>" : "      ",
-                       (int)(t - s), s);
-      fl.helo_seen = TRUE;
+       DEBUG(D_receive) for (const uschar * t, * s = ehlo_resp;
+                             s && (t = Ustrchr(s, '\r'));
+                             s = t + 2)                                /* \r\n */
+           debug_printf("%s %.*s\n",
+                         s == g->s ? "SMTP>>" : "      ",
+                         (int)(t - s), s);
+       fl.helo_seen = TRUE;
+       }
 
       /* Reset the protocol and the state, abandoning any previous message. */
       received_protocol =
@@ -4717,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
@@ -5661,7 +5244,7 @@ while (done <= 0)
       cancel_cutthrough_connection(TRUE, US"STARTTLS received");
       reset_point = smtp_reset(reset_point);
       toomany = FALSE;
-      cmd_list[CMD_LIST_STARTTLS].is_mail_cmd = FALSE;
+      cmd_list[CL_STLS].is_mail_cmd = FALSE;
 
       /* There's an attack where more data is read in past the STARTTLS command
       before TLS is negotiated, then assumed to be part of the secure session
@@ -5702,9 +5285,9 @@ while (done <= 0)
        {
        if (!tls_remember_esmtp)
          fl.helo_seen = fl.esmtp = fl.auth_advertised = f.smtp_in_pipelining_advertised = FALSE;
-       cmd_list[CMD_LIST_EHLO].is_mail_cmd = TRUE;
-       cmd_list[CMD_LIST_AUTH].is_mail_cmd = TRUE;
-       cmd_list[CMD_LIST_TLS_AUTH].is_mail_cmd = TRUE;
+       cmd_list[CL_EHLO].is_mail_cmd = TRUE;
+       cmd_list[CL_AUTH].is_mail_cmd = TRUE;
+       cmd_list[CL_TLAU].is_mail_cmd = TRUE;
        if (sender_helo_name)
          {
          sender_helo_name = NULL;
@@ -5748,8 +5331,7 @@ while (done <= 0)
       while (done <= 0) switch(smtp_read_command(FALSE, GETC_BUFFER_UNLIMITED))
        {
        case EOF_CMD:
-         log_write(L_smtp_connection, LOG_MAIN, "%s closed by EOF",
-           smtp_get_connection_info());
+         log_close_event(US"by EOF");
          smtp_notquit_exit(US"tls-failed", NULL, NULL);
          done = 2;
          break;
@@ -5771,8 +5353,7 @@ while (done <= 0)
            smtp_respond(US"221", 3, TRUE, user_msg);
          else
            smtp_printf("221 %s closing connection\r\n", FALSE, smtp_active_hostname);
-         log_write(L_smtp_connection, LOG_MAIN, "%s closed by QUIT",
-           smtp_get_connection_info());
+         log_close_event(US"by QUIT");
          done = 2;
          break;
 
@@ -5816,23 +5397,23 @@ while (done <= 0)
 
     case HELP_CMD:
       HAD(SCH_HELP);
-      smtp_printf("214-Commands supported:\r\n", TRUE);
-       {
-       uschar buffer[256];
-       buffer[0] = 0;
-       Ustrcat(buffer, US" AUTH");
-       #ifndef DISABLE_TLS
-       if (tls_in.active.sock < 0 &&
-           verify_check_host(&tls_advertise_hosts) != FAIL)
-         Ustrcat(buffer, US" STARTTLS");
-       #endif
-       Ustrcat(buffer, US" HELO EHLO MAIL RCPT DATA BDAT");
-       Ustrcat(buffer, US" NOOP QUIT RSET HELP");
-       if (acl_smtp_etrn) Ustrcat(buffer, US" ETRN");
-       if (acl_smtp_expn) Ustrcat(buffer, US" EXPN");
-       if (acl_smtp_vrfy) Ustrcat(buffer, US" VRFY");
-       smtp_printf("214%s\r\n", FALSE, buffer);
-       }
+      smtp_printf("214-Commands supported:\r\n214", TRUE);
+      smtp_printf(" AUTH", TRUE);
+#ifndef DISABLE_TLS
+      if (tls_in.active.sock < 0 &&
+         verify_check_host(&tls_advertise_hosts) != FAIL)
+       smtp_printf(" STARTTLS", TRUE);
+#endif
+      smtp_printf(" HELO EHLO MAIL RCPT DATA BDAT", TRUE);
+      smtp_printf(" NOOP QUIT RSET HELP", TRUE);
+      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;