tidying
[exim.git] / src / src / smtp_out.c
index 9547c4b81667d7a0221d5ac1c48d10a4d5028acc..888a0006e7ae4b227ee2e5148ebfdeadb74c8ae3 100644 (file)
@@ -2,9 +2,10 @@
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
+/* Copyright (c) The Exim Maintainers 2020 - 2023 */
 /* Copyright (c) University of Cambridge 1995 - 2018 */
 /* Copyright (c) University of Cambridge 1995 - 2018 */
-/* Copyright (c) The Exim Maintainers 2020 - 2021 */
 /* See the file NOTICE for conditions of use and distribution. */
 /* See the file NOTICE for conditions of use and distribution. */
+/* SPDX-License-Identifier: GPL-2.0-or-later */
 
 /* A number of functions for driving outgoing SMTP calls. */
 
 
 /* A number of functions for driving outgoing SMTP calls. */
 
@@ -53,8 +54,11 @@ if (!(expint = expand_string(istring)))
   return FALSE;
   }
 
   return FALSE;
   }
 
-if (is_tainted2(expint, LOG_MAIN|LOG_PANIC, "Tainted value '%s' from '%s' for interface", expint, istring))
+if (is_tainted(expint))
   {
   {
+  log_write(0, LOG_MAIN|LOG_PANIC,
+    "attempt to use tainted value '%s' from '%s' for interface",
+    expint, istring);
   addr->transport_return = PANIC;
   addr->message = string_sprintf("failed to expand \"interface\" "
       "option for %s: configuration error", msg);
   addr->transport_return = PANIC;
   addr->message = string_sprintf("failed to expand \"interface\" "
       "option for %s: configuration error", msg);
@@ -249,44 +253,21 @@ switch (tcp_out_fastopen)
 #endif
 
 
 #endif
 
 
-/* Arguments:
-  host        host item containing name and address and port
-  host_af     AF_INET or AF_INET6
-  port       TCP port number
-  interface   outgoing interface address or NULL
-  tb          transport
-  timeout     timeout value or 0
-  early_data   if non-NULL, idempotent data to be sent -
-               preferably in the TCP SYN segment
-             Special case: non-NULL but with NULL blob.data - caller is
-             client-data-first (eg. TLS-on-connect) and a lazy-TCP-connect is
-             acceptable.
-
-Returns:      connected socket number, or -1 with errno set
+/* Create and bind a socket, given the connect-args.
+Update those with the state.  Return the fd, or -1 with errno set.
 */
 
 int
 */
 
 int
-smtp_sock_connect(host_item * host, int host_af, int port, uschar * interface,
-  transport_instance * tb, int timeout, const blob * early_data)
+smtp_boundsock(smtp_connect_args * sc)
 {
 {
+transport_instance * tb = sc->tblock;
 smtp_transport_options_block * ob =
   (smtp_transport_options_block *)tb->options_block;
 const uschar * dscp = ob->dscp;
 smtp_transport_options_block * ob =
   (smtp_transport_options_block *)tb->options_block;
 const uschar * dscp = ob->dscp;
-int dscp_value;
-int dscp_level;
-int dscp_option;
-int sock;
-int save_errno = 0;
-const blob * fastopen_blob = NULL;
-
-
-#ifndef DISABLE_EVENT
-deliver_host_address = host->address;
-deliver_host_port = port;
-if (event_raise(tb->event_action, US"tcp:connect", NULL)) return -1;
-#endif
+int sock, dscp_value, dscp_level, dscp_option;
 
 
-if ((sock = ip_socket(SOCK_STREAM, host_af)) < 0) return -1;
+if ((sock = ip_socket(SOCK_STREAM, sc->host_af)) < 0)
+  return -1;
 
 /* Set TCP_NODELAY; Exim does its own buffering. */
 
 
 /* Set TCP_NODELAY; Exim does its own buffering. */
 
@@ -297,7 +278,8 @@ if (setsockopt(sock, IPPROTO_TCP, TCP_NODELAY, US &on, sizeof(on)))
 /* Set DSCP value, if we can. For now, if we fail to set the value, we don't
 bomb out, just log it and continue in default traffic class. */
 
 /* Set DSCP value, if we can. For now, if we fail to set the value, we don't
 bomb out, just log it and continue in default traffic class. */
 
-if (dscp && dscp_lookup(dscp, host_af, &dscp_level, &dscp_option, &dscp_value))
+GET_OPTION("dscp");
+if (dscp && dscp_lookup(dscp, sc->host_af, &dscp_level, &dscp_option, &dscp_value))
   {
   HDEBUG(D_transport|D_acl|D_v)
     debug_printf_indent("DSCP \"%s\"=%x ", dscp, dscp_value);
   {
   HDEBUG(D_transport|D_acl|D_v)
     debug_printf_indent("DSCP \"%s\"=%x ", dscp, dscp_value);
@@ -306,7 +288,7 @@ if (dscp && dscp_lookup(dscp, host_af, &dscp_level, &dscp_option, &dscp_value))
       debug_printf_indent("failed to set DSCP: %s ", strerror(errno));
   /* If the kernel supports IPv4 and IPv6 on an IPv6 socket, we need to set the
   option for both; ignore failures here */
       debug_printf_indent("failed to set DSCP: %s ", strerror(errno));
   /* If the kernel supports IPv4 and IPv6 on an IPv6 socket, we need to set the
   option for both; ignore failures here */
-  if (host_af == AF_INET6 &&
+  if (sc->host_af == AF_INET6 &&
       dscp_lookup(dscp, AF_INET, &dscp_level, &dscp_option, &dscp_value))
     (void) setsockopt(sock, dscp_level, dscp_option, &dscp_value, sizeof(dscp_value));
   }
       dscp_lookup(dscp, AF_INET, &dscp_level, &dscp_option, &dscp_value))
     (void) setsockopt(sock, dscp_level, dscp_option, &dscp_value, sizeof(dscp_value));
   }
@@ -314,24 +296,76 @@ if (dscp && dscp_lookup(dscp, host_af, &dscp_level, &dscp_option, &dscp_value))
 /* Bind to a specific interface if requested. Caller must ensure the interface
 is the same type (IPv4 or IPv6) as the outgoing address. */
 
 /* Bind to a specific interface if requested. Caller must ensure the interface
 is the same type (IPv4 or IPv6) as the outgoing address. */
 
-if (interface && ip_bind(sock, host_af, interface, 0) < 0)
+if (sc->interface)
   {
   {
-  save_errno = errno;
-  HDEBUG(D_transport|D_acl|D_v)
-    debug_printf_indent("unable to bind outgoing SMTP call to %s: %s", interface,
-    strerror(errno));
+  union sockaddr_46 interface_sock;
+  EXIM_SOCKLEN_T size = sizeof(interface_sock);
+
+  if (  ip_bind(sock, sc->host_af, sc->interface, 0) < 0
+     || getsockname(sock, (struct sockaddr *) &interface_sock, &size) < 0
+     )
+    {
+    HDEBUG(D_transport|D_acl|D_v)
+      debug_printf_indent("unable to bind outgoing SMTP call to %s: %s\n", sc->interface,
+       strerror(errno));
+    close(sock);
+    return -1;
+    }
+  sending_ip_address = host_ntoa(-1, &interface_sock, NULL, &sending_port);
   }
 
   }
 
+sc->sock = sock;
+return sock;
+}
+
+
+/* Arguments:
+  host        host item containing name and address and port
+  host_af     AF_INET or AF_INET6
+  port       TCP port number
+  interface   outgoing interface address or NULL
+  tb          transport
+  timeout     timeout value or 0
+  early_data   if non-NULL, idempotent data to be sent -
+               preferably in the TCP SYN segment
+             Special case: non-NULL but with NULL blob.data - caller is
+             client-data-first (eg. TLS-on-connect) and a lazy-TCP-connect is
+             acceptable.
+
+Returns:      connected socket number, or -1 with errno set
+*/
+
+int
+smtp_sock_connect(smtp_connect_args * sc, int timeout, const blob * early_data)
+{
+smtp_transport_options_block * ob =
+  (smtp_transport_options_block *)sc->tblock->options_block;
+int sock;
+int save_errno = 0;
+const blob * fastopen_blob = NULL;
+
+
+#ifndef DISABLE_EVENT
+deliver_host_address = sc->host->address;
+deliver_host_port = sc->host->port;
+if (event_raise(sc->tblock->event_action, US"tcp:connect", NULL, &errno)) return -1;
+#endif
+
+if (  (sock = sc->sock) < 0
+   && (sock = smtp_boundsock(sc)) < 0)
+  save_errno = errno;
+sc->sock = -1;
+
 /* Connect to the remote host, and add keepalive to the socket before returning
 it, if requested.  If the build supports TFO, request it - and if the caller
 requested some early-data then include that in the TFO request.  If there is
 early-data but no TFO support, send it after connecting. */
 
 /* Connect to the remote host, and add keepalive to the socket before returning
 it, if requested.  If the build supports TFO, request it - and if the caller
 requested some early-data then include that in the TFO request.  If there is
 early-data but no TFO support, send it after connecting. */
 
-else
+if (!save_errno)
   {
 #ifdef TCP_FASTOPEN
   /* See if TCP Fast Open usable.  Default is a traditional 3WHS connect */
   {
 #ifdef TCP_FASTOPEN
   /* See if TCP Fast Open usable.  Default is a traditional 3WHS connect */
-  if (verify_check_given_host(CUSS &ob->hosts_try_fastopen, host) == OK)
+  if (verify_check_given_host(CUSS &ob->hosts_try_fastopen, sc->host) == OK)
     {
     if (!early_data)
       fastopen_blob = &tcp_fastopen_nodata;    /* TFO, with no data */
     {
     if (!early_data)
       fastopen_blob = &tcp_fastopen_nodata;    /* TFO, with no data */
@@ -340,7 +374,7 @@ else
 # ifdef TCP_FASTOPEN_CONNECT
     else
       {                                                /* expecting client data */
 # ifdef TCP_FASTOPEN_CONNECT
     else
       {                                                /* expecting client data */
-      debug_printf(" set up lazy-connect\n");
+      DEBUG(D_transport|D_acl|D_v) debug_printf(" set up lazy-connect\n");
       setsockopt(sock, IPPROTO_TCP, TCP_FASTOPEN_CONNECT, US &on, sizeof(on));
       /* fastopen_blob = NULL;          lazy TFO, triggered by data write */
       }
       setsockopt(sock, IPPROTO_TCP, TCP_FASTOPEN_CONNECT, US &on, sizeof(on));
       /* fastopen_blob = NULL;          lazy TFO, triggered by data write */
       }
@@ -348,7 +382,7 @@ else
     }
 #endif
 
     }
 #endif
 
-  if (ip_connect(sock, host_af, host->address, port, timeout, fastopen_blob) < 0)
+  if (ip_connect(sock, sc->host_af, sc->host->address, sc->host->port, timeout, fastopen_blob) < 0)
     save_errno = errno;
   else if (early_data && !fastopen_blob && early_data->data && early_data->len)
     {
     save_errno = errno;
   else if (early_data && !fastopen_blob && early_data->data && early_data->len)
     {
@@ -371,29 +405,13 @@ else
 #endif
   }
 
 #endif
   }
 
-/* Either bind() or connect() failed */
-
-if (save_errno != 0)
-  {
-  HDEBUG(D_transport|D_acl|D_v)
-    {
-    debug_printf_indent(" failed: %s", CUstrerror(save_errno));
-    if (save_errno == ETIMEDOUT)
-      debug_printf(" (timeout=%s)", readconf_printtime(timeout));
-    debug_printf("\n");
-    }
-  (void)close(sock);
-  errno = save_errno;
-  return -1;
-  }
-
-/* Both bind() and connect() succeeded, and any early-data */
-
-else
+if (!save_errno)
   {
   union sockaddr_46 interface_sock;
   EXIM_SOCKLEN_T size = sizeof(interface_sock);
 
   {
   union sockaddr_46 interface_sock;
   EXIM_SOCKLEN_T size = sizeof(interface_sock);
 
+  /* Both bind() and connect() succeeded, and any early-data */
+
   HDEBUG(D_transport|D_acl|D_v) debug_printf_indent(" connected\n");
   if (getsockname(sock, (struct sockaddr *)(&interface_sock), &size) == 0)
     sending_ip_address = host_ntoa(-1, &interface_sock, NULL, &sending_port);
   HDEBUG(D_transport|D_acl|D_v) debug_printf_indent(" connected\n");
   if (getsockname(sock, (struct sockaddr *)(&interface_sock), &size) == 0)
     sending_ip_address = host_ntoa(-1, &interface_sock, NULL, &sending_port);
@@ -405,12 +423,25 @@ else
     return -1;
     }
 
     return -1;
     }
 
-  if (ob->keepalive) ip_keepalive(sock, host->address, TRUE);
+  if (ob->keepalive) ip_keepalive(sock, sc->host->address, TRUE);
 #ifdef TCP_FASTOPEN
   tfo_out_check(sock);
 #endif
   return sock;
   }
 #ifdef TCP_FASTOPEN
   tfo_out_check(sock);
 #endif
   return sock;
   }
+
+/* Either bind() or connect() failed */
+
+HDEBUG(D_transport|D_acl|D_v)
+  {
+  debug_printf_indent(" failed: %s", CUstrerror(save_errno));
+  if (save_errno == ETIMEDOUT)
+    debug_printf(" (timeout=%s)", readconf_printtime(timeout));
+  debug_printf("\n");
+  }
+(void)close(sock);
+errno = save_errno;
+return -1;
 }
 
 
 }
 
 
@@ -475,7 +506,7 @@ if (ob->socks_proxy)
   {
   int sock = socks_sock_connect(sc->host, sc->host_af, port, sc->interface,
                                sc->tblock, ob->connect_timeout);
   {
   int sock = socks_sock_connect(sc->host, sc->host_af, port, sc->interface,
                                sc->tblock, ob->connect_timeout);
-
+  
   if (sock >= 0)
     {
     if (early_data && early_data->data && early_data->len)
   if (sock >= 0)
     {
     if (early_data && early_data->data && early_data->len)
@@ -498,8 +529,7 @@ if (ob->socks_proxy)
   }
 #endif
 
   }
 #endif
 
-return smtp_sock_connect(sc->host, sc->host_af, port, sc->interface,
-                         sc->tblock, ob->connect_timeout, early_data);
+return smtp_sock_connect(sc, ob->connect_timeout, early_data);
 }
 
 
 }
 
 
@@ -524,13 +554,21 @@ flush_buffer(smtp_outblock * outblock, int mode)
 int rc;
 int n = outblock->ptr - outblock->buffer;
 BOOL more = mode == SCMD_MORE;
 int rc;
 int n = outblock->ptr - outblock->buffer;
 BOOL more = mode == SCMD_MORE;
+client_conn_ctx * cctx;
 
 HDEBUG(D_transport|D_acl) debug_printf_indent("cmd buf flush %d bytes%s\n", n,
   more ? " (more expected)" : "");
 
 
 HDEBUG(D_transport|D_acl) debug_printf_indent("cmd buf flush %d bytes%s\n", n,
   more ? " (more expected)" : "");
 
+if (!(cctx = outblock->cctx))
+  {
+  log_write(0, LOG_MAIN|LOG_PANIC, "null conn-context pointer");
+  errno = 0;
+  return FALSE;
+  }
+
 #ifndef DISABLE_TLS
 #ifndef DISABLE_TLS
-if (outblock->cctx->tls_ctx)
-  rc = tls_write(outblock->cctx->tls_ctx, outblock->buffer, n, more);
+if (cctx->tls_ctx)             /*XXX have seen a null cctx here, rvfy sending QUIT, hence check above */
+  rc = tls_write(cctx->tls_ctx, outblock->buffer, n, more);
 else
 #endif
 
 else
 #endif
 
@@ -544,7 +582,7 @@ else
     requirement: TFO with data can, in rare cases, replay the data to the
     receiver. */
 
     requirement: TFO with data can, in rare cases, replay the data to the
     receiver. */
 
-    if (  (outblock->cctx->sock = smtp_connect(outblock->conn_args, &early_data))
+    if (  (cctx->sock = smtp_connect(outblock->conn_args, &early_data))
        < 0)
       return FALSE;
     outblock->conn_args = NULL;
        < 0)
       return FALSE;
     outblock->conn_args = NULL;
@@ -552,7 +590,7 @@ else
     }
   else
     {
     }
   else
     {
-    rc = send(outblock->cctx->sock, outblock->buffer, n,
+    rc = send(cctx->sock, outblock->buffer, n,
 #ifdef MSG_MORE
              more ? MSG_MORE : 0
 #else
 #ifdef MSG_MORE
              more ? MSG_MORE : 0
 #else
@@ -567,7 +605,7 @@ else
     https://bugzilla.redhat.com/show_bug.cgi?id=1803806 */
 
     if (!more)
     https://bugzilla.redhat.com/show_bug.cgi?id=1803806 */
 
     if (!more)
-      setsockopt(outblock->cctx->sock, IPPROTO_TCP, TCP_CORK, &off, sizeof(off));
+      setsockopt(cctx->sock, IPPROTO_TCP, TCP_CORK, &off, sizeof(off));
 #endif
     }
   }
 #endif
     }
   }
