Copyright year bumps for substantive changes 2017
[exim.git] / src / src / smtp_in.c
index 655e25394539d70a0759b4f5b585a42bd3d26141..1252603e5b69592995345335e689e649c11efdbd 100644 (file)
@@ -2,7 +2,7 @@
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
-/* Copyright (c) University of Cambridge 1995 - 2016 */
+/* Copyright (c) University of Cambridge 1995 - 2017 */
 /* See the file NOTICE for conditions of use and distribution. */
 
 /* Functions for handling an incoming SMTP call. */
@@ -44,11 +44,11 @@ The maximum size of a Kerberos ticket under Windows 2003 is 12000 bytes, and
 we need room to handle large base64-encoded AUTHs for GSSAPI.
 */
 
-#define smtp_cmd_buffer_size  16384
+#define SMTP_CMD_BUFFER_SIZE  16384
 
 /* Size of buffer for reading SMTP incoming packets */
 
-#define in_buffer_size  8192
+#define IN_BUFFER_SIZE  8192
 
 /* Structure for SMTP command list */
 
@@ -305,7 +305,7 @@ static int     smtp_had_error;
 
 /* forward declarations */
 int bdat_ungetc(int ch);
-static int smtp_read_command(BOOL check_sync);
+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);
@@ -352,7 +352,7 @@ tzero.tv_usec = 0;
 rc = select(fd + 1, (SELECT_ARG2_TYPE *)&fds, NULL, NULL, &tzero);
 
 if (rc <= 0) return TRUE;     /* Not ready to read */
-rc = smtp_getc();
+rc = smtp_getc(GETC_BUFFER_UNLIMITED);
 if (rc < 0) return TRUE;      /* End of file or error */
 
 smtp_ungetc(rc);
@@ -410,12 +410,12 @@ it flushes the output, and refills the buffer, with a timeout. The signal
 handler is set appropriately by the calling function. This function is not used
 after a connection has negotiated itself into an TLS/SSL state.
 
-Arguments:  none
+Arguments:  lim                Maximum amount to read/buffer
 Returns:    the next character or EOF
 */
 
 int
