Move IP option decode out-of-line
[exim.git] / src / src / smtp_in.c
index aeaffeb37ff702b32ba49f415fdbb7c74c49821d..9f9ff7016860bcc70e34f7cdfd0b24340d5e0e95 100644 (file)
@@ -2,7 +2,7 @@
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
-/* Copyright (c) The Exim Maintainers 2020 - 2023 */
+/* Copyright (c) The Exim Maintainers 2020 - 2024 */
 /* 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 */
 /* 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 */
@@ -72,7 +72,7 @@ enum {
 
   HELO_CMD, EHLO_CMD, DATA_CMD, /* These are listed in the pipelining */
   VRFY_CMD, EXPN_CMD, NOOP_CMD, /* RFC as requiring synchronization */
 
   HELO_CMD, EHLO_CMD, DATA_CMD, /* These are listed in the pipelining */
   VRFY_CMD, EXPN_CMD, NOOP_CMD, /* RFC as requiring synchronization */
-  ETRN_CMD,                     /* This by analogy with TURN from the RFC */
+  ATRN_CMD, 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
   STARTTLS_CMD,                 /* Required by the STARTTLS RFC */
   TLS_AUTH_CMD,                        /* auto-command at start of SSL */
 #ifdef EXPERIMENTAL_XCLIENT
@@ -86,6 +86,9 @@ enum {
   /* These commands need not be synchronized when pipelining */
 
   MAIL_CMD, RCPT_CMD, RSET_CMD,
   /* These commands need not be synchronized when pipelining */
 
   MAIL_CMD, RCPT_CMD, RSET_CMD,
+#ifndef DISABLE_WELLKNOWN
+  WELLKNOWN_CMD,
+#endif
 
   /* This is a dummy to identify the non-sync commands when not pipelining */
 
 
   /* This is a dummy to identify the non-sync commands when not pipelining */
 
@@ -121,7 +124,8 @@ enum {
   /* These are specials that don't correspond to actual commands */
 
   EOF_CMD, OTHER_CMD, BADARG_CMD, BADCHAR_CMD, BADSYN_CMD,
   /* These are specials that don't correspond to actual commands */
 
   EOF_CMD, OTHER_CMD, BADARG_CMD, BADCHAR_CMD, BADSYN_CMD,
-  TOO_MANY_NONMAIL_CMD };
+  TOO_MANY_NONMAIL_CMD
+};
 
 
 /* This is a convenience macro for adding the identity of an SMTP command
 
 
 /* This is a convenience macro for adding the identity of an SMTP command
@@ -227,10 +231,14 @@ static smtp_cmd_list cmd_list[] = {
   { "bdat",       sizeof("bdat")-1,       BDAT_CMD, TRUE,  TRUE  },
   { "quit",       sizeof("quit")-1,       QUIT_CMD, FALSE, TRUE  },
   { "noop",       sizeof("noop")-1,       NOOP_CMD, TRUE,  FALSE },
   { "bdat",       sizeof("bdat")-1,       BDAT_CMD, TRUE,  TRUE  },
   { "quit",       sizeof("quit")-1,       QUIT_CMD, FALSE, TRUE  },
   { "noop",       sizeof("noop")-1,       NOOP_CMD, TRUE,  FALSE },
+  { "atrn",       sizeof("atrn")-1,       ATRN_CMD, TRUE,  FALSE },
   { "etrn",       sizeof("etrn")-1,       ETRN_CMD, TRUE,  FALSE },
   { "vrfy",       sizeof("vrfy")-1,       VRFY_CMD, TRUE,  FALSE },
   { "expn",       sizeof("expn")-1,       EXPN_CMD, TRUE,  FALSE },
   { "etrn",       sizeof("etrn")-1,       ETRN_CMD, TRUE,  FALSE },
   { "vrfy",       sizeof("vrfy")-1,       VRFY_CMD, TRUE,  FALSE },
   { "expn",       sizeof("expn")-1,       EXPN_CMD, TRUE,  FALSE },
-  { "help",       sizeof("help")-1,       HELP_CMD, TRUE,  FALSE }
+  { "help",       sizeof("help")-1,       HELP_CMD, TRUE,  FALSE },
+#ifndef DISABLE_WELLKNOWN
+  { "wellknown",  sizeof("wellknown")-1,  WELLKNOWN_CMD, TRUE,  FALSE },
+#endif
 };
 
 /* This list of names is used for performing the smtp_no_mail logging action. */
 };
 
 /* This list of names is used for performing the smtp_no_mail logging action. */
@@ -253,6 +261,9 @@ uschar * smtp_names[] =
   [SCH_RSET] = US"RSET",
   [SCH_STARTTLS] = US"STARTTLS",
   [SCH_VRFY] = US"VRFY",
   [SCH_RSET] = US"RSET",
   [SCH_STARTTLS] = US"STARTTLS",
   [SCH_VRFY] = US"VRFY",
+#ifndef DISABLE_WELLKNOWN
+  [SCH_WELLKNOWN] = US"WELLKNOWN",
+#endif
 #ifdef EXPERIMENTAL_XCLIENT
   [SCH_XCLIENT] = US"XCLIENT",
 #endif
 #ifdef EXPERIMENTAL_XCLIENT
   [SCH_XCLIENT] = US"XCLIENT",
 #endif
@@ -340,7 +351,6 @@ static int     smtp_had_error;
 
 /* forward declarations */
 static int smtp_read_command(BOOL check_sync, unsigned buffer_lim);
 
 /* forward declarations */
 static int smtp_read_command(BOOL check_sync, unsigned buffer_lim);
-static int synprot_error(int type, int code, uschar *data, uschar *errmess);
 static void smtp_quit_handler(uschar **, uschar **);
 static void smtp_rset_handler(void);
 
 static void smtp_quit_handler(uschar **, uschar **);
 static void smtp_rset_handler(void);
 
@@ -454,6 +464,23 @@ smtp_had_eof = smtp_had_error = 0;
 
 
 
 
 
 
+#ifndef DISABLE_DKIM
+/* Feed received message data to the dkim module */
+/*XXX maybe a global dkim_info? */
+void
+smtp_verify_feed(const uschar * s, unsigned n)
+{
+static misc_module_info * dkim_mi = NULL;
+typedef void (*fn_t)(const uschar *, int);
+
+if (!dkim_mi && !(dkim_mi = misc_mod_findonly(US"dkim")))
+  return;
+
+(((fn_t *) dkim_mi->functions)[DKIM_VERIFY_FEED]) (s, n);
+}
+#endif
+
+
 /* Refill the buffer, and notify DKIM verification code.
 Return false for error or EOF.
 */
 /* Refill the buffer, and notify DKIM verification code.
 Return false for error or EOF.
 */
@@ -497,7 +524,7 @@ if (rc <= 0)
   return FALSE;
   }
 #ifndef DISABLE_DKIM
   return FALSE;
   }
 #ifndef DISABLE_DKIM
-dkim_exim_verify_feed(smtp_inbuffer, rc);
+smtp_verify_feed(smtp_inbuffer, rc);
 #endif
 smtp_inend = smtp_inbuffer + rc;
 smtp_inptr = smtp_inbuffer;
 #endif
 smtp_inend = smtp_inbuffer + rc;
 smtp_inptr = smtp_inbuffer;
@@ -560,7 +587,7 @@ int n = smtp_inend - smtp_inptr;
 if (n > lim)
   n = lim;
 if (n > 0)
 if (n > lim)
   n = lim;
 if (n > 0)
-  dkim_exim_verify_feed(smtp_inptr, n);
+  smtp_verify_feed(smtp_inptr, n);
 #endif
 }
 
 #endif
 }
 
@@ -716,19 +743,24 @@ bdat_getc(unsigned lim)
 uschar * user_msg = NULL;
 uschar * log_msg;
 
 uschar * user_msg = NULL;
 uschar * log_msg;
 
-for(;;)
-  {
 #ifndef DISABLE_DKIM
 #ifndef DISABLE_DKIM
-  unsigned dkim_save;
+misc_module_info * dkim_info = misc_mod_findonly(US"dkim");
+typedef void (*dkim_pause_t)(BOOL);
+dkim_pause_t dkim_pause;
+
+dkim_pause = dkim_info
+  ? ((dkim_pause_t *) dkim_info->functions)[DKIM_VERIFY_PAUSE] : NULL;
 #endif
 
 #endif
 
+for(;;)
+  {
+
   if (chunking_data_left > 0)
     return lwr_receive_getc(chunking_data_left--);
 
   bdat_pop_receive_functions();
 #ifndef DISABLE_DKIM
   if (chunking_data_left > 0)
     return lwr_receive_getc(chunking_data_left--);
 
   bdat_pop_receive_functions();
 #ifndef DISABLE_DKIM
-  dkim_save = dkim_collect_input;
-  dkim_collect_input = 0;
+  if (dkim_pause) dkim_pause(TRUE);
 #endif
 
   /* Unless PIPELINING was offered, there should be no next command
 #endif
 
   /* Unless PIPELINING was offered, there should be no next command
@@ -757,9 +789,7 @@ for(;;)
   if (chunking_state == CHUNKING_LAST)
     {
 #ifndef DISABLE_DKIM
   if (chunking_state == CHUNKING_LAST)
     {
 #ifndef DISABLE_DKIM
-    dkim_collect_input = dkim_save;
-    dkim_exim_verify_feed(NULL, 0);    /* notify EOD */
-    dkim_collect_input = 0;
+    smtp_verify_feed(NULL, 0); /* notify EOD */
 #endif
     return EOD;
     }
 #endif
     return EOD;
     }
