build: use pkg-config for i18n
[exim.git] / src / src / smtp_out.c
index 7b8212477af67f305d4c1f67774891159d8f88c0..2ea29a7e442e6d0e0f386188b35e57e74776b4f8 100644 (file)
@@ -2,9 +2,10 @@
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
+/* Copyright (c) The Exim Maintainers 2020 - 2024 */
 /* 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. */
 
@@ -252,44 +253,20 @@ 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)
 {
 {
-smtp_transport_options_block * ob =
-  (smtp_transport_options_block *)tb->options_block;
+transport_instance * tb = sc->tblock;
+smtp_transport_options_block * ob = tb->drinst.options_block;
 const uschar * dscp = ob->dscp;
 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;
-
+int sock, dscp_value, dscp_level, dscp_option;
 
 
-#ifndef DISABLE_EVENT
-deliver_host_address = host->address;
-deliver_host_port = port;
-if (event_raise(tb->event_action, US"tcp:connect", NULL, &errno)) return -1;
-#endif
-
-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. */
 
@@ -300,7 +277,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);
@@ -309,7 +287,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));
   }
@@ -317,24 +295,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 = sc->tblock->drinst.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)
+  expand_level++;
+  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 */
@@ -343,15 +373,16 @@ 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 */
       }
 # endif
     }
       setsockopt(sock, IPPROTO_TCP, TCP_FASTOPEN_CONNECT, US &on, sizeof(on));
       /* fastopen_blob = NULL;          lazy TFO, triggered by data write */
       }
 # endif
     }
+  expand_level--;
 #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)
     {
@@ -374,30 +405,14 @@ 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);
 
-  HDEBUG(D_transport|D_acl|D_v) debug_printf_indent(" connected\n");
+  /* 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);
   else
   if (getsockname(sock, (struct sockaddr *)(&interface_sock), &size) == 0)
     sending_ip_address = host_ntoa(-1, &interface_sock, NULL, &sending_port);
   else
@@ -408,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;
 }
 
 
 }
 
 
@@ -468,7 +496,7 @@ HDEBUG(D_transport|D_acl|D_v)
 #ifdef SUPPORT_SOCKS
   if (ob->socks_proxy) s = string_sprintf("%svia proxy ", s);
 #endif
 #ifdef SUPPORT_SOCKS
   if (ob->socks_proxy) s = string_sprintf("%svia proxy ", s);
 #endif
-  debug_printf_indent("Connecting to %s %s%s... ", sc->host->name, callout_address, s);
+  debug_printf_indent("Connecting to %s %s%s...\n", sc->host->name, callout_address, s);
   }
 
 /* Create and connect the socket */
   }
 
 /* Create and connect the socket */
@@ -501,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,10 +551,10 @@ Returns:     TRUE if OK, FALSE on error, with errno set
 static BOOL
 flush_buffer(smtp_outblock * outblock, int mode)
 {
 static BOOL
 flush_buffer(smtp_outblock * outblock, int mode)
 {
-int rc;
-int n = outblock->ptr - outblock->buffer;
+int n = outblock->ptr - outblock->buffer, rc;
 BOOL more = mode == SCMD_MORE;
 client_conn_ctx * cctx;
 BOOL more = mode == SCMD_MORE;
 client_conn_ctx * cctx;
+const uschar * where;
 
 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)" : "");
@@ -540,6 +567,7 @@ if (!(cctx = outblock->cctx))
   }
 
 #ifndef DISABLE_TLS
   }
 
 #ifndef DISABLE_TLS
+where = US"tls_write";
 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
 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
@@ -555,6 +583,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. */
 
+    where = US"smtp_connect";
     if (  (cctx->sock = smtp_connect(outblock->conn_args, &early_data))
        < 0)
       return FALSE;
     if (  (cctx->sock = smtp_connect(outblock->conn_args, &early_data))
        < 0)
       return FALSE;
@@ -563,6 +592,7 @@ else
     }
   else
     {
     }
   else
     {
+    where = US"send";
     rc = send(cctx->sock, outblock->buffer, n,
 #ifdef MSG_MORE
              more ? MSG_MORE : 0
     rc = send(cctx->sock, outblock->buffer, n,
 #ifdef MSG_MORE
              more ? MSG_MORE : 0
@@ -577,6 +607,7 @@ else
     This is despite NODELAY being active.
     https://bugzilla.redhat.com/show_bug.cgi?id=1803806 */
 
     This is despite NODELAY being active.
     https://bugzilla.redhat.com/show_bug.cgi?id=1803806 */
 
+    where = US"cork";
     if (!more)
       setsockopt(cctx->sock, IPPROTO_TCP, TCP_CORK, &off, sizeof(off));
 #endif
     if (!more)
       setsockopt(cctx->sock, IPPROTO_TCP, TCP_CORK, &off, sizeof(off));
 #endif
@@ -585,7 +616,8 @@ else
 
 if (rc <= 0)
   {
 
 if (rc <= 0)
   {
-  HDEBUG(D_transport|D_acl) debug_printf_indent("send failed: %s\n", strerror(errno));
+  HDEBUG(D_transport|D_acl) debug_printf_indent("%s (fd %d) failed: %s\n",
+    where, cctx->sock, strerror(errno));
   return FALSE;
   }
 
   return FALSE;
   }
 
@@ -596,22 +628,6 @@ 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                 *
 *************************************************/
@@ -623,7 +639,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
 
@@ -654,7 +670,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 "
@@ -678,13 +693,13 @@ 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++;
-      while (!isspace(*p)) p++;
-      while (isspace(*p)) p++;
+      Uskip_whitespace(&p);
+      Uskip_nonwhite(&p);
+      Uskip_whitespace(&p);
       }
     while (*p) *p++ = '*';
     }
       }
     while (*p) *p++ = '*';
     }