Recast more internal string routines to use growable-strings
[exim.git] / src / src / smtp_in.c
index b99b5cdbc15130a2facaa30951a4c0cca361e0c8..54ebf3660892023eef45bcf9b0f4bbee28ada334 100644 (file)
@@ -145,6 +145,9 @@ static struct {
   BOOL helo_verify                     :1;
   BOOL helo_seen                       :1;
   BOOL helo_accept_junk                        :1;
+#ifdef EXPERIMENTAL_PIPE_CONNECT
+  BOOL pipe_connect_acceptable         :1;
+#endif
   BOOL rcpt_smtp_response_same         :1;
   BOOL rcpt_in_progress                        :1;
   BOOL smtp_exit_function_called       :1;
@@ -404,6 +407,18 @@ return TRUE;
 }
 
 
+#ifdef EXPERIMENTAL_PIPE_CONNECT
+static BOOL
+pipeline_connect_sends(void)
+{
+if (!sender_host_address || f.sender_host_notsocket || !fl.pipe_connect_acceptable)
+  return FALSE;
+
+if (wouldblock_reading()) return FALSE;
+f.smtp_in_early_pipe_used = TRUE;
+return TRUE;
+}
+#endif
 
 /*************************************************
 *          Log incomplete transactions           *
@@ -499,14 +514,14 @@ smtp_refill(unsigned lim)
 int rc, save_errno;
 if (!smtp_out) return FALSE;
 fflush(smtp_out);
-if (smtp_receive_timeout > 0) alarm(smtp_receive_timeout);
+if (smtp_receive_timeout > 0) ALARM(smtp_receive_timeout);
 
 /* Limit amount read, so non-message data is not fed to DKIM.
 Take care to not touch the safety NUL at the end of the buffer. */
 
 rc = read(fileno(smtp_in), smtp_inbuffer, MIN(IN_BUFFER_SIZE-1, lim));
 save_errno = errno;
-if (smtp_receive_timeout > 0) alarm(0);
+if (smtp_receive_timeout > 0) ALARM_CLR(0);
 if (rc <= 0)
   {
   /* Must put the error text in fixed store, because this might be during
@@ -898,18 +913,20 @@ call another vararg function, only a function which accepts a va_list. */
 void
 smtp_vprintf(const char *format, BOOL more, va_list ap)
 {
+gstring gs = { .size = big_buffer_size, .ptr = 0, .s = big_buffer };
 BOOL yield;
 
-yield = string_vformat(big_buffer, big_buffer_size, format, ap);
+yield = !! string_vformat(&gs, FALSE, format, ap);
+string_from_gstring(&gs);
 
 DEBUG(D_receive)
   {
   void *reset_point = store_get(0);
   uschar *msg_copy, *cr, *end;
-  msg_copy = string_copy(big_buffer);
-  end = msg_copy + Ustrlen(msg_copy);
+  msg_copy = string_copy(gs.s);
+  end = msg_copy + gs.ptr;
   while ((cr = Ustrchr(msg_copy, '\r')) != NULL)   /* lose CRs */
-  memmove(cr, cr + 1, (end--) - cr);
+    memmove(cr, cr + 1, (end--) - cr);
   debug_printf("SMTP>> %s", msg_copy);
   store_reset(reset_point);
   }
@@ -942,13 +959,13 @@ if (fl.rcpt_in_progress)
 #ifdef SUPPORT_TLS
 if (tls_in.active.sock >= 0)
   {
-  if (tls_write(NULL, big_buffer, Ustrlen(big_buffer), more) < 0)
+  if (tls_write(NULL, gs.s, gs.ptr, more) < 0)
     smtp_write_error = -1;
   }
 else
 #endif
 
-if (fprintf(smtp_out, "%s", big_buffer) < 0) smtp_write_error = -1;
+if (fprintf(smtp_out, "%s", gs.s) < 0) smtp_write_error = -1;
 }
 
 
@@ -1842,7 +1859,7 @@ for (i = 0; i < smtp_ch_index; i++)
 if (!(s = string_from_gstring(g))) s = US"";
 
 log_write(0, LOG_MAIN, "no MAIL in %sSMTP connection from %s D=%s%s",
-  f.tcp_in_fastopen ? US"TFO " : US"",
+  f.tcp_in_fastopen ? f.tcp_in_fastopen_data ? US"TFO* " : US"TFO " : US"",
   host_and_ident(FALSE), string_timesince(&smtp_connection_start), s);
 }
 
@@ -2392,13 +2409,20 @@ tfo_in_check(void)
 struct tcp_info tinfo;
 socklen_t len = sizeof(tinfo);
 