@@ -585,6 +623,22 @@ return TRUE;
 
 
 
 
 
 
+/* This might be called both due to callout and then from delivery.
+Use memory that will not be released between those phases.
+*/
+static void
+smtp_debug_resp(const uschar * buf)
+{
+#ifndef DISABLE_CLIENT_CMD_LOG
+int old_pool = store_pool;
+store_pool = POOL_PERM;
+client_cmd_log = string_append_listele_n(client_cmd_log, ':', buf,
+  buf[3] == ' ' ? 3 : 4);
+store_pool = old_pool;
+#endif
+}
+
+
 /*************************************************
 *             Write SMTP command                 *
 *************************************************/
 /*************************************************
 *             Write SMTP command                 *
 *************************************************/
@@ -596,7 +650,7 @@ Arguments:
   sx        SMTP connection, contains buffer for pipelining, and socket
   mode       buffer, write-with-more-likely, write
   format     a format, starting with one of
   sx        SMTP connection, contains buffer for pipelining, and socket
   mode       buffer, write-with-more-likely, write
   format     a format, starting with one of
-             of HELO, MAIL FROM, RCPT TO, DATA, ".", or QUIT.
+             of HELO, MAIL FROM, RCPT TO, DATA, BDAT, ".", or QUIT.
             If NULL, flush pipeline buffer only.
   ...        data for the format
 
             If NULL, flush pipeline buffer only.
   ...        data for the format
 
