Copyright updates:
[exim.git] / src / src / smtp_in.c
index 7a45772cef98c3674fcd28e8f7b6ac4a9cfc0560..8cc9f74bf6c9635432a0e90c3b8ca12487810623 100644 (file)
@@ -2,7 +2,7 @@
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
-/* Copyright (c) The Exim Maintainers 2020 - 2022 */
+/* Copyright (c) The Exim Maintainers 2020 - 2023 */
 /* Copyright (c) University of Cambridge 1995 - 2018 */
 /* See the file NOTICE for conditions of use and distribution. */
 /* SPDX-License-Identifier: GPL-2.0-or-later */
@@ -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              *
 *************************************************/
@@ -1326,21 +1342,29 @@ smtp_get_connection_info(void)
 {
 const uschar * hostname = sender_fullhost
   ? sender_fullhost : sender_host_address;
+gstring * g = string_catn(NULL, US"SMTP connection", 15);
+
+if (LOGGING(connection_id))
+  g = string_fmt_append(g, " Ci=%lu", connection_id);
+g = string_catn(g, US" from ", 6);
 
 if (host_checking)
-  return string_sprintf("SMTP connection from %s", hostname);
+  g = string_cat(g, hostname);
+
+else if (f.sender_host_unknown || f.sender_host_notsocket)
+  g = string_cat(g, sender_ident);
 
-if (f.sender_host_unknown || f.sender_host_notsocket)
-  return string_sprintf("SMTP connection from %s", sender_ident);
+else if (f.is_inetd)
+  g = string_append(g, 2, hostname, US" (via inetd)");
 
-if (f.is_inetd)
-  return string_sprintf("SMTP connection from %s (via inetd)", hostname);
+else if (LOGGING(incoming_interface) && interface_address)
+  g = string_fmt_append(g, "%s I=[%s]:%d", hostname, interface_address, interface_port);
 
-if (LOGGING(incoming_interface) && interface_address)
-  return string_sprintf("SMTP connection from %s I=[%s]:%d", hostname,
-    interface_address, interface_port);
+else
+  g = string_cat(g, hostname);
 
-return string_sprintf("SMTP connection from %s", hostname);
+gstring_release_unused(g);
+return string_from_gstring(g);
 }
 
 
@@ -1774,7 +1798,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
@@ -2665,13 +2688,13 @@ if (!check_sync())
 /* Now output the banner */
 /*XXX the ehlo-resp code does its own tls/nontls bit.  Maybe subroutine that? */
 
-smtp_printf("%s",
+smtp_printf("%Y",
 #ifndef DISABLE_PIPE_CONNECT
   fl.pipe_connect_acceptable && pipeline_connect_sends(),
 #else
   FALSE,
 #endif
-  string_from_gstring(ss));
+  ss);
 
 /* Attempt to see if we sent the banner before the last ACK of the 3-way
 handshake arrived.  If so we must have managed a TFO. */
@@ -2720,9 +2743,9 @@ if (++synprot_error_count > smtp_max_synprot_errors)
   {
   yield = 1;
   log_write(0, LOG_MAIN|LOG_REJECT, "SMTP call from %s dropped: too many "
-    "syntax or protocol errors (last command was \"%s\", %s)",
+    "syntax or protocol errors (last command was \"%s\", %Y)",
     host_and_ident(FALSE), string_printing(smtp_cmd_buffer),
-    string_from_gstring(s_connhad_log(NULL))
+    s_connhad_log(NULL)
     );
   }
 
@@ -3180,7 +3203,7 @@ if (code && defaultrespond)
     va_start(ap, defaultrespond);
     g = string_vformat(NULL, SVFMT_EXTEND|SVFMT_REBUFFER, CS defaultrespond, ap);
     va_end(ap);
-    smtp_printf("%s %s\r\n", FALSE, code, string_from_gstring(g));
+    smtp_printf("%s %Y\r\n", FALSE, code, g);
     }
   mac_smtp_fflush();
   }
@@ -3857,9 +3880,9 @@ while (done <= 0)
        if (++synprot_error_count > smtp_max_synprot_errors)
          {
          log_write(0, LOG_MAIN|LOG_REJECT, "SMTP call from %s dropped: too many "
-           "syntax or protocol errors (last command was \"%s\", %s)",
+           "syntax or protocol errors (last command was \"%s\", %Y)",
            host_and_ident(FALSE), string_printing(smtp_cmd_buffer),
-           string_from_gstring(s_connhad_log(NULL))
+           s_connhad_log(NULL)
            );
          done = 1;
          }
@@ -4178,7 +4201,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 +4273,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 +5417,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;
 
@@ -5425,8 +5493,8 @@ while (done <= 0)
        BOOL rc;
        etrn_command = smtp_etrn_command;
        deliver_domain = smtp_cmd_data;
-       rc = transport_set_up_command(&argv, smtp_etrn_command, TRUE, 0, NULL,
-         FALSE, US"ETRN processing", &error);
+       rc = transport_set_up_command(&argv, smtp_etrn_command, TSUC_EXPAND_ARGS, 0, NULL,
+         US"ETRN processing", &error);
        deliver_domain = NULL;
        if (!rc)
          {
@@ -5570,27 +5638,27 @@ while (done <= 0)
 
     case BADSYN_CMD:
     SYNC_FAILURE:
-      if (smtp_inend >= smtp_inbuffer + IN_BUFFER_SIZE)
-       smtp_inend = smtp_inbuffer + IN_BUFFER_SIZE - 1;
-      c = smtp_inend - smtp_inptr;
-      if (c > 150) c = 150;    /* limit logged amount */
-      smtp_inptr[c] = 0;
-      incomplete_transaction_log(US"sync failure");
-      log_write(0, LOG_MAIN|LOG_REJECT, "SMTP protocol synchronization error "
-       "(next input sent too soon: pipelining was%s advertised): "
-       "rejected \"%s\" %s next input=\"%s\"",
-       f.smtp_in_pipelining_advertised ? "" : " not",
-       smtp_cmd_buffer, host_and_ident(TRUE),
-       string_printing(smtp_inptr));
-      smtp_notquit_exit(US"synchronization-error", US"554",
-       US"SMTP synchronization error");
-      done = 1;   /* Pretend eof - drops connection */
-      break;
+      {
+       unsigned nchars = 150;
+       uschar * buf = receive_getbuf(&nchars);         /* destructive read */
+       buf[nchars] = '\0';
+       incomplete_transaction_log(US"sync failure");
+       log_write(0, LOG_MAIN|LOG_REJECT, "SMTP protocol synchronization error "
+         "(next input sent too soon: pipelining was%s advertised): "
+         "rejected \"%s\" %s next input=\"%s\" (%u bytes)",
+         f.smtp_in_pipelining_advertised ? "" : " not",
+         smtp_cmd_buffer, host_and_ident(TRUE),
+         string_printing(buf), nchars);
+       smtp_notquit_exit(US"synchronization-error", US"554",
+         US"SMTP synchronization error");
+       done = 1;   /* Pretend eof - drops connection */
+       break;
+      }
 
 
     case TOO_MANY_NONMAIL_CMD:
       s = smtp_cmd_buffer;
-      while (*s != 0 && !isspace(*s)) s++;
+      while (*s && !isspace(*s)) s++;
       incomplete_transaction_log(US"too many non-mail commands");
       log_write(0, LOG_MAIN|LOG_REJECT, "SMTP call from %s dropped: too many "
        "nonmail commands (last was \"%.*s\")",  host_and_ident(FALSE),