-if (  getsockopt(fileno(smtp_out), IPPROTO_TCP, TCP_INFO, &tinfo, &len) == 0
-   && tinfo.tcpi_state == TCP_SYN_RECV
-   )
-  {
-  DEBUG(D_receive) debug_printf("TCP_FASTOPEN mode connection (state TCP_SYN_RECV)\n");
-  f.tcp_in_fastopen = TRUE;
-  }
+if (getsockopt(fileno(smtp_out), IPPROTO_TCP, TCP_INFO, &tinfo, &len) == 0)
+#ifdef TCPI_OPT_SYN_DATA       /* FreeBSD 11 does not seem to have this yet */
+  if (tinfo.tcpi_options & TCPI_OPT_SYN_DATA)
+    {
+    DEBUG(D_receive) debug_printf("TCP_FASTOPEN mode connection (ACKd data-on-SYN)\n");
+    f.tcp_in_fastopen_data = f.tcp_in_fastopen = TRUE;
+    }
+  else
+#endif
+    if (tinfo.tcpi_state == TCP_SYN_RECV)
+    {
+    DEBUG(D_receive) debug_printf("TCP_FASTOPEN mode connection (state TCP_SYN_RECV)\n");
+    f.tcp_in_fastopen = TRUE;
+    }
 # endif
 }
 #endif
@@ -2987,22 +3011,39 @@ while (*p);
 /* Before we write the banner, check that there is no input pending, unless
 this synchronisation check is disabled. */
 
+#ifdef EXPERIMENTAL_PIPE_CONNECT
+fl.pipe_connect_acceptable =
+  sender_host_address && verify_check_host(&pipe_connect_advertise_hosts) == OK;
+
 if (!check_sync())
-  {
-  unsigned n = smtp_inend - smtp_inptr;
-  if (n > 32) n = 32;
-
-  log_write(0, LOG_MAIN|LOG_REJECT, "SMTP protocol "
-    "synchronization error (input sent without waiting for greeting): "
-    "rejected connection from %s input=\"%s\"", host_and_ident(TRUE),
-    string_printing(string_copyn(smtp_inptr, n)));
-  smtp_printf("554 SMTP synchronization error\r\n", FALSE);
-  return FALSE;
-  }
+  if (fl.pipe_connect_acceptable)
+    f.smtp_in_early_pipe_used = TRUE;
+  else
+#else
+if (!check_sync())
+#endif
+    {
+    unsigned n = smtp_inend - smtp_inptr;
+    if (n > 32) n = 32;
+
+    log_write(0, LOG_MAIN|LOG_REJECT, "SMTP protocol "
+      "synchronization error (input sent without waiting for greeting): "
+      "rejected connection from %s input=\"%s\"", host_and_ident(TRUE),
+      string_printing(string_copyn(smtp_inptr, n)));
+    smtp_printf("554 SMTP synchronization error\r\n", FALSE);
+    return FALSE;
+    }
 
 /* Now output the banner */
+/*XXX the ehlo-resp code does its own tls/nontls bit.  Maybe subroutine that? */
 
-smtp_printf("%s", FALSE, string_from_gstring(ss));
+smtp_printf("%s",
+#ifdef EXPERIMENTAL_PIPE_CONNECT
+  fl.pipe_connect_acceptable && pipeline_connect_sends(),
+#else
+  FALSE,
+#endif
+  string_from_gstring(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. */
@@ -3465,6 +3506,9 @@ if (acl_smtp_notquit && reason)
       log_msg);
   }
 
+/* If the connection was dropped, we certainly are no longer talking TLS */
+tls_in.active.sock = -1;
+
 /* Write an SMTP response if we are expected to give one. As the default
 responses are all internal, they should always fit in the buffer, but code a
 warning, just in case. Note that string_vformat() still leaves a complete
@@ -3476,13 +3520,13 @@ if (code && defaultrespond)
     smtp_respond(code, 3, TRUE, user_msg);
   else
     {
-    uschar buffer[128];
+    gstring * g;
     va_list ap;
+
     va_start(ap, defaultrespond);
-    if (!string_vformat(buffer, sizeof(buffer), CS defaultrespond, ap))
-      log_write(0, LOG_MAIN|LOG_PANIC, "string too large in smtp_notquit_exit()");
-    smtp_printf("%s %s\r\n", FALSE, code, buffer);
+    g = string_vformat(NULL, TRUE, CS defaultrespond, ap);
     va_end(ap);
+    smtp_printf("%s %s\r\n", FALSE, code, string_from_gstring(g));
     }
   mac_smtp_fflush();
   }
@@ -3950,7 +3994,13 @@ while (done <= 0)
            US &off, sizeof(off));
 #endif
 
-  switch(smtp_read_command(TRUE, GETC_BUFFER_UNLIMITED))
+  switch(smtp_read_command(
+#ifdef EXPERIMENTAL_PIPE_CONNECT
+         !fl.pipe_connect_acceptable,
+#else
+         TRUE,
+#endif
+         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
@@ -4183,7 +4233,12 @@ while (done <= 0)
          host_build_sender_fullhost();  /* Rebuild */
          break;
          }