@@ -833,7 +863,7 @@ next_cmd:
 
       bdat_push_receive_functions();
 #ifndef DISABLE_DKIM
 
       bdat_push_receive_functions();
 #ifndef DISABLE_DKIM
-      dkim_collect_input = dkim_save;
+      if (dkim_pause) dkim_pause(FALSE);
 #endif
       break;   /* to top of main loop */
       }
 #endif
       break;   /* to top of main loop */
       }
@@ -1227,7 +1257,7 @@ for (smtp_cmd_list * p = cmd_list; p < cmd_list + nelem(cmd_list); p++)
     follow the sender address. */
 
     smtp_cmd_argument = smtp_cmd_buffer + p->len;
     follow the sender address. */
 
     smtp_cmd_argument = smtp_cmd_buffer + p->len;
-    while (isspace(*smtp_cmd_argument)) smtp_cmd_argument++;
+    Uskip_whitespace(&smtp_cmd_argument);
     Ustrcpy(smtp_data_buffer, smtp_cmd_argument);
     smtp_cmd_data = smtp_data_buffer;
 
     Ustrcpy(smtp_data_buffer, smtp_cmd_argument);
     smtp_cmd_data = smtp_data_buffer;
 
@@ -1345,14 +1375,17 @@ const uschar * hostname = sender_fullhost
 gstring * g = string_catn(NULL, US"SMTP connection", 15);
 
 if (LOGGING(connection_id))
 gstring * g = string_catn(NULL, US"SMTP connection", 15);
 
 if (LOGGING(connection_id))
-  g = string_fmt_append(g, " Ci=%lu", connection_id);
+  g = string_fmt_append(g, " Ci=%s", connection_id);
 g = string_catn(g, US" from ", 6);
 
 if (host_checking)
   g = string_cat(g, hostname);
 
 else if (f.sender_host_unknown || f.sender_host_notsocket)
 g = string_catn(g, US" from ", 6);
 
 if (host_checking)
   g = string_cat(g, hostname);
 
 else if (f.sender_host_unknown || f.sender_host_notsocket)
-  g = string_cat(g, sender_ident);
+  g = string_cat(g, sender_ident ? sender_ident : US"NULL");
+
+else if (atrn_mode)
+  g = string_append(g, 2, hostname, US" (ODMR customer)");
 
 else if (f.is_inetd)
   g = string_append(g, 2, hostname, US" (via inetd)");
 
 else if (f.is_inetd)
   g = string_append(g, 2, hostname, US" (via inetd)");
@@ -1671,28 +1704,7 @@ bmi_run = 0;
 bmi_verdicts = NULL;
 #endif
 dnslist_domain = dnslist_matched = NULL;
 bmi_verdicts = NULL;
 #endif
 dnslist_domain = dnslist_matched = NULL;
-#ifdef SUPPORT_SPF
-spf_header_comment = spf_received = spf_result = spf_smtp_comment = NULL;
-spf_result_guessed = FALSE;
-#endif
-#ifndef DISABLE_DKIM
-dkim_cur_signer = dkim_signers =
-dkim_signing_domain = dkim_signing_selector = dkim_signatures = NULL;
-dkim_cur_signer = dkim_signers = dkim_signing_domain = dkim_signing_selector = NULL;
-f.dkim_disable_verify = FALSE;
-dkim_collect_input = 0;
-dkim_verify_overall = dkim_verify_status = dkim_verify_reason = NULL;
-dkim_key_length = 0;
-#endif
-#ifdef SUPPORT_DMARC
-f.dmarc_has_been_checked = f.dmarc_disable_verify = f.dmarc_enable_forensic = FALSE;
-dmarc_domain_policy = dmarc_status = dmarc_status_text =
-dmarc_used_domain = NULL;
-#endif
-#ifdef EXPERIMENTAL_ARC
-arc_state = arc_state_reason = NULL;
-arc_received_instance = 0;
-#endif
+
 dsn_ret = 0;
 dsn_envid = NULL;
 deliver_host = deliver_host_address = NULL;    /* Can be set by ACL */
 dsn_ret = 0;
 dsn_envid = NULL;
 deliver_host = deliver_host_address = NULL;    /* Can be set by ACL */
@@ -1727,6 +1739,7 @@ while (acl_warn_logged)
   store_free(this);
   }
 
   store_free(this);
   }
 
+misc_mod_smtp_reset();
 message_tidyup();
 store_reset(reset_point);
 
 message_tidyup();
 store_reset(reset_point);
 
@@ -1876,10 +1889,11 @@ while (done <= 0)
 
       /* Check maximum number allowed */
 
 
       /* Check maximum number allowed */
 
-      if (recipients_max > 0 && recipients_count + 1 > recipients_max)
+      if (  recipients_max_expanded > 0
+        && recipients_count + 1 > recipients_max_expanded)
        /* The function moan_smtp_batch() does not return. */
        moan_smtp_batch(smtp_cmd_buffer, "%s too many recipients",
        /* The function moan_smtp_batch() does not return. */
        moan_smtp_batch(smtp_cmd_buffer, "%s too many recipients",
-         recipients_max_reject? "552": "452");
+         recipients_max_reject ? "552": "452");
 
       /* Apply SMTP rewrite, then extract address. Don't allow "<>" as a
       recipient address */
 
       /* Apply SMTP rewrite, then extract address. Don't allow "<>" as a
       recipient address */
@@ -1938,13 +1952,13 @@ while (done <= 0)
       break;
 
 
       break;
 
 
-    /* The VRFY, EXPN, HELP, ETRN, and NOOP commands are ignored. */
+    /* The VRFY, EXPN, HELP, ETRN, ATRN and NOOP commands are ignored. */
 
 
-    case VRFY_CMD:
-    case EXPN_CMD:
-    case HELP_CMD:
-    case NOOP_CMD:
-    case ETRN_CMD:
+    case VRFY_CMD: case EXPN_CMD: case HELP_CMD: case NOOP_CMD:
+    case ETRN_CMD: case ATRN_CMD:
+#ifndef DISABLE_WELLKNOWN
+    case WELLKNOWN_CMD:
+#endif
       bsmtp_transaction_linecount = receive_linecount;
       break;
 
       bsmtp_transaction_linecount = receive_linecount;
       break;
 