-smtp_getc(void)
+smtp_getc(unsigned lim)
 {
 if (smtp_inptr >= smtp_inend)
   {
@@ -423,7 +423,10 @@ if (smtp_inptr >= smtp_inend)
   if (!smtp_out) return EOF;
   fflush(smtp_out);
   if (smtp_receive_timeout > 0) alarm(smtp_receive_timeout);
-  rc = read(fileno(smtp_in), smtp_inbuffer, in_buffer_size);
+
+  /* Limit amount read, so non-message data is not fed to DKIM */
+
+  rc = read(fileno(smtp_in), smtp_inbuffer, MIN(IN_BUFFER_SIZE, lim));
   save_errno = errno;
   alarm(0);
   if (rc <= 0)
@@ -471,23 +474,26 @@ to handle the BDAT command/response.
 Placed here due to the correlation with the above smtp_getc(), which it wraps,
 and also by the need to do smtp command/response handling.
 
-Arguments:  none
+Arguments:  lim                (ignored)
 Returns:    the next character or ERR, EOD or EOF
 */
 
 int
-bdat_getc(void)
+bdat_getc(unsigned lim)
 {
 uschar * user_msg = NULL;
 uschar * log_msg;
 
 for(;;)
   {
-  if (chunking_data_left-- > 0)
-    return lwr_receive_getc();
+  if (chunking_data_left > 0)
+    return lwr_receive_getc(chunking_data_left--);
 
   receive_getc = lwr_receive_getc;
   receive_ungetc = lwr_receive_ungetc;
+#ifndef DISABLE_DKIM
+  dkim_collect_input = FALSE;
+#endif
 
   /* Unless PIPELINING was offered, there should be no next command
   until after we ack that chunk */
@@ -516,21 +522,22 @@ for(;;)
     return EOD;
     }
 
-  chunking_state = CHUNKING_OFFERED;
   smtp_printf("250 %u byte chunk received\r\n", chunking_datasize);
+  chunking_state = CHUNKING_OFFERED;
+  DEBUG(D_receive) debug_printf("chunking state %d\n", (int)chunking_state);
 
   /* Expect another BDAT cmd from input. RFC 3030 says nothing about
   QUIT, RSET or NOOP but handling them seems obvious */
 
 next_cmd:
-  switch(smtp_read_command(TRUE))
+  switch(smtp_read_command(TRUE, 1))
     {
     default:
       (void) synprot_error(L_smtp_protocol_error, 503, NULL,
        US"only BDAT permissible after non-LAST BDAT");
 
   repeat_until_rset:
-      switch(smtp_read_command(TRUE))
+      switch(smtp_read_command(TRUE, 1))
        {
        case QUIT_CMD:  smtp_quit_handler(&user_msg, &log_msg); /*FALLTHROUGH */
        case EOF_CMD:   return EOF;
@@ -569,6 +576,8 @@ next_cmd:
       chunking_state = strcmpic(smtp_cmd_data+n, US"LAST") == 0
        ? CHUNKING_LAST : CHUNKING_ACTIVE;
       chunking_data_left = chunking_datasize;
+      DEBUG(D_receive) debug_printf("chunking state %d, %d bytes\n",
+                                   (int)chunking_state, chunking_data_left);
 
       if (chunking_datasize == 0)
        if (chunking_state == CHUNKING_LAST)
@@ -582,24 +591,30 @@ next_cmd:
 
       receive_getc = bdat_getc;
       receive_ungetc = bdat_ungetc;
+#ifndef DISABLE_DKIM
+      dkim_collect_input = TRUE;
+#endif
       break;   /* to top of main loop */
       }
     }
   }
 }
 
-static void
+void
 bdat_flush_data(void)
 {
-while (chunking_data_left-- > 0)
-  if (lwr_receive_getc() < 0)
+while (chunking_data_left > 0)
+  if (lwr_receive_getc(chunking_data_left--) < 0)
     break;
 
 receive_getc = lwr_receive_getc;
 receive_ungetc = lwr_receive_ungetc;
 
 if (chunking_state != CHUNKING_LAST)
+  {
   chunking_state = CHUNKING_OFFERED;
+  DEBUG(D_receive) debug_printf("chunking state %d\n", (int)chunking_state);
+  }
 }
 
 
@@ -882,7 +897,8 @@ if (get_ok == 0)
 *       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.
+or if it is configured that it must use Proxy Protocol.  A local
+connection cannot.
 
 Arguments: none
 Returns:   bool
@@ -892,12 +908,10 @@ static BOOL
 check_proxy_protocol_host()
 {
 int rc;
-/* Cannot configure local connection as a proxy inbound */
-if (sender_host_address == NULL) return proxy_session;
 
-rc = verify_check_this_host(CUSS &hosts_proxy, NULL, NULL,
-                           sender_host_address, NULL);
-if (rc == OK)
+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");
@@ -907,6 +921,52 @@ 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, void *vto, int capacity)
+{
+  uschar *to = (uschar *)vto;
+  int have = 0;
+  int ret;
+  int last = 0;
+
+  while (capacity > 0) {
+    do { ret = recv(fd, to, 1, 0); } while (ret == -1 && errno == EINTR);
+    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          *
 *************************************************/
@@ -919,7 +979,7 @@ Arguments: none
 Returns:   Boolean success
 */
 
-static BOOL
+static void
 setup_proxy_protocol_host()
 {
 union {
@@ -962,11 +1022,13 @@ struct sockaddr_in6 tmpaddr6;
 int get_ok = 0;
 int size, ret;
 int fd = fileno(smtp_in);
+#define PROXY_INITIAL_READ 12
 const char v2sig[12] = "\x0D\x0A\x0D\x0A\x00\x0D\x0A\x51\x55\x49\x54\x0A";
-uschar *iptype;  /* To display debug info */
+uschar * iptype;  /* To display debug info */
 struct timeval tv;
 struct timeval tvtmp;
 socklen_t vslen = sizeof(struct timeval);
+BOOL yield = FALSE;
 
 /* Save current socket timeout values */
 get_ok = getsockopt(fd, SOL_SOCKET, SO_RCVTIMEO, CS &tvtmp, &vslen);
@@ -976,13 +1038,16 @@ get_ok = getsockopt(fd, SOL_SOCKET, SO_RCVTIMEO, CS &tvtmp, &vslen);
 tv.tv_sec  = PROXY_NEGOTIATION_TIMEOUT_SEC;
 tv.tv_usec = PROXY_NEGOTIATION_TIMEOUT_USEC;
 if (setsockopt(fd, SOL_SOCKET, SO_RCVTIMEO, CS &tv, sizeof(tv)) < 0)
-  return FALSE;
+  goto bad;
 
 do
   {
   /* The inbound host was declared to be a Proxy Protocol host, so
      don't do a PEEK into the data, actually slurp it up. */
-  ret = recv(fd, &hdr, sizeof(hdr), 0);
+  /* We assume that the zie of a v2sig is less than the size of a complete
+     PROXYv1 line, so that we will _have_ to read more data; "PROXY TCPn\r\n"
+     is 12, so an IP address pushes it over, so we're good. */
+  ret = recv(fd, &hdr, PROXY_INITIAL_READ, 0);
   }
   while (ret == -1 && errno == EINTR);
 
@@ -991,8 +1056,19 @@ if (ret == -1)
 
 if (ret >= 16 && memcmp(&hdr.v2, v2sig, 12) == 0)
   {
-  uint8_t ver = (hdr.v2.ver_cmd & 0xf0) >> 4;
-  uint8_t cmd = (hdr.v2.ver_cmd & 0x0f);
+  uint8_t ver, cmd;
+  int ret2nd;
+
+  /* It's now safe to read the rest. */
+  do {
+    ret2nd = recv(fd, (uschar*)&hdr + ret, sizeof(hdr)-ret, 0);
+  } while (ret2nd == -1 && errno == EINTR);
+  if (ret2nd == -1)
+    goto proxyfail;
+  ret += ret2nd;
+
+  ver = (hdr.v2.ver_cmd & 0xf0) >> 4;
+  cmd = (hdr.v2.ver_cmd & 0x0f);
 
   /* 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
@@ -1080,6 +1156,7 @@ if (ret >= 16 && memcmp(&hdr.v2, v2sig, 12) == 0)
       break;
     case 0x00: /* LOCAL command */
       /* Keep local connection address for LOCAL */
+      iptype = US"local";
       break;
     default:
       DEBUG(D_receive)
@@ -1089,20 +1166,31 @@ if (ret >= 16 && memcmp(&hdr.v2, v2sig, 12) == 0)
   }
 else if (ret >= 8 && memcmp(hdr.v1.line, "PROXY", 5) == 0)
   {
-  uschar *p = string_copy(hdr.v1.line);
-  uschar *end = memchr(p, '\r', ret - 1);
+  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[1] != '\n')
     {
     DEBUG(D_receive) debug_printf("Partial or invalid PROXY header\n");
     goto proxyfail;
     }
   *end = '\0'; /* Terminate the string */
-  size = end + 2 - hdr.v1.line; /* Skip header + CRLF */
+  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;
@@ -1194,26 +1282,41 @@ else if (ret >= 8 && memcmp(hdr.v1.line, "PROXY", 5) == 0)
     }
   proxy_external_port = tmp_port;
   /* Already checked for /r /n above. Good V1 header received. */
-  goto done;
   }
 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:
-restore_socket_timeout(fd, get_ok, &tvtmp, vslen);
-/* Don't flush any potential buffer contents. Any input should cause a
-   synchronization failure */
-return FALSE;
+  restore_socket_timeout(fd, get_ok, &tvtmp, vslen);
 
-done:
-restore_socket_timeout(fd, get_ok, &tvtmp, vslen);
-DEBUG(D_receive)
-  debug_printf("Valid %s sender from Proxy Protocol header\n", iptype);
-return proxy_session;
+bad:
+  if (yield)
+    {
+    sender_host_name = NULL;
+    (void) host_name_lookup();
+    host_build_sender_fullhost();
+    }
+  else
+    {
+    proxy_session_failed = TRUE;
+    DEBUG(D_receive)
+      debug_printf("Failure to extract proxied host, only QUIT allowed\n");
+    }
+
+return;
 }
 #endif
 
@@ -1234,13 +1337,14 @@ signal handler that closes down the session on a timeout. Control does not
 return when it runs.
 
 Arguments:
-  check_sync   if TRUE, check synchronization rules if global option is TRUE
+  check_sync   if TRUE, check synchronization rules if global option is TRUE
+  buffer_lim   maximum to buffer in lower layer
 
 Returns:       a code identifying the command (enumerated above)
 */
 
 static int
-smtp_read_command(BOOL check_sync)
+smtp_read_command(BOOL check_sync, unsigned buffer_lim)
 {
 int c;
 int ptr = 0;
@@ -1249,9 +1353,9 @@ BOOL hadnull = FALSE;
 
 os_non_restarting_signal(SIGALRM, command_timeout_handler);
 
-while ((c = (receive_getc)()) != '\n' && c != EOF)
+while ((c = (receive_getc)(buffer_lim)) != '\n' && c != EOF)
   {
-  if (ptr >= smtp_cmd_buffer_size)
+  if (ptr >= SMTP_CMD_BUFFER_SIZE)
     {
     os_non_restarting_signal(SIGALRM, sigalrm_handler);
     return OTHER_CMD;
@@ -1290,14 +1394,11 @@ if required. */
 
 for (p = cmd_list; p < cmd_list_end; p++)
   {
-  #ifdef SUPPORT_PROXY
+#ifdef SUPPORT_PROXY
   /* Only allow QUIT command if Proxy Protocol parsing failed */
-  if (proxy_session && proxy_session_failed)
-    {
-    if (p->cmd != QUIT_CMD)
-      continue;
-    }
-  #endif
+  if (proxy_session && proxy_session_failed && p->cmd != QUIT_CMD)
+    continue;
+#endif
   if (  p->len
      && strncmpic(smtp_cmd_buffer, US p->name, p->len) == 0
      && (  smtp_cmd_buffer[p->len-1] == ':'    /* "mail from:" or "rcpt to:" */
@@ -1391,7 +1492,7 @@ if (smtp_in == NULL || smtp_batched_input) return;
 receive_swallow_smtp();
 smtp_printf("421 %s\r\n", message);
 
-for (;;) switch(smtp_read_command(FALSE))
+for (;;) switch(smtp_read_command(FALSE, GETC_BUFFER_UNLIMITED))
   {
   case EOF_CMD:
   return;
@@ -1658,7 +1759,13 @@ uschar *n;
 uschar *v = smtp_cmd_data + Ustrlen(smtp_cmd_data) - 1;
 while (isspace(*v)) v--;
 v[1] = 0;
-while (v > smtp_cmd_data && *v != '=' && !isspace(*v)) v--;
+while (v > smtp_cmd_data && *v != '=' && !isspace(*v))
+  {
+  /* Take care to not stop at a space embedded in a quoted local-part */
+
+  if (*v == '"') do v--; while (*v != '"' && v > smtp_cmd_data+1);
+  v--;
+  }
 
 n = v;
 if (*v == '=')
@@ -1835,7 +1942,7 @@ while (done <= 0)
   uschar *recipient = NULL;
   int start, end, sender_domain, recipient_domain;
 
-  switch(smtp_read_command(FALSE))
+  switch(smtp_read_command(FALSE, GETC_BUFFER_UNLIMITED))
     {
     /* The HELO/EHLO commands set sender_address_helo if they have
     valid data; otherwise they are ignored, except that they do
@@ -2094,12 +2201,12 @@ acl_var_c = NULL;
 
 /* Allow for trailing 0 in the command and data buffers. */
 
-if (!(smtp_cmd_buffer = US malloc(2*smtp_cmd_buffer_size + 2)))
+if (!(smtp_cmd_buffer = US malloc(2*SMTP_CMD_BUFFER_SIZE + 2)))
   log_write(0, LOG_MAIN|LOG_PANIC_DIE,
     "malloc() failed for SMTP command buffer");
 
 smtp_cmd_buffer[0] = 0;
-smtp_data_buffer = smtp_cmd_buffer + smtp_cmd_buffer_size + 1;
+smtp_data_buffer = smtp_cmd_buffer + SMTP_CMD_BUFFER_SIZE + 1;
 
 /* For batched input, the protocol setting can be overridden from the
 command line by a trusted caller. */
@@ -2119,7 +2226,7 @@ else
 /* Set up the buffer for inputting using direct read() calls, and arrange to
 call the local functions instead of the standard C ones. */
 
-if (!(smtp_inbuffer = (uschar *)malloc(in_buffer_size)))
+if (!(smtp_inbuffer = (uschar *)malloc(IN_BUFFER_SIZE)))
   log_write(0, LOG_MAIN|LOG_PANIC_DIE, "malloc() failed for SMTP input buffer");
 
 receive_getc = smtp_getc;
@@ -2382,14 +2489,6 @@ if (!sender_host_unknown)
        "bad value for smtp_receive_timeout: '%s'", exp ? exp : US"");
     }
 
-  /* Start up TLS if tls_on_connect is set. This is for supporting the legacy
-  smtps port for use with older style SSL MTAs. */
-
-  #ifdef SUPPORT_TLS
-  if (tls_in.on_connect && tls_server_start(tls_require_ciphers) != OK)
-    return FALSE;
-  #endif
-
   /* Test for explicit connection rejection */
 
   if (verify_check_host(&host_reject_connection) == OK)
@@ -2409,19 +2508,17 @@ if (!sender_host_unknown)
   value of errno is 0 or ENOENT (which happens if /etc/hosts.{allow,deny} does
   not exist). */
 
-  #ifdef USE_TCP_WRAPPERS
+#ifdef USE_TCP_WRAPPERS
   errno = 0;
-  tcp_wrappers_name = expand_string(tcp_wrappers_daemon_name);
-  if (tcp_wrappers_name == NULL)
-    {
+  if (!(tcp_wrappers_name = expand_string(tcp_wrappers_daemon_name)))
     log_write(0, LOG_MAIN|LOG_PANIC_DIE, "Expansion of \"%s\" "
       "(tcp_wrappers_name) failed: %s", string_printing(tcp_wrappers_name),
         expand_string_message);
-    }
+
   if (!hosts_ctl(tcp_wrappers_name,
-         (sender_host_name == NULL)? STRING_UNKNOWN : CS sender_host_name,
-         (sender_host_address == NULL)? STRING_UNKNOWN : CS sender_host_address,
-         (sender_ident == NULL)? STRING_UNKNOWN : CS sender_ident))
+         sender_host_name ? CS sender_host_name : STRING_UNKNOWN,
+         sender_host_address ? CS sender_host_address : STRING_UNKNOWN,
+         sender_ident ? CS sender_ident : STRING_UNKNOWN))
     {
     if (errno == 0 || errno == ENOENT)
       {
@@ -2443,7 +2540,7 @@ if (!sender_host_unknown)
       }
     return FALSE;
     }
-  #endif
+#endif
 
   /* Check for reserved slots. The value of smtp_accept_count has already been
   incremented to include this process. */
@@ -2514,27 +2611,25 @@ if (!sender_host_unknown)
 
 if (smtp_batched_input) return TRUE;
 
-#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;
 proxy_session_failed = FALSE;
 if (check_proxy_protocol_host())
-  if (!setup_proxy_protocol_host())
-    {
-    proxy_session_failed = TRUE;
-    DEBUG(D_receive)
-      debug_printf("Failure to extract proxied host, only QUIT allowed\n");
-    }
-  else
-    {
-    sender_host_name = NULL;
-    (void)host_name_lookup();
-    host_build_sender_fullhost();
-    }
+  setup_proxy_protocol_host();
 #endif
 
-/* Run the ACL if it exists */
+  /* Start up TLS if tls_on_connect is set. This is for supporting the legacy
+  smtps port for use with older style SSL MTAs. */
+
+#ifdef SUPPORT_TLS
+  if (tls_in.on_connect && tls_server_start(tls_require_ciphers) != OK)
+    return FALSE;
+#endif
+
+/* Run the connect ACL if it exists */
 
 user_msg = NULL;
 if (acl_smtp_connect)
@@ -2917,7 +3012,7 @@ we have not sent a response about it yet, do so now, as a preliminary line for
 failures, but not defers. However, always log it for defer, and log it for fail
 unless the sender_verify_fail log selector has been turned off. */
 
-if (sender_verified_failed != NULL &&
+if (sender_verified_failed &&
     !testflag(sender_verified_failed, af_sverify_told))
   {
   BOOL save_rcpt_in_progress = rcpt_in_progress;
@@ -2933,7 +3028,7 @@ if (sender_verified_failed != NULL &&
       (sender_verified_failed->message == NULL)? US"" :
       string_sprintf(": %s", sender_verified_failed->message));
 
-  if (rc == FAIL && sender_verified_failed->user_message != NULL)
+  if (rc == FAIL && sender_verified_failed->user_message)
     smtp_respond(smtp_code, codelen, FALSE, string_sprintf(
         testflag(sender_verified_failed, af_verify_pmfail)?
           "Postmaster verification failed while checking <%s>\n%s\n"
@@ -3567,7 +3662,7 @@ while (done <= 0)
            US &off, sizeof(off));
 #endif
 
-  switch(smtp_read_command(TRUE))
+  switch(smtp_read_command(TRUE, GETC_BUFFER_UNLIMITED))
     {
     /* The AUTH command is not permitted to occur inside a transaction, and may
     occur successfully only once per connection. Actually, that isn't quite
@@ -3903,19 +3998,20 @@ while (done <= 0)
         dsn_advertised = TRUE;
         }
 
-      /* Advertise ETRN if there's an ACL checking whether a host is
-      permitted to issue it; a check is made when any host actually tries. */
+      /* 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. */
 
-      if (acl_smtp_etrn != NULL)
+      if (acl_smtp_etrn)
         {
         s = string_catn(s, &size, &ptr, smtp_code, 3);
         s = string_catn(s, &size, &ptr, US"-ETRN\r\n", 7);
         }
-
-      /* Advertise EXPN if there's an ACL checking whether a host is
-      permitted to issue it; a check is made when any host actually tries. */
-
-      if (acl_smtp_expn != NULL)
+      if (acl_smtp_vrfy)
+        {
+        s = string_catn(s, &size, &ptr, smtp_code, 3);
+        s = string_catn(s, &size, &ptr, US"-VRFY\r\n", 7);
+        }
+      if (acl_smtp_expn)
         {
         s = string_catn(s, &size, &ptr, smtp_code, 3);
         s = string_catn(s, &size, &ptr, US"-EXPN\r\n", 7);
@@ -4617,7 +4713,7 @@ while (done <= 0)
     friends now makes it absolutely clear that it means *mailbox*. Consequently
     we must always qualify this address, regardless. */
 
-    if (recipient_domain == 0)
+    if (!recipient_domain)
       if (!(recipient_domain = qualify_recipient(&recipient, smtp_cmd_data,
                                  US"recipient")))
         {
@@ -4767,14 +4863,14 @@ while (done <= 0)
       chunking_state = strcmpic(smtp_cmd_data+n, US"LAST") == 0
        ? CHUNKING_LAST : CHUNKING_ACTIVE;
       chunking_data_left = chunking_datasize;
+      DEBUG(D_receive) debug_printf("chunking state %d, %d bytes\n",
+                                   (int)chunking_state, chunking_data_left);
 
       lwr_receive_getc = receive_getc;
       lwr_receive_ungetc = receive_ungetc;
       receive_getc = bdat_getc;
       receive_ungetc = bdat_ungetc;
 
-      DEBUG(D_any)
-        debug_printf("chunking state %d\n", (int)chunking_state);
       goto DATA_BDAT;
       }
 
@@ -4875,7 +4971,7 @@ while (done <= 0)
        break;
        }
 
-      if (recipient_domain == 0)
+      if (!recipient_domain)
        if (!(recipient_domain = qualify_recipient(&address, smtp_cmd_data,
                                    US"verify")))
          break;
@@ -4990,7 +5086,7 @@ while (done <= 0)
     It seems safest to just wipe away the content rather than leave it as a
     target to jump to. */
 
-    memset(smtp_inbuffer, 0, in_buffer_size);
+    memset(smtp_inbuffer, 0, IN_BUFFER_SIZE);
 
     /* Attempt to start up a TLS session, and if successful, discard all
     knowledge that was obtained previously. At least, that's what the RFC says,
@@ -5044,7 +5140,7 @@ while (done <= 0)
     set, but we must still reject all incoming commands. */
 
     DEBUG(D_tls) debug_printf("TLS failed to start\n");
-    while (done <= 0) switch(smtp_read_command(FALSE))
+    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",
@@ -5332,8 +5428,8 @@ while (done <= 0)
 
     case BADSYN_CMD:
     SYNC_FAILURE:
-    if (smtp_inend >= smtp_inbuffer + in_buffer_size)
-      smtp_inend = smtp_inbuffer + in_buffer_size - 1;
+    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;
     smtp_inptr[c] = 0;