-       else if (!check_sync()) goto SYNC_FAILURE;
+#ifdef EXPERIMENTAL_PIPE_CONNECT
+       else if (!fl.pipe_connect_acceptable && !check_sync())
+#else
+       else if (!check_sync())
+#endif
+         goto SYNC_FAILURE;
 
       /* Generate an OK reply. The default string includes the ident if present,
       and also the IP address if present. Reflecting back the ident is intended
@@ -4312,13 +4367,22 @@ while (done <= 0)
        /* Exim is quite happy with pipelining, so let the other end know that
        it is safe to use it, unless advertising is disabled. */
 
-       if (f.pipelining_enable &&
-           verify_check_host(&pipelining_advertise_hosts) == OK)
+       if (  f.pipelining_enable
+          && verify_check_host(&pipelining_advertise_hosts) == OK)
          {
          g = string_catn(g, smtp_code, 3);
          g = string_catn(g, US"-PIPELINING\r\n", 13);
          sync_cmd_limit = NON_SYNC_CMD_PIPELINING;
          f.smtp_in_pipelining_advertised = TRUE;
+
+#ifdef EXPERIMENTAL_PIPE_CONNECT
+         if (fl.pipe_connect_acceptable)
+           {
+           f.smtp_in_early_pipe_advertised = TRUE;
+           g = string_catn(g, smtp_code, 3);
+           g = string_catn(g, US"-" EARLY_PIPE_FEATURE_NAME "\r\n", EARLY_PIPE_FEATURE_LEN+3);
+           }
+#endif
          }
 
 
@@ -4439,7 +4503,14 @@ while (done <= 0)
       has been seen. */
 
 #ifdef SUPPORT_TLS
-      if (tls_in.active.sock >= 0) (void)tls_write(NULL, g->s, g->ptr, FALSE); else
+      if (tls_in.active.sock >= 0)
+       (void)tls_write(NULL, g->s, g->ptr,
+# ifdef EXPERIMENTAL_PIPE_CONNECT
+                       fl.pipe_connect_acceptable && pipeline_connect_sends());
+# else
+                       FALSE);
+# endif
+      else
 #endif
 
        {
@@ -5244,7 +5315,10 @@ while (done <= 0)
       HAD(SCH_DATA);
       f.dot_ends = TRUE;
 
-      DATA_BDAT:               /* Common code for DATA and BDAT */
+    DATA_BDAT:         /* Common code for DATA and BDAT */
+#ifdef EXPERIMENTAL_PIPE_CONNECT
+      fl.pipe_connect_acceptable = FALSE;
+#endif
       if (!discarded && recipients_count <= 0)
        {
        if (fl.rcpt_smtp_response_same && rcpt_smtp_response != NULL)
@@ -5508,7 +5582,9 @@ while (done <= 0)
 
       /* Hard failure. Reject everything except QUIT or closed connection. One
       cause for failure is a nested STARTTLS, in which case tls_in.active remains
-      set, but we must still reject all incoming commands. */
+      set, but we must still reject all incoming commands.  Another is a handshake
+      failure - and there may some encrypted data still in the pipe to us, which we
+      see as garbage commands. */
 
       DEBUG(D_tls) debug_printf("TLS failed to start\n");
       while (done <= 0) switch(smtp_read_command(FALSE, GETC_BUFFER_UNLIMITED))
@@ -5614,7 +5690,11 @@ while (done <= 0)
        log_write(L_lost_incoming_connection, LOG_MAIN,
          "unexpected %s while reading SMTP command from %s%s%s D=%s",
          f.sender_host_unknown ? "EOF" : "disconnection",
-         f.tcp_in_fastopen && !f.tcp_in_fastopen_logged ? US"TFO " : US"",
+         f.tcp_in_fastopen_logged
+         ? US""
+         : f.tcp_in_fastopen
+         ? f.tcp_in_fastopen_data ? US"TFO* " : US"TFO "
+         : US"",
          host_and_ident(FALSE), smtp_read_error,
          string_timesince(&smtp_connection_start)
          );