@@ -2053,9 +2067,12 @@ log_connect_tls_drop(const uschar * what, const uschar * log_msg)
 {
 if (log_reject_target)
   {
 {
 if (log_reject_target)
   {
+#ifdef DISABLE_TLS
+  uschar * tls = NULL;
+#else
   gstring * g = s_tlslog(NULL);
   uschar * tls = string_from_gstring(g);
   gstring * g = s_tlslog(NULL);
   uschar * tls = string_from_gstring(g);
-
+#endif
   log_write(L_connection_reject,
     log_reject_target, "%s%s%s dropped by %s%s%s",
     LOGGING(dnssec) && sender_host_dnssec ? US" DS" : US"",
   log_write(L_connection_reject,
     log_reject_target, "%s%s%s dropped by %s%s%s",
     LOGGING(dnssec) && sender_host_dnssec ? US" DS" : US"",
@@ -2067,6 +2084,132 @@ if (log_reject_target)
 }
 
 
 }
 
 
+
+
+/* IP options: log and reject the connection.
+
+Deal with any IP options that are set. On the systems I have looked at,
+the value of MAX_IPOPTLEN has been 40, meaning that there should never be
+more logging data than will fit in big_buffer. Nevertheless, after somebody
+questioned this code, I've added in some paranoid checking.
+
+Given the disuse of options on the internet as of 2024 I'm tempted to
+drop the detailed parsing and logging. */
+
+#ifdef GLIBC_IP_OPTIONS
+# if (!defined __GLIBC__) || (__GLIBC__ < 2)
+#  define OPTSTYLE 1
+# else
+#  define OPTSTYLE 2
+# endif
+#elif defined DARWIN_IP_OPTIONS
+# define OPTSTYLE 2
+#else
+# define OPTSTYLE 3
+# endif
+
+#if OPTSTYLE == 1
+# define EXIM_IP_OPT_T struct ip_options
+# define OPTSTART      (ipopt->__data)
+#elif OPTSTYLE == 2
+# define EXIM_IP_OPT_T struct ip_opts
+# define OPTSTART      (ipopt->ip_opts);
+#else
+# define EXIM_IP_OPT_T struct ipoption
+# define OPTSTART      (ipopt->ipopt_list);
+#endif
+
+static BOOL
+smtp_in_reject_options(EXIM_IP_OPT_T * ipopt, EXIM_SOCKLEN_T optlen)
+{
+uschar * p, * pend = big_buffer + big_buffer_size;
+uschar * adptr;
+int optcount, sprint_len;
+struct in_addr addr;
+uschar * optstart = US OPTSTART;
+
+DEBUG(D_receive) debug_printf("IP options exist\n");
+
+p = Ustpcpy(big_buffer, "IP options on incoming call:");
+
+for (uschar * opt = optstart; opt && opt < US (ipopt) + optlen; )
+  switch (*opt)
+    {
+    case IPOPT_EOL:
+      opt = NULL;
+      break;
+
+    case IPOPT_NOP:
+      opt++;
+      break;
+
+    case IPOPT_SSRR:
+    case IPOPT_LSRR:
+      if (!
+# if OPTSTYLE == 1
+          string_format(p, pend-p, " %s [@%s%n",
+            *opt == IPOPT_SSRR ? "SSRR" : "LSRR",
+            inet_ntoa(*(struct in_addr *)&ipopt->faddr),
+            &sprint_len)
+# elif OPTSTYLE == 2
+          string_format(p, pend-p, " %s [@%s%n",
+            *opt == IPOPT_SSRR ? "SSRR" : "LSRR",
+            inet_ntoa(ipopt->ip_dst),
+            &sprint_len)
+# else
+          string_format(p, pend-p, " %s [@%s%n",
+            *opt == IPOPT_SSRR ? "SSRR" : "LSRR",
+            inet_ntoa(ipopt->ipopt_dst),
+            &sprint_len)
+# endif
+         )
+       opt = NULL;
+      else
+       {
+       p += sprint_len;
+       optcount = (opt[1] - 3) / sizeof(struct in_addr);
+       adptr = opt + 3;
+       while (optcount-- > 0)
+         {
+         memcpy(&addr, adptr, sizeof(addr));
+         if (!string_format(p, pend - p - 1, "%s%s%n",
+               optcount == 0 ? ":" : "@", inet_ntoa(addr), &sprint_len))
+           { opt = NULL; goto bad_srr; }
+         p += sprint_len;
+         adptr += sizeof(struct in_addr);
+         }
+       *p++ = ']';
+       opt += opt[1];
+       }
+bad_srr:    break;
+
+    default:
+      if (pend - p < 4 + 3*opt[1])
+       opt = NULL;
+      else
+       {
+       p = Ustpcpy(p, "[ ");
+       for (int i = 0; i < opt[1]; i++)
+         p += sprintf(CS p, "%2.2x ", opt[i]);
+       *p++ = ']';
+       opt += opt[1];
+       }
+      break;
+    }
+
+*p = '\0';
+log_write(0, LOG_MAIN, "%s", big_buffer);
+
+/* Refuse any call with IP options. This is what tcpwrappers 7.5 does. */
+
+log_write(0, LOG_MAIN|LOG_REJECT,
+  "connection from %s refused (IP options)", host_and_ident(FALSE));
+
+smtp_printf("554 SMTP service not available\r\n", SP_NO_MORE);
+return FALSE;
+}
+
+
 /*************************************************
 *          Start an SMTP session                 *
 *************************************************/
 /*************************************************
 *          Start an SMTP session                 *
 *************************************************/
@@ -2115,10 +2258,13 @@ if (!host_checking && !f.sender_host_notsocket)
 authenticated_by = NULL;
 
 #ifndef DISABLE_TLS
 authenticated_by = NULL;
 
 #ifndef DISABLE_TLS
-tls_in.ver = tls_in.cipher = tls_in.peerdn = NULL;
-tls_in.ourcert = tls_in.peercert = NULL;
-tls_in.sni = NULL;
-tls_in.ocsp = OCSP_NOT_REQ;
+if (!atrn_mode)
+  {
+  tls_in.ver = tls_in.cipher = tls_in.peerdn = NULL;
+  tls_in.ourcert = tls_in.peercert = NULL;
+  tls_in.sni = NULL;
+  tls_in.ocsp = OCSP_NOT_REQ;
+  }
 fl.tls_advertised = FALSE;
 #endif
 fl.dsn_advertised = FALSE;
 fl.tls_advertised = FALSE;
 #endif
 fl.dsn_advertised = FALSE;
@@ -2157,13 +2303,28 @@ call the local functions instead of the standard C ones. */
 
 smtp_buf_init();
 
 
 smtp_buf_init();
 
-receive_getc = smtp_getc;
-receive_getbuf = smtp_getbuf;
-receive_get_cache = smtp_get_cache;
-receive_hasc = smtp_hasc;
-receive_ungetc = smtp_ungetc;
-receive_feof = smtp_feof;
-receive_ferror = smtp_ferror;
+#ifndef DISABLE_TLS
+if (atrn_mode && tls_in.active.sock >= 0)
+  {
+  receive_getc = tls_getc;
+  receive_getbuf = tls_getbuf;
+  receive_get_cache = tls_get_cache;
+  receive_hasc = tls_hasc;
+  receive_ungetc = tls_ungetc;
+  receive_feof = tls_feof;
+  receive_ferror = tls_ferror;
+  }
+else
+#endif
+  {
+  receive_getc = smtp_getc;
+  receive_getbuf = smtp_getbuf;
+  receive_get_cache = smtp_get_cache;
+  receive_hasc = smtp_hasc;
+  receive_ungetc = smtp_ungetc;
+  receive_feof = smtp_feof;
+  receive_ferror = smtp_ferror;
+  }
 lwr_receive_getc = NULL;
 lwr_receive_getbuf = NULL;
 lwr_receive_hasc = NULL;
 lwr_receive_getc = NULL;
 lwr_receive_getbuf = NULL;
 lwr_receive_hasc = NULL;
@@ -2171,6 +2332,7 @@ lwr_receive_ungetc = NULL;
 
 /* Set up the message size limit; this may be host-specific */
 
 
 /* Set up the message size limit; this may be host-specific */
 
+GET_OPTION("message_size_limit");
 thismessage_size_limit = expand_string_integer(message_size_limit, TRUE);
 if (expand_string_message)
   {
 thismessage_size_limit = expand_string_integer(message_size_limit, TRUE);
 if (expand_string_message)
   {
@@ -2226,31 +2388,15 @@ if (!f.sender_host_unknown)
 
 #if !HAVE_IPV6 && !defined(NO_IP_OPTIONS)
 
 
 #if !HAVE_IPV6 && !defined(NO_IP_OPTIONS)
 
-# ifdef GLIBC_IP_OPTIONS
-#  if (!defined __GLIBC__) || (__GLIBC__ < 2)
-#   define OPTSTYLE 1
-#  else
-#   define OPTSTYLE 2
-#  endif
-# elif defined DARWIN_IP_OPTIONS
-# define OPTSTYLE 2
-# else
-# define OPTSTYLE 3
-# endif
-
   if (!host_checking && !f.sender_host_notsocket)
     {
 # if OPTSTYLE == 1
   if (!host_checking && !f.sender_host_notsocket)
     {
 # if OPTSTYLE == 1
-    EXIM_SOCKLEN_T optlen = sizeof(struct ip_options) + MAX_IPOPTLEN;
-    struct ip_options *ipopt = store_get(optlen, GET_UNTAINTED);
-# elif OPTSTYLE == 2
-    struct ip_opts ipoptblock;
-    struct ip_opts *ipopt = &ipoptblock;
-    EXIM_SOCKLEN_T optlen = sizeof(ipoptblock);
+    EXIM_SOCKLEN_T optlen = sizeof(EXIM_IP_OPT_T) + MAX_IPOPTLEN;
+    EXIM_IP_OPT_T * ipopt = store_get(optlen, GET_UNTAINTED);
 # else
 # else
-    struct ipoption ipoptblock;
-    struct ipoption *ipopt = &ipoptblock;
-    EXIM_SOCKLEN_T optlen = sizeof(ipoptblock);
+    EXIM_IP_OPT_T ipoptblock;
+    EXIM_IP_OPT_T * ipopt = &ipoptblock;
+    EXIM_SOCKLEN_T optlen = sizeof(EXIM_IP_OPT_T);
 # endif
 
     /* Occasional genuine failures of getsockopt() have been seen - for
 # endif
 
     /* Occasional genuine failures of getsockopt() have been seen - for
@@ -2262,7 +2408,7 @@ if (!f.sender_host_unknown)
 
     DEBUG(D_receive) debug_printf("checking for IP options\n");
 
 
     DEBUG(D_receive) debug_printf("checking for IP options\n");
 
-    if (getsockopt(fileno(smtp_out), IPPROTO_IP, IP_OPTIONS, US (ipopt),
+    if (getsockopt(fileno(smtp_out), IPPROTO_IP, IP_OPTIONS, US ipopt,
           &optlen) < 0)
       {
       if (errno != ENOPROTOOPT)
           &optlen) < 0)
       {
       if (errno != ENOPROTOOPT)
@@ -2280,102 +2426,7 @@ if (!f.sender_host_unknown)
     questioned this code, I've added in some paranoid checking. */
 
     else if (optlen > 0)
     questioned this code, I've added in some paranoid checking. */
 
     else if (optlen > 0)
-      {
-      uschar * p = big_buffer;
-      uschar * pend = big_buffer + big_buffer_size;
-      uschar * adptr;
-      int optcount;
-      struct in_addr addr;
-
-# if OPTSTYLE == 1
-      uschar * optstart = US (ipopt->__data);
-# elif OPTSTYLE == 2
-      uschar * optstart = US (ipopt->ip_opts);
-# else
-      uschar * optstart = US (ipopt->ipopt_list);
-# endif
-
-      DEBUG(D_receive) debug_printf("IP options exist\n");
-
-      Ustrcpy(p, "IP options on incoming call:");
-      p += Ustrlen(p);
-
-      for (uschar * opt = optstart; opt && opt < US (ipopt) + optlen; )
-        switch (*opt)
-          {
-          case IPOPT_EOL:
-           opt = NULL;
-           break;
-
-          case IPOPT_NOP:
-           opt++;
-           break;
-
-          case IPOPT_SSRR:
-          case IPOPT_LSRR:
-           if (!
-# if OPTSTYLE == 1
-                string_format(p, pend-p, " %s [@%s",
-                (*opt == IPOPT_SSRR)? "SSRR" : "LSRR",
-                inet_ntoa(*((struct in_addr *)(&(ipopt->faddr)))))
-# elif OPTSTYLE == 2
-                string_format(p, pend-p, " %s [@%s",
-                (*opt == IPOPT_SSRR)? "SSRR" : "LSRR",
-                inet_ntoa(ipopt->ip_dst))
-# else
-                string_format(p, pend-p, " %s [@%s",
-                (*opt == IPOPT_SSRR)? "SSRR" : "LSRR",
-                inet_ntoa(ipopt->ipopt_dst))
-# endif
-             )
-             {
-             opt = NULL;
-             break;
-             }
-
-           p += Ustrlen(p);
-           optcount = (opt[1] - 3) / sizeof(struct in_addr);
-           adptr = opt + 3;
-           while (optcount-- > 0)
-             {
-             memcpy(&addr, adptr, sizeof(addr));
-             if (!string_format(p, pend - p - 1, "%s%s",
-                   (optcount == 0)? ":" : "@", inet_ntoa(addr)))
-               {
-               opt = NULL;
-               break;
-               }
-             p += Ustrlen(p);
-             adptr += sizeof(struct in_addr);
-             }
-           *p++ = ']';
-           opt += opt[1];
-           break;
-
-          default:
-             {
-             if (pend - p < 4 + 3*opt[1]) { opt = NULL; break; }
-             Ustrcat(p, "[ ");
-             p += 2;
-             for (int i = 0; i < opt[1]; i++)
-               p += sprintf(CS p, "%2.2x ", opt[i]);
-             *p++ = ']';
-             }
-           opt += opt[1];
-           break;
-          }
-
-      *p = 0;
-      log_write(0, LOG_MAIN, "%s", big_buffer);
-
-      /* Refuse any call with IP options. This is what tcpwrappers 7.5 does. */
-
-      log_write(0, LOG_MAIN|LOG_REJECT,
-        "connection from %s refused (IP options)", host_and_ident(FALSE));
-
-      smtp_printf("554 SMTP service not available\r\n", SP_NO_MORE);
-      return FALSE;
-      }
+      return smtp_in_reject_options(ipopt);
 
     /* Length of options = 0 => there are no options */
 
 
     /* Length of options = 0 => there are no options */
 
@@ -2540,11 +2591,16 @@ if (!f.sender_host_unknown)
   fl.helo_accept_junk = verify_check_host(&helo_accept_junk_hosts) == OK;
   }
 
   fl.helo_accept_junk = verify_check_host(&helo_accept_junk_hosts) == OK;
   }
 
+/* Expand recipients_max, if needed */
+ {
+  uschar * rme = expand_string(recipients_max);
+  recipients_max_expanded = atoi(CCS rme);
+ }
 /* For batch SMTP input we are now done. */
 
 if (smtp_batched_input) return TRUE;
 
 /* For batch SMTP input we are now done. */
 
 if (smtp_batched_input) return TRUE;
 
-#if defined(SUPPORT_PROXY) || defined(SUPPORT_SOCKS) || defined(EXPERIMETAL_XCLIENT)
+#if defined(SUPPORT_PROXY) || defined(SUPPORT_SOCKS) || defined(EXPERIMENTAL_XCLIENT)
 proxy_session = FALSE;
 #endif
 
 proxy_session = FALSE;
 #endif
 
@@ -2563,6 +2619,7 @@ if (proxy_protocol_host())
 /* Run the connect ACL if it exists */
 
 user_msg = NULL;
 /* Run the connect ACL if it exists */
 
 user_msg = NULL;
+GET_OPTION("acl_smtp_connect");
 if (acl_smtp_connect)
   {
   int rc;
 if (acl_smtp_connect)
   {
   int rc;
@@ -2609,16 +2666,20 @@ if (user_msg)
     esclen = codelen - 4;
     }
   }
     esclen = codelen - 4;
     }
   }
-else if (!(s = expand_string(smtp_banner)))
+else
   {
   {
-  log_write(0, f.expand_string_forcedfail ? LOG_MAIN : LOG_MAIN|LOG_PANIC_DIE,
-    "Expansion of \"%s\" (smtp_banner) failed: %s",
-    smtp_banner, expand_string_message);
-  /* for force-fail */
-#ifndef DISABLE_TLS
-  if (tls_in.on_connect) tls_close(NULL, TLS_SHUTDOWN_WAIT);
-#endif
-  return FALSE;
+  GET_OPTION("smtp_banner");
+  if (!(s = expand_string(smtp_banner)))
+    {
+    log_write(0, f.expand_string_forcedfail ? LOG_MAIN : LOG_MAIN|LOG_PANIC_DIE,
+      "Expansion of \"%s\" (smtp_banner) failed: %s",
+      smtp_banner, expand_string_message);
+    /* for force-fail */
+  #ifndef DISABLE_TLS
+    if (tls_in.on_connect) tls_close(NULL, TLS_SHUTDOWN_WAIT);
+  #endif
+    return FALSE;
+    }
   }
 
 /* Remove any terminating newlines; might as well remove trailing space too */
   }
 
 /* Remove any terminating newlines; might as well remove trailing space too */
@@ -2703,7 +2764,8 @@ smtp_printf("%Y",
 handshake arrived.  If so we must have managed a TFO. */
 
 #ifdef TCP_FASTOPEN
 handshake arrived.  If so we must have managed a TFO. */
 
 #ifdef TCP_FASTOPEN
-if (sender_host_address && !f.sender_host_notsocket) tfo_in_check();
+if (sender_host_address && !f.sender_host_notsocket && !atrn_mode)
+  tfo_in_check();
 #endif
 
 return TRUE;
 #endif
 
 return TRUE;
@@ -2733,11 +2795,17 @@ Returns:    -1   limit of syntax/protocol errors NOT exceeded
 These values fit in with the values of the "done" variable in the main
 processing loop in smtp_setup_msg(). */
 
 These values fit in with the values of the "done" variable in the main
 processing loop in smtp_setup_msg(). */
 
-static int
+int
 synprot_error(int type, int code, uschar *data, uschar *errmess)
 {
 int yield = -1;
 
 synprot_error(int type, int code, uschar *data, uschar *errmess)
 {
 int yield = -1;
 
+#ifndef DISABLE_EVENT
+event_raise(event_action,
+  L_smtp_syntax_error ? US"smtp:fail:syntax" : US"smtp:fail:protocol",
+  errmess, NULL);
+#endif
+
 log_write(type, LOG_MAIN, "SMTP %s error in \"%s\" %s %s",
   type == L_smtp_syntax_error ? "syntax" : "protocol",
   string_printing(smtp_cmd_buffer), host_and_ident(TRUE), errmess);
 log_write(type, LOG_MAIN, "SMTP %s error in \"%s\" %s %s",
   type == L_smtp_syntax_error ? "syntax" : "protocol",
   string_printing(smtp_cmd_buffer), host_and_ident(TRUE), errmess);
@@ -2817,10 +2885,8 @@ if (fl.rcpt_in_progress)
 We only handle pipelining these responses as far as nonfinal/final groups,
 not the whole MAIL/RCPT/DATA response set. */
 
 We only handle pipelining these responses as far as nonfinal/final groups,
 not the whole MAIL/RCPT/DATA response set. */
 
-for (;;)
-  {
-  uschar *nl = Ustrchr(msg, '\n');
-  if (!nl)
+for (uschar * nl;;)
+  if (!(nl = Ustrchr(msg, '\n')))
     {
     smtp_printf("%.3s%c%.*s%s\r\n", !final, code, final ? ' ':'-', esclen, esc, msg);
     return;
     {
     smtp_printf("%.3s%c%.*s%s\r\n", !final, code, final ? ' ':'-', esclen, esc, msg);
     return;
@@ -2837,7 +2903,6 @@ for (;;)
     msg = nl + 1;
     Uskip_whitespace(&msg);
     }
     msg = nl + 1;
     Uskip_whitespace(&msg);
     }
-  }
 }
 
 
 }
 
 
@@ -2956,7 +3021,8 @@ smtp_code = rc == FAIL ? acl_wherecodes[where] : US"451";
 smtp_message_code(&smtp_code, &codelen, &user_msg, &log_msg,
   where != ACL_WHERE_VRFY);
 
 smtp_message_code(&smtp_code, &codelen, &user_msg, &log_msg,
   where != ACL_WHERE_VRFY);
 
-/* We used to have sender_address here; however, there was a bug that was not
+/* Get info for logging.
+We used to have sender_address here; however, there was a bug that was not
 updating sender_address after a rewrite during a verify. When this bug was
 fixed, sender_address at this point became the rewritten address. I'm not sure
 this is what should be logged, so I've changed to logging the unrewritten
 updating sender_address after a rewrite during a verify. When this bug was
 fixed, sender_address at this point became the rewritten address. I'm not sure
 this is what should be logged, so I've changed to logging the unrewritten
@@ -2979,9 +3045,9 @@ switch (where)
 
     if (where == ACL_WHERE_AUTH)       /* avoid logging auth creds */
       {
 
     if (where == ACL_WHERE_AUTH)       /* avoid logging auth creds */
       {
-      uschar * s;
-      for (s = smtp_cmd_data; *s && !isspace(*s); ) s++;
-      lim = s - smtp_cmd_data; /* atop after method */
+      uschar * s = smtp_cmd_data;
+      Uskip_nonwhite(&s);
+      lim = s - smtp_cmd_data; /* stop after method */
       }
     what = string_sprintf("%s %.*s", acl_wherenames[where], lim, place);
     }
       }
     what = string_sprintf("%s %.*s", acl_wherenames[where], lim, place);
     }
@@ -3179,6 +3245,7 @@ fl.smtp_exit_function_called = TRUE;
 
 /* Call the not-QUIT ACL, if there is one, unless no reason is given. */
 
 
 /* Call the not-QUIT ACL, if there is one, unless no reason is given. */
 
+GET_OPTION("acl_smtp_notquit");
 if (acl_smtp_notquit && reason)
   {
   smtp_notquit_reason = reason;
 if (acl_smtp_notquit && reason)
   {
   smtp_notquit_reason = reason;
@@ -3359,7 +3426,7 @@ Returns:       nothing
 */
 
 static void
 */
 
 static void
-smtp_user_msg(uschar *code, uschar *user_msg)
+smtp_user_msg(uschar * code, uschar * user_msg)
 {
 int len = 3;
 smtp_message_code(&code, &len, &user_msg, NULL, TRUE);
 {
 int len = 3;
 smtp_message_code(&code, &len, &user_msg, NULL, TRUE);
@@ -3376,9 +3443,9 @@ int rc;
 
 /* Set up globals for error messages */
 
 
 /* Set up globals for error messages */
 
-authenticator_name = au->name;
-driver_srcfile = au->srcfile;
-driver_srcline = au->srcline;
+authenticator_name = au->drinst.name;
+driver_srcfile = au->drinst.srcfile;
+driver_srcline = au->drinst.srcline;
 
 /* Run the checking code, passing the remainder of the command line as
 data. Initials the $auth<n> variables as empty. Initialize $0 empty and set
 
 /* Run the checking code, passing the remainder of the command line as
 data. Initials the $auth<n> variables as empty. Initialize $0 empty and set
@@ -3396,7 +3463,10 @@ for (int i = 0; i < AUTH_VARS; i++) auth_vars[i] = NULL;
 expand_nmax = 0;
 expand_nlength[0] = 0;   /* $0 contains nothing */
 
 expand_nmax = 0;
 expand_nlength[0] = 0;   /* $0 contains nothing */
 
-rc = (au->info->servercode)(au, smtp_cmd_data);
+  {
+  auth_info * ai = au->drinst.info;
+  rc = (ai->servercode)(au, smtp_cmd_data);
+  }
 if (au->set_id) set_id = expand_string(au->set_id);
 expand_nmax = -1;        /* Reset numeric variables */
 for (int i = 0; i < AUTH_VARS; i++) auth_vars[i] = NULL;   /* Reset $auth<n> */
 if (au->set_id) set_id = expand_string(au->set_id);
 expand_nmax = -1;        /* Reset numeric variables */
 for (int i = 0; i < AUTH_VARS; i++) auth_vars[i] = NULL;   /* Reset $auth<n> */
@@ -3425,7 +3495,7 @@ switch(rc)
     if (!au->set_id || set_id)    /* Complete success */
       {
       if (set_id) authenticated_id = string_copy_perm(set_id, TRUE);
     if (!au->set_id || set_id)    /* Complete success */
       {
       if (set_id) authenticated_id = string_copy_perm(set_id, TRUE);
-      sender_host_authenticated = au->name;
+      sender_host_authenticated = au->drinst.name;
       sender_host_auth_pubname  = au->public_name;
       authentication_failed = FALSE;
       authenticated_fail_id = NULL;   /* Impossible to already be set? */
       sender_host_auth_pubname  = au->public_name;
       authentication_failed = FALSE;
       authenticated_fail_id = NULL;   /* Impossible to already be set? */
@@ -3515,6 +3585,7 @@ smtp_quit_handler(uschar ** user_msgp, uschar ** log_msgp)
 HAD(SCH_QUIT);
 f.smtp_in_quit = TRUE;
 incomplete_transaction_log(US"QUIT");
 HAD(SCH_QUIT);
 f.smtp_in_quit = TRUE;
 incomplete_transaction_log(US"QUIT");
+GET_OPTION("acl_smtp_quit");
 if (  acl_smtp_quit
    && acl_check(ACL_WHERE_QUIT, NULL, acl_smtp_quit, user_msgp, log_msgp)
        == ERROR)
 if (  acl_smtp_quit
    && acl_check(ACL_WHERE_QUIT, NULL, acl_smtp_quit, user_msgp, log_msgp)
        == ERROR)
@@ -3564,6 +3635,36 @@ if (chunking_state > CHUNKING_OFFERED)
 }
 
 
 }
 
 
+#ifndef DISABLE_WELLKNOWN
+static int
+smtp_wellknown_handler(void)
+{
+if (verify_check_host(&wellknown_advertise_hosts) != FAIL)
+  {
+  GET_OPTION("acl_smtp_wellknown");
+  if (acl_smtp_wellknown)
+    {
+    uschar * user_msg = NULL, * log_msg;
+    int rc;
+
+    if ((rc = acl_check(ACL_WHERE_WELLKNOWN, NULL, acl_smtp_wellknown,
+               &user_msg, &log_msg)) != OK)
+      return smtp_handle_acl_fail(ACL_WHERE_WELLKNOWN, rc, user_msg, log_msg);
+    else if (!wellknown_response)
+      return smtp_handle_acl_fail(ACL_WHERE_WELLKNOWN, ERROR, user_msg, log_msg);
+    smtp_user_msg(US"250", wellknown_response);
+    return 0;
+    }
+  }
+
+smtp_printf("554 not permitted\r\n", SP_NO_MORE);
+log_write(0, LOG_MAIN|LOG_REJECT, "rejected \"%s\" from %s",
+             smtp_cmd_buffer, sender_helo_name, host_and_ident(FALSE));
+return 0;
+}
+#endif
+
+
 static int
 expand_mailmax(const uschar * s)
 {
 static int
 expand_mailmax(const uschar * s)
 {
@@ -3627,7 +3728,7 @@ cmd_list[CL_EHLO].is_mail_cmd = TRUE;
 cmd_list[CL_STLS].is_mail_cmd = TRUE;
 #endif
 
 cmd_list[CL_STLS].is_mail_cmd = TRUE;
 #endif
 
-if (lwr_receive_getc != NULL)
+if (lwr_receive_getc && !atrn_mode)
   {
   /* This should have already happened, but if we've gotten confused,
   force a reset here. */
   {
   /* This should have already happened, but if we've gotten confused,
   force a reset here. */
@@ -3655,24 +3756,17 @@ value. The values are 2 larger than the required yield of the function. */
 
 while (done <= 0)
   {
 
 while (done <= 0)
   {
-  const uschar **argv;
-  uschar *etrn_command;
-  uschar *etrn_serialize_key;
-  uschar *errmess;
-  uschar *log_msg, *smtp_code;
-  uschar *user_msg = NULL;
-  uschar *recipient = NULL;
-  uschar *hello = NULL;
-  uschar *s, *ss;
-  BOOL was_rej_mail = FALSE;
-  BOOL was_rcpt = FALSE;
+  const uschar ** argv;
+  uschar * etrn_command, * etrn_serialize_key, * errmess;
+  uschar * log_msg, * smtp_code;
+  uschar * user_msg = NULL, * recipient = NULL, * hello = NULL;
+  uschar * s, * ss;
+  BOOL was_rej_mail = FALSE, was_rcpt = FALSE;
   void (*oldsignal)(int);
   pid_t pid;
   int start, end, sender_domain, recipient_domain;
   void (*oldsignal)(int);
   pid_t pid;
   int start, end, sender_domain, recipient_domain;
-  int rc;
-  int c;
-  uschar *orcpt = NULL;
-  int dsn_flags;
+  int rc, c, dsn_flags;
+  uschar * orcpt = NULL;
   gstring * g;
 
 #ifdef AUTH_TLS
   gstring * g;
 
 #ifdef AUTH_TLS
@@ -3685,9 +3779,10 @@ while (done <= 0)
     {
     cmd_list[CL_TLAU].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)
+    for (auth_instance * au = auths; au; au = au->drinst.next)
+      if (strcmpic(US"tls", au->drinst.driver_name) == 0)
        {
        {
+       GET_OPTION("acl_smtp_auth");
        if (  acl_smtp_auth
           && (rc = acl_check(ACL_WHERE_AUTH, NULL, acl_smtp_auth,
                      &user_msg, &log_msg)) != OK
        if (  acl_smtp_auth
           && (rc = acl_check(ACL_WHERE_AUTH, NULL, acl_smtp_auth,
                      &user_msg, &log_msg)) != OK
@@ -3705,7 +3800,7 @@ while (done <= 0)
 #ifndef DISABLE_EVENT
             {
              uschar * save_name = sender_host_authenticated, * logmsg;
 #ifndef DISABLE_EVENT
             {
              uschar * save_name = sender_host_authenticated, * logmsg;
-             sender_host_authenticated = au->name;
+             sender_host_authenticated = au->drinst.name;
              if ((logmsg = event_raise(event_action, US"auth:fail", s, NULL)))
                log_write(0, LOG_MAIN, "%s", logmsg);
              sender_host_authenticated = save_name;
              if ((logmsg = event_raise(event_action, US"auth:fail", s, NULL)))
                log_write(0, LOG_MAIN, "%s", logmsg);
              sender_host_authenticated = save_name;
@@ -3766,6 +3861,7 @@ while (done <= 0)
 
       /* Check the ACL */
 
 
       /* Check the ACL */
 
+      GET_OPTION("acl_smtp_auth");
       if (  acl_smtp_auth
         && (rc = acl_check(ACL_WHERE_AUTH, NULL, acl_smtp_auth,
                    &user_msg, &log_msg)) != OK
       if (  acl_smtp_auth
         && (rc = acl_check(ACL_WHERE_AUTH, NULL, acl_smtp_auth,
                    &user_msg, &log_msg)) != OK
@@ -3791,8 +3887,8 @@ while (done <= 0)
 
       if (*smtp_cmd_data)
        {
 
       if (*smtp_cmd_data)
        {
-       *smtp_cmd_data++ = 0;
-       while (isspace(*smtp_cmd_data)) smtp_cmd_data++;
+       *smtp_cmd_data++ = '\0';
+       Uskip_whitespace(&smtp_cmd_data);
        }
 
       /* Search for an authentication mechanism which is configured for use
        }
 
       /* Search for an authentication mechanism which is configured for use
@@ -3803,7 +3899,7 @@ while (done <= 0)
        auth_instance * au;
        uschar * smtp_resp, * errmsg;
 
        auth_instance * au;
        uschar * smtp_resp, * errmsg;
 
-       for (au = auths; au; au = au->next)
+       for (au = auths; au; au = au->drinst.next)
          if (strcmpic(s, au->public_name) == 0 && au->server &&
              (au->advertised || f.allow_auth_unadvertised))
            break;
          if (strcmpic(s, au->public_name) == 0 && au->server &&
              (au->advertised || f.allow_auth_unadvertised))
            break;
@@ -3818,7 +3914,7 @@ while (done <= 0)
            uschar * logmsg = NULL;
 #ifndef DISABLE_EVENT
             {uschar * save_name = sender_host_authenticated;
            uschar * logmsg = NULL;
 #ifndef DISABLE_EVENT
             {uschar * save_name = sender_host_authenticated;
-             sender_host_authenticated = au->name;
+             sender_host_authenticated = au->drinst.name;
              logmsg = event_raise(event_action, US"auth:fail", smtp_resp, NULL);
              sender_host_authenticated = save_name;
             }
              logmsg = event_raise(event_action, US"auth:fail", smtp_resp, NULL);
              sender_host_authenticated = save_name;
             }
@@ -3827,7 +3923,7 @@ while (done <= 0)
              log_write(0, LOG_MAIN|LOG_REJECT, "%s", logmsg);
            else
              log_write(0, LOG_MAIN|LOG_REJECT, "%s authenticator failed for %s: %s",
              log_write(0, LOG_MAIN|LOG_REJECT, "%s", logmsg);
            else
              log_write(0, LOG_MAIN|LOG_REJECT, "%s authenticator failed for %s: %s",
-               au->name, host_and_ident(FALSE), errmsg);
+               au->drinst.name, host_and_ident(FALSE), errmsg);
            }
          }
        else
            }
          }
        else
@@ -3903,10 +3999,10 @@ while (done <= 0)
       if (!f.sender_host_unknown)
        {
        BOOL old_helo_verified = f.helo_verified;
       if (!f.sender_host_unknown)
        {
        BOOL old_helo_verified = f.helo_verified;
-       uschar *p = smtp_cmd_data;
+       uschar * p = smtp_cmd_data;
 
 
-       while (*p != 0 && !isspace(*p)) { *p = tolower(*p); p++; }
-       *p = 0;
+       while (*p && !isspace(*p)) { *p = tolower(*p); p++; }
+       *p = '\0';
 
        /* Force a reverse lookup if HELO quoted something in helo_lookup_domains
        because otherwise the log can be confusing. */
 
        /* Force a reverse lookup if HELO quoted something in helo_lookup_domains
        because otherwise the log can be confusing. */
@@ -3952,14 +4048,19 @@ while (done <= 0)
          }
        }
 
          }
        }
 
-#ifdef SUPPORT_SPF
-      /* set up SPF context */
-      spf_conn_init(sender_helo_name, sender_host_address);
-#endif
+      /* For any misc-module having a connection-init routine, call it. */
+      
+      if (misc_mod_conn_init(sender_helo_name, sender_host_address) != OK)
+       {
+       DEBUG(D_receive) debug_printf("A module conn-init routine failed\n");
+       done = 1;
+       break;
+       }
 
       /* Apply an ACL check if one is defined; afterwards, recheck
       synchronization in case the client started sending in a delay. */
 
 
       /* Apply an ACL check if one is defined; afterwards, recheck
       synchronization in case the client started sending in a delay. */
 
+      GET_OPTION("acl_smtp_helo");
       if (acl_smtp_helo)
        if ((rc = acl_check(ACL_WHERE_HELO, NULL, acl_smtp_helo,
                  &user_msg, &log_msg)) != OK)
       if (acl_smtp_helo)
        if ((rc = acl_check(ACL_WHERE_HELO, NULL, acl_smtp_helo,
                  &user_msg, &log_msg)) != OK)
@@ -4054,15 +4155,15 @@ while (done <= 0)
          g = string_catn(g, US"-SIZE\r\n", 7);
          }
 
          g = string_catn(g, US"-SIZE\r\n", 7);
          }
 
-#ifdef EXPERIMENTAL_ESMTP_LIMITS
-       if (  (smtp_mailcmd_max > 0 || recipients_max)
+#ifndef DISABLE_ESMTP_LIMITS
+       if (  (smtp_mailcmd_max > 0 || recipients_max_expanded > 0)
           && verify_check_host(&limits_advertise_hosts) == OK)
          {
          g = string_fmt_append(g, "%.3s-LIMITS", smtp_code);
          if (smtp_mailcmd_max > 0)
            g = string_fmt_append(g, " MAILMAX=%d", smtp_mailcmd_max);
           && verify_check_host(&limits_advertise_hosts) == OK)
          {
          g = string_fmt_append(g, "%.3s-LIMITS", smtp_code);
          if (smtp_mailcmd_max > 0)
            g = string_fmt_append(g, " MAILMAX=%d", smtp_mailcmd_max);
-         if (recipients_max)
-           g = string_fmt_append(g, " RCPTMAX=%d", recipients_max);
+         if (recipients_max_expanded > 0)
+           g = string_fmt_append(g, " RCPTMAX=%d", recipients_max_expanded);
          g = string_catn(g, US"\r\n", 2);
          }
 #endif
          g = string_catn(g, US"\r\n", 2);
          }
 #endif
@@ -4088,19 +4189,33 @@ while (done <= 0)
          fl.dsn_advertised = TRUE;
          }
 
          fl.dsn_advertised = TRUE;
          }
 
-       /* Advertise ETRN/VRFY/EXPN if there's are ACL checking whether a host is
-       permitted to issue them; a check is made when any host actually tries. */
+       /* Advertise ATRN/ETRN/VRFY/EXPN if there's are ACL checking whether a
+       host is permitted to issue them; a check is made when any host actually
+       tries. */
 
 
+       GET_OPTION("acl_smtp_atrn");
+       if (acl_smtp_atrn && !atrn_mode)
+         {
+         const uschar * s = expand_cstring(acl_smtp_atrn);
+         if (s && *s)
+           {
+           g = string_catn(g, smtp_code, 3);
+           g = string_catn(g, US"-ATRN\r\n", 7);
+           }
+         }
+       GET_OPTION("acl_smtp_etrn");
        if (acl_smtp_etrn)
          {
          g = string_catn(g, smtp_code, 3);
          g = string_catn(g, US"-ETRN\r\n", 7);
          }
        if (acl_smtp_etrn)
          {
          g = string_catn(g, smtp_code, 3);
          g = string_catn(g, US"-ETRN\r\n", 7);
          }
+       GET_OPTION("acl_smtp_vrfy");
        if (acl_smtp_vrfy)
          {
          g = string_catn(g, smtp_code, 3);
          g = string_catn(g, US"-VRFY\r\n", 7);
          }
        if (acl_smtp_vrfy)
          {
          g = string_catn(g, smtp_code, 3);
          g = string_catn(g, US"-VRFY\r\n", 7);
          }
+       GET_OPTION("acl_smtp_expn");
        if (acl_smtp_expn)
          {
          g = string_catn(g, smtp_code, 3);
        if (acl_smtp_expn)
          {
          g = string_catn(g, smtp_code, 3);
@@ -4147,17 +4262,17 @@ while (done <= 0)
           )
          {
          BOOL first = TRUE;
           )
          {
          BOOL first = TRUE;
-         for (auth_instance * au = auths; au; au = au->next)
+         for (auth_instance * au = auths; au; au = au->drinst.next)
            {
            au->advertised = FALSE;
            if (au->server)
              {
              DEBUG(D_auth+D_expand) debug_printf_indent(
                "Evaluating advertise_condition for %s %s athenticator\n",
            {
            au->advertised = FALSE;
            if (au->server)
              {
              DEBUG(D_auth+D_expand) debug_printf_indent(
                "Evaluating advertise_condition for %s %s athenticator\n",
-               au->name, au->public_name);
+               au->drinst.name, au->public_name);
              if (  !au->advertise_condition
              if (  !au->advertise_condition
-                || expand_check_condition(au->advertise_condition, au->name,
-                       US"authenticator")
+                || expand_check_condition(au->advertise_condition,
+                                         au->drinst.name, US"authenticator")
                 )
                {
                int saveptr;
                 )
                {
                int saveptr;
@@ -4190,12 +4305,12 @@ while (done <= 0)
          chunking_state = CHUNKING_OFFERED;
          }
 
          chunking_state = CHUNKING_OFFERED;
          }
 
+#ifndef DISABLE_TLS
        /* Advertise TLS (Transport Level Security) aka SSL (Secure Socket Layer)
        if it has been included in the binary, and the host matches
        tls_advertise_hosts. We must *not* advertise if we are already in a
        secure connection. */
 
        /* Advertise TLS (Transport Level Security) aka SSL (Secure Socket Layer)
        if it has been included in the binary, and the host matches
        tls_advertise_hosts. We must *not* advertise if we are already in a
        secure connection. */
 
-#ifndef DISABLE_TLS
        if (tls_in.active.sock < 0 &&
            verify_check_host(&tls_advertise_hosts) != FAIL)
          {
        if (tls_in.active.sock < 0 &&
            verify_check_host(&tls_advertise_hosts) != FAIL)
          {
@@ -4229,6 +4344,13 @@ while (done <= 0)
          fl.smtputf8_advertised = TRUE;
          }
 #endif
          fl.smtputf8_advertised = TRUE;
          }
 #endif
+#ifndef DISABLE_WELLKNOWN
+       if (verify_check_host(&wellknown_advertise_hosts) != FAIL)
+         {
+         g = string_catn(g, smtp_code, 3);
+         g = string_catn(g, US"-WELLKNOWN\r\n", 12);
+         }
+#endif
 
        /* Finish off the multiline reply with one that is always available. */
 
 
        /* Finish off the multiline reply with one that is always available. */
 
@@ -4276,6 +4398,14 @@ while (done <= 0)
       toomany = FALSE;
       break;   /* HELO/EHLO */
 
       toomany = FALSE;
       break;   /* HELO/EHLO */
 
+#ifndef DISABLE_WELLKNOWN
+    case WELLKNOWN_CMD:
+      HAD(SCH_WELLKNOWN);
+      smtp_mailcmd_count++;
+      smtp_wellknown_handler();
+      break;
+#endif
+
 #ifdef EXPERIMENTAL_XCLIENT
     case XCLIENT_CMD:
       {
 #ifdef EXPERIMENTAL_XCLIENT
     case XCLIENT_CMD:
       {
@@ -4329,9 +4459,10 @@ while (done <= 0)
        if (  fl.helo_verify_required
           || verify_check_host(&hosts_require_helo) == OK)
          {
        if (  fl.helo_verify_required
           || verify_check_host(&hosts_require_helo) == OK)
          {
-         smtp_printf("503 HELO or EHLO required\r\n", SP_NO_MORE);
          log_write(0, LOG_MAIN|LOG_REJECT, "rejected MAIL from %s: no "
            "HELO/EHLO given", host_and_ident(FALSE));
          log_write(0, LOG_MAIN|LOG_REJECT, "rejected MAIL from %s: no "
            "HELO/EHLO given", host_and_ident(FALSE));
+         done = synprot_error(L_smtp_protocol_error, 503, NULL,
+                     US"HELO or EHLO required");
          break;
          }
        else if (smtp_mailcmd_max < 0)
          break;
          }
        else if (smtp_mailcmd_max < 0)
@@ -4487,7 +4618,7 @@ while (done <= 0)
              int rc;
              uschar *ignore_msg;
 
              int rc;
              uschar *ignore_msg;
 
-             if (auth_xtextdecode(value, &authenticated_sender) < 0)
+             if (xtextdecode(value, &authenticated_sender) < 0)
                {
                /* Put back terminator overrides for error message */
                value[-1] = '=';
                {
                /* Put back terminator overrides for error message */
                value[-1] = '=';
@@ -4496,6 +4627,7 @@ while (done <= 0)
                  US"invalid data for AUTH");
                goto COMMAND_LOOP;
                }
                  US"invalid data for AUTH");
                goto COMMAND_LOOP;
                }
+             GET_OPTION("acl_smtp_mailauth");
              if (!acl_smtp_mailauth)
                {
                ignore_msg = US"client not authenticated";
              if (!acl_smtp_mailauth)
                {
                ignore_msg = US"client not authenticated";
@@ -4514,7 +4646,7 @@ while (done <= 0)
                  if (authenticated_by == NULL ||
                      authenticated_by->mail_auth_condition == NULL ||
                      expand_check_condition(authenticated_by->mail_auth_condition,
                  if (authenticated_by == NULL ||
                      authenticated_by->mail_auth_condition == NULL ||
                      expand_check_condition(authenticated_by->mail_auth_condition,
-                         authenticated_by->name, US"authenticator"))
+                         authenticated_by->drinst.name, US"authenticator"))
                    break;     /* Accept the AUTH */
 
                  ignore_msg = US"server_mail_auth_condition failed";
                    break;     /* Accept the AUTH */
 
                  ignore_msg = US"server_mail_auth_condition failed";
@@ -4692,6 +4824,7 @@ while (done <= 0)
       when pipelining is not advertised, do another sync check in case the ACL
       delayed and the client started sending in the meantime. */
 
       when pipelining is not advertised, do another sync check in case the ACL
       delayed and the client started sending in the meantime. */
 
+      GET_OPTION("acl_smtp_mail");
       if (acl_smtp_mail)
        {
        rc = acl_check(ACL_WHERE_MAIL, NULL, acl_smtp_mail, &user_msg, &log_msg);
       if (acl_smtp_mail)
        {
        rc = acl_check(ACL_WHERE_MAIL, NULL, acl_smtp_mail, &user_msg, &log_msg);
@@ -4900,7 +5033,8 @@ while (done <= 0)
 
       /* Check maximum allowed */
 
 
       /* Check maximum allowed */
 
-      if (rcpt_count+1 < 0 || rcpt_count > recipients_max && recipients_max > 0)
+      if (  rcpt_count+1 < 0
+         || rcpt_count > recipients_max_expanded && recipients_max_expanded > 0)
        {
        if (recipients_max_reject)
          {
        {
        if (recipients_max_reject)
          {
@@ -4946,10 +5080,13 @@ while (done <= 0)
       if (f.recipients_discarded)
        rc = DISCARD;
       else
       if (f.recipients_discarded)
        rc = DISCARD;
       else
+       {
+       GET_OPTION("acl_smtp_rcpt");
        if (  (rc = acl_check(ACL_WHERE_RCPT, recipient, acl_smtp_rcpt, &user_msg,
                      &log_msg)) == OK
           && !f.smtp_in_pipelining_advertised && !check_sync())
          goto SYNC_FAILURE;
        if (  (rc = acl_check(ACL_WHERE_RCPT, recipient, acl_smtp_rcpt, &user_msg,
                      &log_msg)) == OK
           && !f.smtp_in_pipelining_advertised && !check_sync())
          goto SYNC_FAILURE;
+       }
 
       /* The ACL was happy */
 
 
       /* The ACL was happy */
 
@@ -5112,11 +5249,9 @@ while (done <= 0)
        since the ACL may have delayed.  To handle cutthrough delivery enforce a
        dummy call to get the DATA command sent. */
 
        since the ACL may have delayed.  To handle cutthrough delivery enforce a
        dummy call to get the DATA command sent. */
 
+       GET_OPTION("acl_smtp_predata");
        if (!acl_smtp_predata && cutthrough.cctx.sock < 0)
        if (!acl_smtp_predata && cutthrough.cctx.sock < 0)
-         {
-         if (!check_sync()) goto SYNC_FAILURE;
          rc = OK;
          rc = OK;
-         }
        else
          {
          uschar * acl = acl_smtp_predata ? acl_smtp_predata : US"accept";
        else
          {
          uschar * acl = acl_smtp_predata ? acl_smtp_predata : US"accept";
@@ -5173,6 +5308,7 @@ while (done <= 0)
                                    US"verify")))
          break;
 
                                    US"verify")))
          break;
 
+      GET_OPTION("acl_smtp_vrfy");
       if ((rc = acl_check(ACL_WHERE_VRFY, address, acl_smtp_vrfy,
                    &user_msg, &log_msg)) != OK)
        done = smtp_handle_acl_fail(ACL_WHERE_VRFY, rc, user_msg, log_msg);
       if ((rc = acl_check(ACL_WHERE_VRFY, address, acl_smtp_vrfy,
                    &user_msg, &log_msg)) != OK)
        done = smtp_handle_acl_fail(ACL_WHERE_VRFY, rc, user_msg, log_msg);
@@ -5211,6 +5347,7 @@ while (done <= 0)
 
     case EXPN_CMD:
       HAD(SCH_EXPN);
 
     case EXPN_CMD:
       HAD(SCH_EXPN);
+      GET_OPTION("acl_smtp_expn");
       rc = acl_check(ACL_WHERE_EXPN, NULL, acl_smtp_expn, &user_msg, &log_msg);
       if (rc != OK)
        done = smtp_handle_acl_fail(ACL_WHERE_EXPN, rc, user_msg, log_msg);
       rc = acl_check(ACL_WHERE_EXPN, NULL, acl_smtp_expn, &user_msg, &log_msg);
       if (rc != OK)
        done = smtp_handle_acl_fail(ACL_WHERE_EXPN, rc, user_msg, log_msg);
@@ -5240,6 +5377,7 @@ while (done <= 0)
 
       /* Apply an ACL check if one is defined */
 
 
       /* Apply an ACL check if one is defined */
 
+      GET_OPTION("acl_smtp_starttls");
       if (  acl_smtp_starttls
         && (rc = acl_check(ACL_WHERE_STARTTLS, NULL, acl_smtp_starttls,
                    &user_msg, &log_msg)) != OK
       if (  acl_smtp_starttls
         && (rc = acl_check(ACL_WHERE_STARTTLS, NULL, acl_smtp_starttls,
                    &user_msg, &log_msg)) != OK
@@ -5358,6 +5496,7 @@ while (done <= 0)
        case QUIT_CMD:
          f.smtp_in_quit = TRUE;
          user_msg = NULL;
        case QUIT_CMD:
          f.smtp_in_quit = TRUE;
          user_msg = NULL;
+         GET_OPTION("acl_smtp_quit");
          if (  acl_smtp_quit
             && ((rc = acl_check(ACL_WHERE_QUIT, NULL, acl_smtp_quit, &user_msg,
                                &log_msg)) == ERROR))
          if (  acl_smtp_quit
             && ((rc = acl_check(ACL_WHERE_QUIT, NULL, acl_smtp_quit, &user_msg,
                                &log_msg)) == ERROR))
@@ -5420,9 +5559,14 @@ while (done <= 0)
 #endif
       smtp_printf(" HELO EHLO MAIL RCPT DATA BDAT", SP_MORE);
       smtp_printf(" NOOP QUIT RSET HELP", SP_MORE);
 #endif
       smtp_printf(" HELO EHLO MAIL RCPT DATA BDAT", SP_MORE);
       smtp_printf(" NOOP QUIT RSET HELP", SP_MORE);
+      if (acl_smtp_atrn) smtp_printf(" ATRN", SP_MORE);
       if (acl_smtp_etrn) smtp_printf(" ETRN", SP_MORE);
       if (acl_smtp_expn) smtp_printf(" EXPN", SP_MORE);
       if (acl_smtp_vrfy) smtp_printf(" VRFY", SP_MORE);
       if (acl_smtp_etrn) smtp_printf(" ETRN", SP_MORE);
       if (acl_smtp_expn) smtp_printf(" EXPN", SP_MORE);
       if (acl_smtp_vrfy) smtp_printf(" VRFY", SP_MORE);
+#ifndef DISABLE_WELLKNOWN
+      if (verify_check_host(&wellknown_advertise_hosts) != FAIL)
+       smtp_printf(" WELLKNOWN", SP_MORE);
+#endif
 #ifdef EXPERIMENTAL_XCLIENT
       if (proxy_session || verify_check_host(&hosts_xclient) != FAIL)
        smtp_printf(" XCLIENT", SP_MORE);
 #ifdef EXPERIMENTAL_XCLIENT
       if (proxy_session || verify_check_host(&hosts_xclient) != FAIL)
        smtp_printf(" XCLIENT", SP_MORE);
@@ -5465,6 +5609,11 @@ while (done <= 0)
       break;
 
 
       break;
 
 
+    case ATRN_CMD:
+      HAD(SCH_ATRN);
+      done = atrn_handle_provider(&user_msg, &log_msg);        /* Normal: exit() */
+      break;                                           /* Error cases */
+
     case ETRN_CMD:
       HAD(SCH_ETRN);
       if (sender_address)
     case ETRN_CMD:
       HAD(SCH_ETRN);
       if (sender_address)
@@ -5477,6 +5626,7 @@ while (done <= 0)
       log_write(L_etrn, LOG_MAIN, "ETRN %s received from %s", smtp_cmd_argument,
        host_and_ident(FALSE));
 
       log_write(L_etrn, LOG_MAIN, "ETRN %s received from %s", smtp_cmd_argument,
        host_and_ident(FALSE));
 
+      GET_OPTION("acl_smtp_etrn");
       if ((rc = acl_check(ACL_WHERE_ETRN, NULL, acl_smtp_etrn,
                  &user_msg, &log_msg)) != OK)
        {
       if ((rc = acl_check(ACL_WHERE_ETRN, NULL, acl_smtp_etrn,
                  &user_msg, &log_msg)) != OK)
        {
@@ -5493,6 +5643,7 @@ while (done <= 0)
       since that is strictly the only kind of ETRN that can be implemented
       according to the RFC. */
 
       since that is strictly the only kind of ETRN that can be implemented
       according to the RFC. */
 
+      GET_OPTION("smtp_etrn_command");
       if (smtp_etrn_command)
        {
        uschar *error;
       if (smtp_etrn_command)
        {
        uschar *error;
@@ -5664,7 +5815,7 @@ while (done <= 0)
 
     case TOO_MANY_NONMAIL_CMD:
       s = smtp_cmd_buffer;
 
     case TOO_MANY_NONMAIL_CMD:
       s = smtp_cmd_buffer;
-      while (*s && !isspace(*s)) s++;
+      Uskip_nonwhite(&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),
       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),