@@ -606,7 +660,7 @@ Returns:     0 if command added to pipelining buffer, with nothing transmitted
 */
 
 int
 */
 
 int
-smtp_write_command(void * sx, int mode, const char *format, ...)
+smtp_write_command(void * sx, int mode, const char * format, ...)
 {
 smtp_outblock * outblock = &((smtp_context *)sx)->outblock;
 int rc = 0;
 {
 smtp_outblock * outblock = &((smtp_context *)sx)->outblock;
 int rc = 0;
@@ -627,7 +681,6 @@ if (format)
     log_write(0, LOG_MAIN|LOG_PANIC_DIE, "overlong write_command in outgoing "
       "SMTP");
   va_end(ap);
     log_write(0, LOG_MAIN|LOG_PANIC_DIE, "overlong write_command in outgoing "
       "SMTP");
   va_end(ap);
-  string_from_gstring(&gs);
 
   if (gs.ptr > outblock->buffersize)
     log_write(0, LOG_MAIN|LOG_PANIC_DIE, "overlong write_command in outgoing "
 
   if (gs.ptr > outblock->buffersize)
     log_write(0, LOG_MAIN|LOG_PANIC_DIE, "overlong write_command in outgoing "
@@ -651,20 +704,18 @@ if (format)
 
   if (outblock->authenticating)
     {
 
   if (outblock->authenticating)
     {
-    uschar *p = big_buffer;
+    uschar * p = big_buffer;
     if (Ustrncmp(big_buffer, "AUTH ", 5) == 0)
       {
       p += 5;
     if (Ustrncmp(big_buffer, "AUTH ", 5) == 0)
       {
       p += 5;
-      while (isspace(*p)) p++;
+      Uskip_whitespace(&p);
       while (!isspace(*p)) p++;
       while (!isspace(*p)) p++;
-      while (isspace(*p)) p++;
+      Uskip_whitespace(&p);
       }
     while (*p) *p++ = '*';
     }
 
       }
     while (*p) *p++ = '*';
     }
 
-  HDEBUG(D_transport|D_acl|D_v) debug_printf_indent("  SMTP%c> %s\n",
-    mode == SCMD_BUFFER ? '|' : mode == SCMD_MORE ? '+' : '>',
-    big_buffer);
+  smtp_debug_cmd(big_buffer, mode);
   }
 
 if (mode != SCMD_BUFFER)
   }
 
 if (mode != SCMD_BUFFER)
@@ -802,8 +853,10 @@ smtp_context * sx = sx0;
 uschar * ptr = buffer;
 int count = 0;
 time_t timelimit = time(NULL) + timeout;
 uschar * ptr = buffer;
 int count = 0;
 time_t timelimit = time(NULL) + timeout;
+BOOL yield = FALSE;
 
 errno = 0;  /* Ensure errno starts out zero */
 
 errno = 0;  /* Ensure errno starts out zero */
+buffer[0] = '\0';
 
 #ifndef DISABLE_PIPE_CONNECT
 if (sx->pending_BANNER || sx->pending_EHLO)
 
 #ifndef DISABLE_PIPE_CONNECT
 if (sx->pending_BANNER || sx->pending_EHLO)
@@ -812,9 +865,8 @@ if (sx->pending_BANNER || sx->pending_EHLO)
   if ((rc = smtp_reap_early_pipe(sx, &count)) != OK)
     {
     DEBUG(D_transport) debug_printf("failed reaping pipelined cmd responsess\n");
   if ((rc = smtp_reap_early_pipe(sx, &count)) != OK)
     {
     DEBUG(D_transport) debug_printf("failed reaping pipelined cmd responsess\n");
-    buffer[0] = '\0';
     if (rc == DEFER) errno = ERRNO_TLSFAILURE;
     if (rc == DEFER) errno = ERRNO_TLSFAILURE;
-    return FALSE;
+    goto out;
     }
   }
 #endif
     }
   }
 #endif
@@ -845,7 +897,7 @@ for (;;)
      (ptr[3] != '-' && ptr[3] != ' ' && ptr[3] != 0))
     {
     errno = ERRNO_SMTPFORMAT;    /* format error */
      (ptr[3] != '-' && ptr[3] != ' ' && ptr[3] != 0))
     {
     errno = ERRNO_SMTPFORMAT;    /* format error */
-    return FALSE;
+    goto out;
     }
 
   /* If the line we have just read is a terminal line, line, we are done.
     }
 
   /* If the line we have just read is a terminal line, line, we are done.
@@ -873,7 +925,11 @@ distinguish between an unexpected return code and other errors such as
 timeouts, lost connections, etc. */
 
 errno = 0;
 timeouts, lost connections, etc. */
 
 errno = 0;
-return buffer[0] == okdigit;
+yield = buffer[0] == okdigit;
+
+out:
+  smtp_debug_resp(buffer);
+  return yield;
 }
 
 /* End of smtp_out.c */
 }
 
 /* End of smtp_out.c */